ASUS Series-Router SQLi in libbwdpi_sql.so
好长一段时间没更新关于实战的内容了,这段时间一直在钻研 kernel 的知识,还是需要拾起一下老本行.
在上周发现 ASUS libbwdpi_sql.so 中函数 api 存在 SQL injection 漏洞,并且观测到该漏洞在该至少已经存在了六年之久,因此想将该漏洞作为专门的对于 lib 安全的文章.
目前已经发现 GT-AC、GT-AX、DSL-AC 等多个系列路由的 web api 中均引用了该库,并且基于该库造成的远程 Authenticated SQLi(RCE in specified situation when user turns on a specific debugging mode)
A report on the ASUS Routers Vuls in /usr/lib/libbwdpi_sql.so sqlite_Stat_hook caused SQL Injection in multiple series(Or even RCE in specified situation)
Vulnerability Product: GT-AC5300、GT-AC2900、specified versions in GT-AX and other versions that have not yet been discovered
Vulnerability Test Firmware Version: GT-AC5300_3.0.0.4_386_51569-g9ee6a79_ubi.w (latest)
Vulnerability type: SQL Injection(Or even RCE in specified situation)
There is a SQL Injection in ASUS specified lib /usr/lib/libbwdpi_sql.so
. When an attacker has a low privilege account of the remote router system(Triggering this vulnerability on some router firmware may not require any permissions), the attacker could trigger SQL injection through the lib api: /usr/lib/libbwdpi_sql.so
.sqlite_Stat_hook
(On the GT-AC5300 the remote attacker calling path is /usr/sbin/httpd
.appGet.cgi
.bwdpi_appStat
-> /usr/lib/libbwdpi_sql.so
.sqlite_Stat_hook
), and even cause RCE in specified situation
Contents |
---|
Bin re & Lib re |
Bin re & Lib re & Simulation |
Bin re & Lib re & Investigation |
Firmware re |
Harm |
Info |
Bin re and Lib re:
Some ASUS router firmwares are ubifs (UBI file system) file systems. For example, the following example is GT-AC5300_3.0.0.4_386_51569-g9ee6a79_ubi.w. You only need to install ubireader and binwalk to easily extract the firmware file system. I will not go into details.
Let’s directly focus on libbwdpi_sql.so
. The basic information about the library is as follows
类别: ELF32
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: ARM
I will not go into details about the discovery process of the libbwdpi_sql.so. You can see that the call stack is as follows: libbwdpi_sql.so
is essentially the function interface of libsqlite3.so.0.8.6
/usr/lib/libbwdpi_sql.so sqlite_Stat_hook
/usr/lib/libbwdpi_sql.so j_bwdpi_appStat
/usr/lib/libbwdpi_sql.so bwdpi_appStat
/usr/lib/libbwdpi_sql.so j_sql_get_table
/usr/lib/libsqlite3.so.0.8.6 sqlite3_get_table
...
let’s just directly focus on the sqlite_Stat_hook
in /usr/lib/libbwdpi_sql.so
, you can find that the param client
just was simply formatted into where
without any further filter when client != "all"
is true. (the following Figure 3).
Figure 3
And if v13 != 0
is true, it means a1 == 0 && _IF_mode_e_detail_then_0
is true. IF_mode_e_detail_then_0 = _IF_mode_e_detail_then_0 = strcmp(mode, "detail")
is true. so mode != "detail" && a1 == 0
and where is the a1
comes from? a1
is actually the first parameter of the function sqlite_Stat_hook
int __fastcall sqlite_Stat_hook(int a1, const char *client, const char *mode, unsigned int dura, const char *date, _DWORD *a6, FILE *a7);
But don’t worry, in most cases, the first parameter of the router firmware calling this function is 0, as shown in the example GT-AC5300 I will give below
Let’s focus on the most important parameter where
. what happend to where
? You can see that when mode == "hour" && dura == 24
is true, where
is called as the third parameter to j_bwdpi_appStat
(the following Figure 4)
Figure 4
Then, let’s see what happend in j_bwdpi_appStat
. j_bwdpi_appStat
call the bwdpi_appStat
with the original parameters (the following Figure 5)
You can clearly find that when having != NULL && where[0] != NULL
is true, the where
is directly formatted into the sql_query
and the SQL statement is executed without any filtering, resulting in SQL injection vulnerability (the following Figure 6)
Figure 5
Figure 6
Then what’s the j_sql_get_table
? Does j_sql_get_table
set any bypass or ORM processing for sql? Further tracking found that j_sql_get_table
calls sqlite3_get_table
, sqlite3_get_table
is actually an interface function located in /usr/lib/libsqlite3.so.0.8.6
as a Cpl interface. its definition is as follows
int sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
void sqlite3_free_table(char **result);
//This is a legacy interface that is preserved for backwards compatibility. Use of this interface is not recommended.
//Definition: A result table is memory data structure created by the sqlite3_get_table() interface. A result table records the complete query results from one or more queries.
//The table conceptually has a number of rows and columns. But these numbers are not part of the result table itself. These numbers are obtained separately. Let N be the number of rows and M be the number of columns.
Obviously, this function as a Cpl interface does not have any filtering, it just executes the second parameter and outputs the result
So in theory, when you call the function in this form, it will trigger SQL injection or even RCE. Of course, this is the calling method for local command execution, and it must be combined with different APIs of each router to achieve remote command execution.
sqlite_Stat_hook(0, "[SQLi Payload]", "hour", "24", [Irrelevant parameters], [Irrelevant parameters], ...)
A possible local call POC is as follows
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef void (*sqlite_Stat_hook_t)(int type, char *client, char *mode, char *dura, char *date, int *retval, void *wp);
int main() {
void *handle = dlopen("/usr/lib/libbwdpi_sql.so", RTLD_LAZY);
...[Omitted]...
sqlite_Stat_hook_t sqlite_Stat_hook = (sqlite_Stat_hook_t)dlsym(handle, "sqlite_Stat_hook");
if (!sqlite_Stat_hook) {
fprintf(stderr, "Error loading function: %s\n", dlerror());
dlclose(handle);
return 1;
}
sqlite_Stat_hook(0, "[SQLi Payload]", "hour", "24", "1000", &retval, NULL);
dlclose(handle);
return 0;
}
Next, I discovered an even more bizarre vulnerability, which even has the opportunity to exploit RCE after SQL injection.
if /tmp/BWSQL_LOG
exists, the sql_query
will be formatted into command and executed directly, causing RCE (the following Figure 7)
Figure 7
But it is difficult to make /tmp/BWQL_LOG
exist, so no further discussion will be made here
Bin re and Lib re and Simulation:
Example firmware download: GT-AC5300_3.0.0.4_386_51569-g9ee6a79_ubi.w
The firmware took me a very long time to simulate and run, and I spent most of my time to find a way to running it. Sometimes there suddenly a strange errors appear, I will not go into details here. For problems with ASUS router simulation, you can refer to this article for assistance. (Given that the complexity of this firmware simulation is not at a low level, user-mode simulation is not recommended excepted you are just wanna debug a single file.)
iotsec-zone 模拟启动ASUS华硕路由器的http服务
I still chose to use user mode simulation. Running a single program in user mode simulation is not much different from running in system mode, and in some aspects it is even closer to the physical machine.
Since some conditions are difficult to implement in a virtual machine, I chose to directly run the sqlite_Stat_hook
by modifying the PC register and function parameters(reg R0
、R1
、R2
… ) in pwndbg instead of writing a C code to link and debug it.
First, you need to move qemu-arm-static
to root, and use the unpacked file system as the root directory, mount the necessary basic files.(you also need to creat other basic file I don’t metioned, such as /var/run
, /tmp/etc/
…)
You only need to run this command once.
cp `whereis qemu-arm-static | awk '{print $2}'` ./rootfs_ubifs
umount ./rootfs_ubifs/proc
umount ./rootfs_ubifs/dev
mount -o bind /dev ./rootfs_ubifs/dev && mount -t proc /proc ./rootfs_ubifs/proc
cd rootfs_ubifs
chroot . ./bin/bash
Once you finish it, run qemu-arm-static
, and start pwndbg, attach it.
./qemu-arm-static -g 12345 ./usr/sbin/httpd
pwndbg ./ubifs-root/0/rootfs_ubifs/usr/sbin/httpd -ex "b *0x176c8" -ex "b *0x176c8" -ex "target remote 127.0.0.1:12345"
please attention:
0x176c8
is main
of httpd
0x16a6c
is the interface func of sqlite_Stat_hook
Then continue the program. After it, you need to get the address of libbwdpi_sql.so
by following command after load the libs(must after load the libs)
cat /proc/`ps -e | grep "qemu-arm-static" | awk '{print $1}'`/maps | grep "libbwdpi_sql.so" | head -n 1
Here the address of libbwdpi_sql.so is 0x40d3f000, as you can see
Continue the program, once the reg PC
is 0x176c8
(meet the break point 1), input following command in pwndbg, modify the r0-r4
、stack
to arg1-arg7
, modify the PC
to sqlite_Stat_hook
.
Don’t forget to set break point in libbwdpi_sql.so
so that you could debug the program
# 1st para
set $r0=0
# 2nd para
set {char[16]} 0xbb010 = "\"OR\"1\"=\"1"
set $r1=0xbb010
# 3rd para
set {char[16]} 0xbb020 = "hour"
set $r2=0xbb020
# 4th para
set {char[16]} 0xbb030 = "24"
set $r3=0xbb030
# 7th para
set $sp=$sp - 4
set {unsigned int} $sp = 0xbb280
# 6th para
set $sp=$sp - 4
set {unsigned int} $sp = 0xbb080
# 5th para
set $sp=$sp - 4
set {char[16]} 0xbb040 = "1000"
set {unsigned int} $sp = 0xbb040
# modify pc to sqlite_Stat_hook
set $pc=0x0031B08
b *(0x0031B08) //the call of sqlite_Stat_hook
b *([Base of libbwdpi_sql.so] + 0x21c4) //the addr of sqlite_Stat_hook
b *([Base of libbwdpi_sql.so] + 0x1868) //the addr of bwdpi_appStat
After you run set $pc=0x0031B08
, you entered the sqlite_Stat_hook debugging. the values of the reg r0-r4
、stack and the PC
register and their corresponding values should be as follows. After a simple call of sqlite_Stat_hook
, you could debug the program.
Bin re and Lib re and Investigation:
After investigation, it was found that the injection vulnerability of the SQL library has existed for at least six years, The following is a search for the possible source code of the same dynamic link library, which was pulled by a user on GitHub six years ago. It clearly shows that the splicing vulnerability has existed for at least six years.
Even a few years ago, you can observe that there is no echo debug statement in the lib that is vulnerable to RCE, as shown below. A few years later, ASUS not only added the echo’ debug statement that is more prone to RCE vulnerability, but also did not fix this obvious vulnerability.
This code was submitted six years ago when the DSL-AC88U product was initially committed to this repository. Therefore, you can also reproduce this vulnerability on multiple models such as DSL-AC88U
Firmware re:
(Take the GT-AC5300 as an example)
There happens to be a latest version of the GT-AC5300 router firmware that uses this lib and passes its web parameters into sqlite_Stat_hook, so I will analyze the firmware next
Some ASUS router firmwares are ubifs (UBI file system) file systems. For example, the following example is GT-AC5300_3.0.0.4_386_51569-g9ee6a79_ubi.w. You only need to install ubireader and binwalk to easily extract the firmware file system. I will not go into details.
Firmware download: GT-AC5300_3.0.0.4_386_51569-g9ee6a79_ubi.w
Firstly let’s see the function of web api bwdpi_appStat
in /usr/sbin/httpd
, I call it T_danger_SQL
here. (the following Figure 1)
you can find that here it received GET args from user, Then call sqlite_Stat_hook
with the client
, mode
, dura
, date
received from the user as parameters(the following Figure 2), please attention that here is no any filter
In the following steps, you will find that the param client
is not filtered until the end
Figure 1
Figure 2
You can directly inject malicious payload into the where parameter, causing SQL injection or even RCE. The only drawback is that authorization is required to call this function in GT-AC5300.
You can see that the call stack is as followss
/appGet.cgi?hook=bwdpi_appStat() Web interface
/usr/sbin/httpd T_danger_SQL(Self-naming function)
/usr/lib/libbwdpi_sql.so sqlite_Stat_hook
/usr/lib/libbwdpi_sql.so j_bwdpi_appStat
/usr/lib/libbwdpi_sql.so bwdpi_appStat
/usr/lib/libbwdpi_sql.so j_sql_get_table
/usr/lib/libsqlite3.so.0.8.6 sqlite3_get_table
...
HARM:
When an attacker has a low privilege account of the system, the attacker could trigger SQL injection through the bwdpi_appStat
in appGet.cgi
, attacker could SQL Inject by blind injection (such as time based injection, boolean based injection), and even cause RCE in specified situation
Info
Thu, Feb 06, 12:36 —— Send the report to ASUS
Wed, Feb 12, 14:53 —— ASUS published a patch fix of next version to fix the vul
Wed, Feb 19, 11:44 —— ASUS confirmed the vul, will add a hall of fame and assign a CVE.
discovered by leeya_bug