点此返回首页

leeya_bug@home:~$

一个Coder,Attacker,Creator

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

asus

类别:                        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).

asus
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

asus

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)

asus
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)

asus
Figure 5

asus
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.

asus

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)

asus
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 R0R1R2 … ) 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

asus

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 

asus
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-r4stack 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

asus

# 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.

asus

asus

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.

https://github.com/smx-smx/bcm63138/blob/efd289adf8f0ea2b6c618dc7a427eb37026fc4e1/package/asus_bwdpi_source/src/asus_sql/sqlite_stat.c#L226

asus

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.

https://github.com/smx-smx/bcm63138/blob/efd289adf8f0ea2b6c618dc7a427eb37026fc4e1/package/asus_bwdpi_source/src/asus_sql/sqlite_stat.c#L87

asus

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

https://github.com/smx-smx/bcm63138/tree/efd289adf8f0ea2b6c618dc7a427eb37026fc4e1/package/asus_bwdpi_source/src/asus_sql

asus

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

asus
Figure 1

asus
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