Talos Vulnerability Report

TALOS-2018-0620

TP-Link TL-R600VPN HTTP Server fs directory Remote Code Execution Vulnerability

November 19, 2018
CVE Number

CVE-2018-3951

Summary

An exploitable remote code execution vulnerability exists in the HTTP header-parsing function of the TP-Link TL-R600VPN HTTP Server. A specially crafted HTTP request can cause a buffer overflow, resulting in remote code execution on the device. An attacker can send an authenticated HTTP request to trigger this vulnerability.

Tested Versions

TP-Link TL-R600VPN HWv3 FRNv1.3.0

Product URLs

https://www.tp-link.com/us/products/details/cat-4909_TL-R600VPN.html

CVSSv3 Score

7.2 - CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-120 - Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')

Details

If a request is made to any of the below examples of pages in the /fs/ directory, the device's web server will crash due to a buffer overflow, making the management portal unaccessible until a power cycle has occurred.

Vulnerable pages include: http://192.168.0.1/fs/help http://192.168.0.1/fs/images http://192.168.0.1/fs/frames http://192.168.0.1/fs/dynaform http://192.168.0.1/fs/localiztion

Any requests to these pages must be properly authenticated and contain a valid referrer header.

An example of a malicious GET request is as follows:

GET /fs/help HTTP/1.1
Host: 192.168.0.1
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python
Referer: http://192.168.0.1/userRpm/MenuRpm.htm
Authorization: Basic YWRtaW46YWRtaW4=

When accessing any valid /fs/* page, the application incorrectly parses the HTTP header it has passed. In the function 'httpGetMimeTypeByFileName', the web server attempts to parse out headers from the user's GET request. During this processing, the server calculates the length of the user-controlled HTTP header buffer and adds the value to the input buffer offset. This creates an overflow condition when a longer than expected GET request is processed.

The user-controlled buffer is first parsed backward, character by character, searching for a single period (0x2e), loading each character into the $s0 register along the way. When a single period is encountered, the extracted string is processed, character by character, in the toupper function, the result of which is written to a stack-based buffer by a store byte instruction. The program continues execution until it reaches the httpGetMimeTypeByFileName function epilogue where the return address and five saved registers are overwritten with the user-supplied values. This gives the user control of the return value, as well as the means to remotely execute code in the context of httpd.

[Annotated Disassembly / Decompilation output]

#
# extracts a header substring
#
LOAD:00425CDC loc_425CDC:
LOAD:00425CDC                 la      $t9, strlen
LOAD:00425CE0                 sw      $zero, 0x38+var_20($sp)
LOAD:00425CE4                 jalr    $t9 ; strlen
LOAD:00425CE8                 sh      $zero, 0x38+var_1C($sp)
LOAD:00425CEC                 addu    $s0, $v0
LOAD:00425CF0                 li      $v0, 0x2E                         # specify the character to look for: '.'
LOAD:00425CF4                 lbu     $v1, 0($s0)
LOAD:00425CF8                 lw      $gp, 0x38+var_28($sp)
LOAD:00425CFC                 beq     $v1, $v0, loc_425D14
LOAD:00425D00                 li      $v1, 0b101110
LOAD:00425D04
LOAD:00425D04 loc_425D04:                                                # loop backwards until a '.' is found
LOAD:00425D04                 addiu   $s0, -1
LOAD:00425D08                 lbu     $v0, 0($s0)                        # load the character into $s0
LOAD:00425D0C                 bne     $v0, $v1, loc_425D04
LOAD:00425D10                 nop
LOAD:00425D14
LOAD:00425D14 loc_425D14:
LOAD:00425D14                 addiu   $s0, 1                             # move past the '.' character
LOAD:00425D18                 b       loc_425D34
LOAD:00425D1C                 move    $s1, $zero

#
# loads header data onto stack via a store byte call from $s0 register
#
LOAD:00425D20 loc_425D20:
LOAD:00425D20                 lbu     $a0, 0($a0)
LOAD:00425D24                 jalr    $t9 ; toupper                      # returns an upper case version of the character where possible
LOAD:00425D28                 nop
LOAD:00425D2C                 lw      $gp, 0x38+var_28($sp)              # here $gp is referencing $s2, making $s2 the place for the next char on the stack buffer
LOAD:00425D30                 sb      $v0, 0($s2)                        # stores the character into $s2
LOAD:00425D34
LOAD:00425D34 loc_425D34:
LOAD:00425D34                 la      $t9, strlen
LOAD:00425D38                 jalr    $t9 ; strlen                       # calculates the length of the entire user-supplied string
LOAD:00425D3C                 move    $a0, $s0                           # place the user-controlled buffer into arg0
LOAD:00425D40                 addiu   $v1, $sp, 0x38+var_20
LOAD:00425D44                 lw      $gp, 0x38+var_28($sp)
LOAD:00425D48                 sltu    $v0, $s1, $v0
LOAD:00425D4C                 addu    $a0, $s0, $s1
LOAD:00425D50                 addu    $s2, $v1, $s1
LOAD:00425D54                 la      $t9, toupper
LOAD:00425D58                 bnez    $v0, loc_425D20                    # loop continuation branch
LOAD:00425D5C                 addiu   $s1, 1
LOAD:00425D60                 la      $s3, aSkDportHttpPor               # loop exit branch
LOAD:00425D64                 lw      $v0, (off_57805C - 0x580000)($s3)
LOAD:00425D68                 beqz    $v0, loc_425DB4
LOAD:00425D6C                 move    $s1, $zero
LOAD:00425D70                 addiu   $s2, $s3, (off_57805C - 0x580000)

#
# registers get overwritten with the controlled stack values
#
LOAD:00425DB4 loc_425DB4:
LOAD:00425DB4
LOAD:00425DB4                 lw      $ra, 0x38+var_4($sp)               # loads user-supplied address from stack buffer
LOAD:00425DB8                 lw      $s4, 0x38+var_8($sp)               # loads user-supplied address from stack buffer
LOAD:00425DBC                 lw      $s3, 0x38+var_C($sp)               # loads user-supplied address from stack buffer
LOAD:00425DC0                 lw      $s2, 0x38+var_10($sp)              # loads user-supplied address from stack buffer
LOAD:00425DC4                 lw      $s1, 0x38+var_14($sp)              # loads user-supplied address from stack buffer
LOAD:00425DC8                 lw      $s0, 0x38+var_18($sp)              # loads user-supplied address from stack buffer
LOAD:00425DCC                 jr      $ra                                # jumps to user-supplied address
LOAD:00425DD0                 addiu   $sp, 0x38
LOAD:00425DD0  # End of function httpGetMimeTypeByFileName

Crash Information

[  221.436000] do_page_fault() #2: sending SIGSEGV to httpd for invalid read access from
[  221.436000] 52544156 (epc == 52544157, ra == 52544157)
[  221.460000] Cpu 0
[  221.464000] $ 0   : 00000000 00000001 00514004 00000048
[  221.476000] $ 4   : 7ddff821 0051432d 01010101 80808080
[  221.484000] $ 8   : 00000002 fffffffe 00000000 00000006
[  221.488000] $12   : 19999999 00000000 00000057 00425d2c
[  221.496000] $16   : 484f5249 5a415449 4f4e3a20 42415349
[  221.500000] $20   : 43205957 00679914 006798f0 00000000
[  221.504000] $24   : 00000132 2ab02820                  
[  221.516000] $28   : 00598790 7ddff840 7ddffa62 52544157
[  221.524000] Hi    : 000001fa
[  221.524000] Lo    : 00059cf8
[  221.528000] epc   : 52544157 0x52544157
[  221.532000]     Not tainted
[  221.532000] ra    : 52544157 0x52544157
[  221.540000] Status: 0000a413    USER EXL IE 
[  221.540000] Cause : 10800008
[  221.544000] BadVA : 52544156
[  221.544000] PrId  : 00019300 (MIPS 24Kc)
[  221.556000] Modules linked in:
[  221.564000] Process httpd (pid: 9668, threadinfo=8f08c000, task=8f05f6e0, tls=00000000)
[  221.572000] Stack : 34365957 52544157 343d0d0a 0d0a002f 00598790 00000000 7ddffa62 2abf117c
[  221.576000]         2ac3db70 2accb61c 2ac3db70 005a3ab0 2ac3db70 005a3ab0 005a3ab0 0042cc9c
[  221.584000]         2ac3db70 005a3c38 00598790 005a3c38 00000000 00000801 00000000 00000000
[  221.596000]         0000a003 000041ed 00000002 000003e8 000003e8 00000000 00000000 00000000
[  221.608000]         00000000 00001000 00000000 5aa1b2f2 00000000 57a1bf75 00000000 5aa07904
[  221.616000]         ...
[  221.616000] Call Trace:
[  221.616000] [<803dfeef>] input_ff_upload+0xbb/0x2a0
[  221.624000] 
[  221.624000] 
[  221.624000] Code: (Bad address in epc)
[  221.628000] 
[  221.644000] httpd/9668: potentially unexpected fatal signal 11.
[  221.652000] 
[  221.656000] Cpu 0
[  221.656000] $ 0   : 00000000 00000001 00514004 00000048
[  221.664000] $ 4   : 7ddff821 0051432d 01010101 80808080
[  221.672000] $ 8   : 00000002 fffffffe 00000000 00000006
[  221.684000] $12   : 19999999 00000000 00000057 00425d2c
[  221.688000] $16   : 484f5249 5a415449 4f4e3a20 42415349
[  221.696000] $20   : 43205957 00679914 006798f0 00000000
[  221.700000] $24   : 00000132 2ab02820                  
[  221.704000] $28   : 00598790 7ddff840 7ddffa62 52544157
[  221.708000] Hi    : 000001fa
[  221.708000] Lo    : 00059cf8
[  221.712000] epc   : 52544157 0x52544157
[  221.724000]     Not tainted
[  221.728000] ra    : 52544157 0x52544157
[  221.732000] Status: 0000a413    USER EXL IE 
[  221.732000] Cause : 10800008
[  221.732000] BadVA : 52544156
[  221.736000] PrId  : 00019300 (MIPS 24Kc)

Timeline

2018-06-28 - Vendor Disclosure
2018-10-09 - Vendor provided first beta test; Issue not fixed
2018-10-11 - Vendor provided new beta; Fix confirmed
2018-11-19 - Public Release

Credit

Discovered by Jared Rittle and Carl Hurd of Cisco Talos