Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple vulnerabilities leading to preauth RCE #285

Closed
rekter0 opened this issue Aug 7, 2021 · 2 comments
Closed

multiple vulnerabilities leading to preauth RCE #285

rekter0 opened this issue Aug 7, 2021 · 2 comments
Labels
bug Something isn't working

Comments

@rekter0
Copy link

rekter0 commented Aug 7, 2021

i found haproxy-wi in aws/digitalocean marketplace when i was looking for a solution to manage multiple reverse proxies, since it was opensource i peaked at how it works and found some critical issues when combined leading to pre-auth RCE

# SQL injections:

Inside /app/sql.py some SQL statements have user controlled input supplied directly into SQL queries

## Unauthenticated SQLi

when an attacker request any of the pages inside /app folder, authentication is checked via funct.check_login()

def check_login():
	import sql
	import http.cookies
>	cookie = http.cookies.SimpleCookie(os.environ.get("HTTP_COOKIE"))
>	user_uuid = cookie.get('uuid')
	ref = os.environ.get("REQUEST_URI")

	sql.delete_old_uuid()

	if user_uuid is not None:
>		sql.update_last_act_user(user_uuid.value)
		if sql.get_user_name_by_uuid(user_uuid.value) is None:
			print('<meta http-equiv="refresh" content="0; url=login.py?ref=%s">' % ref)
			return False
	else:
		print('<meta http-equiv="refresh" content="0; url=login.py?ref=%s">' % ref)
		return False

check_login() takes uuid cookie value and try to update expiration timestamp for the given uuid with sql.update_last_act_user(user_uuid.value)

def update_last_act_user(uuid):
	cursor = conn.cursor()
	session_ttl = get_setting('session_ttl')

	if mysql_enable == '1':
>		sql = """ update uuid set exp = now()+ INTERVAL %s day where uuid = '%s' """ % (session_ttl, uuid)
	else:
>		sql = """ update uuid set exp = datetime('now', '+%s days') where uuid = '%s' """ % (session_ttl, uuid)
	try:
		cursor.execute(sql)
	except Exception as e:
		funct.out_error(e)

uuid cookie value is directly supplied into the query, so an unauthenticated attacker can perform a blind SQL injection to dump the database or extract a valid uuid to bypass authentication

## authenticated SQLi

One example of authenticated SQLi via reaching select_servers function

def select_servers(**kwargs):
	cursor = conn.cursor()
	sql = """select * from servers where enable = '1' ORDER BY groups """

	if kwargs.get("server") is not None:
		sql = """select * from servers where ip='%s' """ % kwargs.get("server")
	if kwargs.get("full") is not None:
		sql = """select * from servers ORDER BY hostname """
	if kwargs.get("get_master_servers") is not None:
		sql = """select id,hostname from servers where master = 0 and type_ip = 0 and enable = 1 ORDER BY groups """
	if kwargs.get("get_master_servers") is not None and kwargs.get('uuid') is not None:
		sql = """ select servers.id, servers.hostname from servers 
			left join user as user on servers.groups = user.groups 
			left join uuid as uuid on user.id = uuid.user_id 
			where uuid.uuid = '%s' and servers.master = 0 and servers.type_ip = 0 and servers.enable = 1 ORDER BY servers.groups 
			""" % kwargs.get('uuid')
	if kwargs.get("id"):
		sql = """select * from servers where id='%s' """ % kwargs.get("id")
	if kwargs.get("hostname"):
		sql = """select * from servers where hostname='%s' """ % kwargs.get("hostname")
	if kwargs.get("id_hostname"):
		sql = """select * from servers where hostname='%s' or id = '%s' or ip = '%s'""" % (kwargs.get("id_hostname"), kwargs.get("id_hostname"), kwargs.get("id_hostname"))
	if kwargs.get("server") and kwargs.get("keep_alive"):
		sql = """select active from servers where ip='%s' """ % kwargs.get("server")
	try:
		cursor.execute(sql)
	except Exception as e:
		funct.out_error(e)
	else:
		return cursor.fetchall()

there's multiple injection points from user supplied input here

one way to reach this is from hapservers.py

[...]
[...]
serv = form.getvalue('serv')
service = form.getvalue('service')
[...]
[...]
 if funct.check_is_server_in_group(serv):
            servers = sql.select_servers(server=serv)

this could be exploited by least privilege account such as guest

There's some more functions supplying user input to SQL queries

# Command injection:

Inside /app/funct.py and /api/api_funct.py some commands executed are supplied with user input

one of many examples of a second order command injection here:

def get_all_stick_table():
	import sql
	hap_sock_p = sql.get_setting('haproxy_sock_port')
	cmd = 'echo "show table"|nc %s %s |awk \'{print $3}\' | tr -d \'\n\' | tr -d \'[:space:]\'' % (serv, hap_sock_p)
	output, stderr = subprocess_execute(cmd)
	return output[0]

haproxy_sock_port is stored in settings table, and an authenticated user can change it from https://[%HOST%]/app/users.py#settings then calls options page to call that function and execute arbitrary system command

most cmds in different functions are prone to command injection or second order from settings stored in the database and user controlled

# Conclusion

combining both unauthenticated SQLi to grab a valid uuid and bypass authentication, then use command injection an unauthenticated user can achieve pre-auth RCE

@Aidaho12 Aidaho12 added the bug Something isn't working label Aug 7, 2021
@Aidaho12
Copy link
Member

Aidaho12 commented Aug 9, 2021

Thank you very much for you report!

The most sql requests have been re-writen to peewee ORM, and there isn't sqli now.

About command injection: types of parameters have started to be checked, it should close the most of injection

@Aidaho12 Aidaho12 closed this as completed Aug 9, 2021
@Derkness
Copy link

Derkness commented Sep 18, 2021

Would it be possible to link the fixing commit/pr? Just so I can see how to avoid this for my studies. (Mainly for the command injection)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants