web
Challenges

grandas_notes

My grandma is into vibe coding and has developed this web application to help her remember all the important information. It would work be great, if she wouldn't keep forgetting her password, but she's found a solution for that, too.
tl;dr: The login.php endpoint leaks how many leading characters of the submitted password are correct via a flash message (“you got X characters correct!”). (🗣️🗣️grandma should not be vibe coding~)
Because that prefix count is available for any user, we can incrementally brute-force the admin password one character at a time: try all printable characters for the next position and keep the one that bumps the count. Once we have the full password, we log in as admin and read the note (the flag) from dashboard.php.

Okay now here is the long version. Now first look at the code base, we need to identify where is the flag?
In docker file (as environment variable) access by the web app in config.php when it seed the data for the webapp. And later on It lives in the database, in the users.note column for the admin user, and is displayed to the logged-in user on the dashboard.
So obviously, naive solution is log in as admin → GET /dashboard.php → read <textarea name="note">…</textarea>. But we do know that the username is "admin" but how do we get the password?
On failed login, the code compares the submitted password from the start against those stored hashes and returns how many front characters match. That creates a prefix oracle: we can recover the password by growing a correct prefix one character at a time, then log in and fetch the note.
After a password failure, the code leaks a prefix match count:
Exploit strategy:
Target:
admin.Initialize an empty prefix
"".For position i = 0..:
For each printable character
c:Submit login
(username=admin, password=prefix + c).If we get the dashboard → we’re done (full password matched).
Else read the flash: “you got X characters correct!”
If
X == len(prefix) + 1, keepc(it’s correct for this position) and move to the next position.
Repeat until we log in (or until the prefix stops growing → adjust charset).
ENO{V1b3_C0D1nG_Gr4nDmA_Bu1ld5_InS3cUr3_4PP5!!}
pwgen

Password policies aren't always great. That's why we generate passwords for our users based on a strong master password!
tl;dr: Takes a flag, seeds random with 0x1337 (static), then shuffles the flag's characters randomly. So to solve this....just (breh 🥀):
Get the shuffled flag from server
Create array of indices [0,1,2,3...]
Seed random with same
0x1337Shuffle the indices array (gets same pattern as server used)
Use shuffled indices as a "reverse map" to put characters back in original positions

Since the source is...like 1 file so the source is just right there you know...
Also, see that nthpw? yea, curl that sh
Solve it in reverse
ENO{N3V3r_SHUFFLE_W1TH_STAT1C_S333D_OR_B4D_TH1NGS_WiLL_H4pp3n:-/_0d68ea85d88ba14eb6238776845542cf6fe560936f128404e8c14bd5544636f7}
webby

MFA is awesome! Even if someone gets our login credentials, and they still can't get our secrets!
tl;dr: The app marks a user logged in before doing MFA, then flips that flag back to False after generating an MFA token with bcrypt cost=14 (slow).
During that slow window, the session cookie holds loggedIn=True + username=admin. Hitting /flag with the same cookie inside that window returns the flag. What we need to do is: we trigger the login and, in parallel, spam /flag until one request lands in the race.
To get the source...you do /?source=1 (pretty neat, aye?)
Alrighty, where is the flag? — The value is read from
/tmp/flag.txtintoFLAGand rendered only by the/flaghandler:
(So: be admin and be loggedIn when you hit /flag.)
What lines of code is vulnerable?
In /flag:
The code saves
loggedIn=Truebefore MFA completes.It performs slow work (bcrypt(14)) before resetting
loggedIn=False./flagdoes not check that MFA passed—onlyloggedIn+username.
This produces a time-of-check/time-of-use race window where the session is already “logged in” as admin.
Exploit strategry:
Get a fresh session cookie:
GET /→webpy_session_id.Start login as
admin:adminin the background using that cookie.Server sets
loggedIn=True; username=admin; save()Server spends ~hundreds of ms doing bcrypt(14).
Only after that it flips
loggedIn=False.
Hammer
/flagwith the same cookie during that slow window.One request lands while
loggedIn=True→/flagreturns the flag page.
ENO{R4Ces_Ar3_3ver1Wher3_Y3ah!!}
slasher

Slashing all the slashes...
note: this was a hard challenge to me since I went for a funny ahh route, but it didn't give out much it was a header injection challenge tbh...This probably the hardest web challege of this batch imo. Except the dogv2, I have a sense of what to do but me sleep...💤
tl;dr: The page evaluates whatever you POST into input with eval("$input;"). The challenge tries to “sanitize” it with htmlentities, addslashes, and addcslashes, but because the code is evaluated unquoted, slashes don’t neutralize PHP tokens.
Since we can still call built-in functions, we can exploit this by using only function calls (without quotes or variables) to traverse the filesystem. By chaining opendir() and readdir() to walk through the current directory, then using readfile() to stream file contents, we can discover flag.php and extract the flag.
There's also an alternative solution via header injection, but I pursued the file-traversal route instead. I wasn't able to think logically the specific logic that made this a header injection challenge (although I thought simply call getallheaders() or read $_SERVER['HTTP_*'] and lets you smuggle data via headers and then do readfile(<value-from-headers>) inside the eval somewhat...make senses?
The page included the source, which is quite nice, here is the long version of my writeup.
Now, lets see where is the flag?:
Therefore, reading flag.php or causing the page to print its contents will show the flag. My focus are on these lines
Again, as mentioned that they challenge will tries to sanitizes our input and prevent us from doing our shenanigans (highlighted lines of code above):
That is why we need to craft an eval payload that:
avoids quotes/variables (they’re backslash-escaped),
uses only function calls and operator chaining,
lists files in the working directory and prints each with
readfile()
If you think about this methodically, the filters fail?
The eval sees the raw PHP code ($input supposed to be a strings...give it some quotes man).
Backslashes don’t stop tokens like
opendir,readdir,readfile, parentheses, commas, pipes, etc.An error handler suppresses warnings, so failed attempts don’t reveal much —but we don’t need them. (good job bozo, I test this locally with
php -S localhost:8000)
I was able to do an equivalent to a "ls" command and see what we are dealing with
Assume the web root contains
., .., Dockerfile, docker-compose.yml, flag.php, index.php, style.css
Now how would we print the flag and access the flag.php? — well it is is sitting at...index 5? including the . and ..
I will be using
readfileto achieve this (small note that this payload took me a fat minutes to figured out). Here is a bit of visualization for ya.
ENO{3v4L_0nC3_Ag41n_F0r_Th3_W1n_:-)}Take away:
readdir()without a handle uses the last opendir handle, so you can iterate without variables.readfile()prints as a side effect, so the flag is emitted even if$outputis just an integer.🥀
dogfinder

I like dogs, so I wrote this awesome dogfinder page. Somewhere on the filesystem is a nice treat for you.
This was solved by my teammate "anar" (🗣️🗣️🔥🥀), really want to see the official writeups or the intended solution of this challenge...As our path to solve this challenge at the time was quite guessy (not my favorite thing). Regardless, it was ... rough.
ENO{CuT3_D0GG0S_T0_F1nD_Ev3r1Wh3re_<3}
dogfinderv2

I still like dogs, so I tried to make the previous version more secure. The treat is now in the root directory - that should help, shouldn't it?
I have a feeling it has to do something with listening the request via rce, as I was able to dump some of the permission and file using sqlmap, but i didn't solve it in time :<.
todo: ...lazy
Last updated