前言
这次再来复现一个路由器的 sql 注入类型的漏洞。在实际环境中可能注入会比较少见,而且这个洞也比较老了,但是为了掌握更大的漏洞攻击面还是有必要去接触的。
漏洞分析
环境搭建
还是使用 qemu-user 模式,同样的还是编写一个 sh 脚本:
#!/bin/bash
INPUT="$1"
LEN=$(echo -n "$INPUT"|wc -c)
DEBUG="$2"
if [ "$DEBUG" == "-d" ]
then
echo $INPUT | chroot . ./qemu -E REMOTE_ADDR="127.0.0.1" -E CONTENT_TYPE="multipart/x-form-data" -E REQUEST_METHOD="POST" -g 23946 -E CONTENT_LENGTH=$LEN ./usr/bin/my_cgi.cgi
else
echo $INPUT | chroot . ./qemu -E REMOTE_ADDR="127.0.0.1" -E CONTENT_TYPE="multipart/x-form-data" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN ./usr/bin/my_cgi.cgi
fi
不带调试的运行结果:
使用 IDA 进行动态调试发现已经将我们的输入直接拼接到 bss 段的 sql 变量中
漏洞分析
在进行漏洞分析时,还发现了存在多处的栈溢出漏洞。
多处栈溢出
在 do_login 函数的开头,s1 寄存器的值为 “request”:
在执行第一个 strcpy 函数时,直接将 user_name 的值复制到栈上,导致栈溢出
同样在执行第二个 strcpy 函数之前,在右边的 general register
窗口中跟进 a1 寄存器的值(在 a1 寄存器上右键,Jump in a new window
)
同样使用 Ghidra 9.0 的反汇编窗口中也可以很清楚看到这个漏洞,*(iParm1 + 0x299) 和 *(iParm1 + 0x512)
分别为传入的 user_name 和 user_pass
继续往下分析,又有一个危险函数 sprintf ,这里会将用户名和密码格式化一个 sql 语句到 bss 段的 sql 变量,所以按理说如果输入的 user_name 或者 user_pass 过长,那么这里也是存在一处 bss 溢出的。
Ghidra 中的代码:
sprintf(sql,"select level from user where user_name='%s' and user_pwd='%s'",&local_60,&local_40);
接着会往下执行 exec_sql 函数来执行 sql 函数,这里先 f8 步过
这里执行 sql 查询以及下面的判断如下,经过 exec_sql 函数的执行之后,取得返回值来判断登录的状态
iVar1 = exec_sql(my_db,sql,__ptr);
if (iVar1 == 0) {
if (*(int *)((int)__ptr + 0x4f200) == 0) {
__n_00 = strlen(sql);
memset(sql,0,__n_00);
sprintf(sql,"select level from user where user_name='%s'",&local_60);
iVar2 = exec_sql(my_db,sql,__ptr); // 无过滤直接拼接 SQL 语句
if (iVar2 == 0) {
if (*(int *)((int)__ptr + 0x4f200) == 0) {
uVar2 = *strings;
}
else {
uVar2 = strings[1];
}
add_msg(uVar2,"login");
}
free(__ptr);
set_redirect_page(&DAT_00417e98);
return;
}
iVar2 = atoi((char *)((int)__ptr + 0x20));
iVar2 = add_login_user(uParm3,iVar2);
if (iVar2 == 0) {
add_msg(strings[3],"login");
pcVar3 = "back";
}
else {
if (iParm4 != 1) {
free(__ptr);
set_redirect_page(0x4174a8);
return;
}
pcVar3 = "wlan_client_basic";
}
}
else {
pcVar3 = "login";
}
free(__ptr);
set_redirect_page(pcVar3);
return;
这里确实是将用户名和密码不经过过滤就直接传入拼接出 sql 语句并且传入 exec_sql 函数
exec_sql(db,"select level...",heap_ptr)
在 gdb-multiarch 动态调试中也可以发现:
这里的 exec_sql
函数为位于:
./squashfs-root/lib/libdbapi.so.1.0.0
我们把这个链接库文件加载到 Ghidra 中进行静态分析。
在 0x766c1034 处下一个断点,可以知道这里调用的是 sqlite3_exec,此函数的地址是 0x76769470
此时执行的函数如下,拼接传入的 sql 语句位于第二个参数:
sqlite3_exec(db,"select level...",(void *)(callback_exec),**a3)
通过 vmmap 命令可以找到这个函数位于:
/lib/libsqlite3.so.0.8.6
继续将他放到 Ghidra 中分析,在左边的导出表 Exports 中找到 sqlite3_exec 函数
发现这里执行的都是 sqlite3 中标准的数据库函数,即 sqlite3_open、sqlite3_prepare、sqlite3_exec 等等,参考:
https://www.cnblogs.com/zfyouxi/p/5258589.html
这里 exec_sql 函数的原型:
函数分析
回到上面的函数,关注前两个参数:
- 第一个参数为经过 sqlite3_open 函数返回的指向数据库的指针。
- 第二个参数为拼接的 sql 语句(sql statement)
接下来依次经过了 sqlite3_prepare 、sqlite3_column_count、sqlite3_setp
- 参考上面的资料,这几个函数的作用依次为:
- 初始化数据库的连接,将结果的对象指针存储到 &stmt 中
- sqlite3_column_count 顾名思义返回查询的表中的列数
- sqlite3_setp 函数将 stmt 指针作为参数执行 sql 语句
接下来的一些操作就是返回执行的结果,对 sqlite3 数据库查询感兴趣的可以自行去了解下。
总结:
综上,这里主要是没有对用户的输入进行过滤,直接将拼接完的 sql 语句带到 exec_sql 函数中,并执行 sqlite3 的查询语句,造成注入。