Solved very little, but no excuses for this one :)
🎵 I was listening to Alesso’s PROGRESSO VOL 2 for this CTF!
Details | Links |
---|---|
CTFtime.org Event Page | https://ctftime.org/event/1285/ |
Web
strpos and substr
Can you bypass this WAF
Link - strpos and substr: web.zh3r0.cf:2222
Author - hades
Visiting the page shows us the source code:
|
|
The user
GET query value is taken and checked to be a string of less than 25 characters. If the value fails the two if
conditions at line 10 and 13, the value will not be turned into an MD5 hash and will be passed directly to eval
in 17, thus allowing for arbitrary code execution.
A normal payload for user
would usually be like '.system('id').'
which results in
|
|
But because the string concatenation operator .
is blacklisted by the first preg_match
check in line 10, we can actually use commas instead to supply arguments to echo
since it is not a regular function but a language construct. This is described in the manual page for echo, and was suggested by my teammate zeyu2001.
Manual page example:
|
|
So our current payload will be this instead: ',system('id'),'
.
Let’s next break down the second check in line 13. I’ve formatted it nicely and used variables to replace nested strpos
and substr
calls. I did dynamic testing on a local PHP server to verify my understanding of the checks.
|
|
The if
statement is overall composed of two conditions on each side of a &&
operator. This means that we just need to fail either condition for the whole if
statement to be false
so that our payload won’t turn into a MD5 hash.
|
|
The first condition from line 9 checks for the existence of (
and )
after index 4 and index 6 of the string respectively, meaning that the PHP command word used for code execution like system
ought to be less than 4 characters. But because there seemed to be no such useful command available, I assumed that this check was impossible to bypass.
So, we can move on to focus on the second condition.
|
|
The first preg_match
dictates that the 2 characters before any (
occurring after index 4 cannot be alphanumeric or an underscore. Fortunately, from the echo manual page I also read that expressions can be evaluated in parentheses.
Manual page example:
|
|
This means we can modify our payload to be ',('system')('id'),'
and the 2 characters before ('id')
will not be alphanumeric anymore.
The second preg_match
takes up to 12 characters from any (
occuring after index 4, looks for a )
in those 12 characters, and checks if the character before )
is alphanumeric, an underscore, or a single quote.
In our case, we have a single quote '
before )
in ('id')
so I added a space before )
. The space is represented by +
when URL-encoded.
Final payload for code execution: ',('system')('id'+),'
Request:
|
|
Response:
Hello uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)<br>
Listing files with ls
showed that there was a flag file in /
named FLlA4agGgg999gg
.
Request:
|
|
Response:
Hello FLlA4agGgg999gg
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
var<br>
However, we can’t use cat
to read the file contents because c
is blacklisted. The only suitable command that did not breach the payload length limit of 24 was od, which returns an octal dump of the contents of files. I found out about this from https://unix.stackexchange.com/questions/86321/how-can-i-display-the-contents-of-a-text-file-on-the-command-line.
|
|
Hello 0000000 066146 063541 062547 062547 070056 070150 005015 066146
0000020 063541 074170 027170 074164 006564 063012 060554 063147
0000040 065541 032545 005015 066146 063541 060546 062553 006464
0000060 063012 060554 063147 065541 031545 005015 066146 063541
0000100 060546 062553 006462 063012 060554 063147 065541 030545
0000120 005015 064172 071063 075460 032127 066522 070125 032137
0000140 043137 067165 030537 031463 031463 031463 031463 033463
0000160 006575 063012 060554 063147 065541 033145
0000174
0000174<br>
With guidance from this stackoverflow answer explaining how to interpret od’s output, I wrote a script to convert the octal dump back to a string. Note that byte order is swapped at line 7. I also ignored some bytes that was messing up the final flag output using line 6, but I don’t know the reason behind this.
|
|
|
|
Flag: zh3r0{W4RmUp_4_Fun_13333333337}
bxxs
We’ve made some new epic updates to our website. Could you send us some feedback on it ?
⬇️ link - bxxs - web.zh3r0.cf:3333
Author - ZyperX
The website has a link to /feedback
, where you can send things to the admin.
While simply submitting links does not achieve any effect, my teammate zeyu2001 discovered that you can submit arbitrary HTML that will be rendered on the admin’s side. This is proven with a simple payload like:
|
|
which redirects the admin to our Burp Collaborator server and results in a successful pingback.
My teammate lim_yj found a path /flag
that says it’s only for admins, so we probably need to steal the admin’s cookies for access to this page.
However, attempting to steal the cookie via the script with document.cookie
failed, which we later realized was because the cookie had the HttpOnly
attribute. By trying to extract other information instead, my teammate zeyu2001 found out that the admin’s window.location.href
value was http://0.0.0.0:8080/Secret_admin_cookie_panel
.
Feedback form payload:
|
|
Admin’s request to Burp Collaborator:
|
|
Visiting /Secret_admin_cookie_panel
sets us with the admin cookie.
Request:
|
|
Response:
|
|
Visiting /flag
now gives us the flag.
Request:
|
|
Response:
|
|
Flag: zh3r0{{Ea5y_bx55_ri8}}
Thank you for reading!