readme | Easy
The flag was in
.ht_flag.txtfile.
This challenge was basically the upgraded version of the easy challenge from Karachi. The challenge was exactly similar except for one minor thing. The source code of the challenge is given as:
php
While analyzing the code and comparing it with the challenge from Karachi, i came across the difference in both files which was the regex in the filter:
Whilst, in Karachi challenge in regex, we had the multiline flag like this: /^.(php|flag)./m. This doesn’t mean much except for the fact that we can use a newline character \n to bypass this regex. 
The actual solution for the Karachi challenge was that we can do a Catastrophic Backtracking Attack to provide a large string to PHP which would bypass the regex as well. I tried that for a while with no success. Then analyzing the code, the following part seems to be vulnerable:
php
In the second condition, we are removing the last element of $safeParts array which means that if the element in array if .. whatever the last character is will be removed. Combining this with the regex bypass earlier of newline. We can send an input such as:
This bypassed the filter and we can read the index.php file. The final script to read the flag:
python
SMG | Medium
This is related to HTTP Descync
The challenge contained a haproxy behind which a flask app is running. The web app is basically vulnerable to SSTI. The vulnerable function on the backend is:
python
The input from user is being directly passed through render_template_string function. This looks quite straight forward. However, haproxy doesn’t let us access URL:
frontend http bind *:80 default_backend web acl restricted_page path_sub -i admin http-request deny if restricted_page acl restricted_page path_sub % http-request deny if restricted_page
Morever, with second ACL condition, its also difficult to provide URL-encoded strings. Reading the code, we are using a very specific version of gunicorn in Dockerfile:
The version gunicorn<=20.0.4 is very specific here to be exact. Looking online, i came across an HTTP Desync in this very specific version. Reading the blogs, i found a request specific for haproxy with gunicorn:
The request worked. However, everytime you change the payload the Content-Length here needs to be very correctly specified. If this is wrong, this would result in error. The first content length header
of 68 starts from the letter xxx.. and ends where the next request starts. While the second header of length 35 starts at the beginning of body which is at GET at the moment
and it ends at the \r\n\r\n. 
The left up part is SSTI. Reading the filters, we can’t use {{ and }}. However, payloads like {%%} can still be used. The {% with %} tag can be used to get RCE:
html
Initially i called import_string from __globals__ but that doesn’t work for some reason. So, lets go the old way of finding the Popen function first. Get the classes:
python
This would result in a large output. Clean the output, store the list properly and the index of the Popen function. Once, you get the index, payload becomes simple:
python
With the final request would become:
wtwaf | Hard
Code Analysis & Race Condition. I wasn’t able to solve this challenge within due time. And kudos to its author and team Sekai for all the help in solving this later on.
Reading the code, we had to exploit 3 things:
- Bypass the req.originalUrl.toLowerCase().startsWith("/admin")statement.
- Bypass the WAF: (item) => item && JSON.stringify(item).includes("flag")
- Race conditon in /admin/debugcall.
Initially, we need to bypass the middleware:
javascript
This would require us to do a bit of code analysis of express.js. The req.originalUrl is created as:
javascript
The req.url is the url after GET in http request. And the requests are resolved through pathname. The first condition can be bypassed by providing a full url after GET:
At the reading part we have:
javascript
The file parameter can be dictionary as extended parameters are not set to false. The readFileSync and similar functions such as readFile can accept a dictionary with a specified set of attributes. The object to read a file is:
javascript
Furthermore, the value of pathname can be `urlencoded as well: 
When the above url is requested, the file be read. However, an error will trigger at following line:
javascript
Because now our parameter is an object and it doesn’t have a function named includes. This is where the race conditions comes in. Since, the readFile function is being successfully called, a file descriptor must have opened as well for this.
This is a very short window. If we can race the descriptor when the file is opened, we can read the flag. However, we can’t get just one descriptor as the time in which it opens is way too short. To cope this, we can generate infinity descriptors and try to race one.
This worked actually and the final poc would look like:
python