IIS与ASP.NET与漏洞

Published: 六 22 十月 2022

In Vuln.

前言

IIS与ASP.NET在国内相对小众,但在国外还是挺流行的,IIS本身不开源因此分析资料不多,而.NET对标Java它们间有很多相似之处,比如都足够复杂,因此本文简单记载一些在分析这类应用需要用到的知识点。

先说几个概念:

1.IIS是Windows的Web容器,支持其他功能但是这里只关注WEB,就类比成Tomcat吧

2..NET一个软件平台,一套软件运行框架,类比成Java/JVM吧(CLR<->JVM)

3.C#/VB等是特定的语言,但可以编译为.NET使用的MSIL,就像其他解释型语言也可以编译为JVM使用的字节码

4.ASP.NET.NET中的一个WEB框架,用于开发WEB服务的,把它类比成Servlet机制吧

5.ASP.NET Core是 ASP.NET 4.x 的重新设计的一个跨平台的web框架,后者是windows上的,这里假装它们一样

有了这些概念再继续看后文...

IIS分析

整体架构

IIS已经到v10了,之前的不同版本间差异都还挺大,这里以v7为目标,它的架构如下[2]:

img

1.协议监听器:就是listener,对于HTTP/HTTPS协议使用的是HTTP.SYS,看名字就知道它是内核模块,放内核里当然是性能好咯,如果要实现其他协议的可用WCF但应该遇不到

2.WWW服务(W3SVC):它作为HTTP.SYS这个监听器的适配器,处理与WEB相关的功能,如配置HTTP.SYS,在请求放入请求队列通知WAS等

3.Windows进程激活服务(WAS):它管理应用程序池配置与辅助进程,如上图它读取ApplicationHost.config配置告知W3SVC再由后者应用到HTTP.SYS,它监测工作进程是否已启动,是否需要再新建等

4.工作进程(w3wp.exe):它是处理具体http请求的

注:由于IIS不仅可作为WEB容器,因此v7开始把W3SVC分成了两部分,W3SVC/WAS是同一个dll但功能分离了,由svchost.exe容器加载执行

经典模式与集成模式

IIS作为企业级的服务本身是提供了很多功能的,在7之前的版本动态内容会由ISAPI模块及相关的扩展处理,这种架构很蓝瘦啊,比如IIS和.NET都会走鉴权等有大量重复的步骤,开发不便效率低容易出问题,7之后IIS和.NET融合了就没这些事了,即经典模式与集成模式:

img

模块与处理器

Module

IIS是事件驱动的,从v7开始IIS将器功能分配到一个个模块上,模块在对应事件发生时对请求进行处理(就是钩子),用户可根据需要只启动所需的模块,这样提升性能的同时减少了攻击面,用户也可以自己实现模块,IIS支持两类模块:

1.本地模块(Native Module):就是C/C++等实现的模块(SDK是C++的),由于Native模块能实现的操作太多了因此需要管理员权限才能安装,且需要放到指定位置

2.托管模块(Managed Module):就是C#/VB等实现 运行在为.NET上的模块,基本遇到的都是这种啦

使用本地模块一般是性能考量,比较少见,详情可见例子,而托管模块也很简单,它要实现一个派生自System.Web.IHttpModule类,在Init方法里添加请求事件的handler,各事件生效位置见下文生命周期处[4]。

Handler

看名字也很好懂对吧,还是对照成Servlet吧,用于生成具体响应的[0]。

生命周期

其实不管生命周期这里主要是要了解请求的处理流程,这样才能知道哪些函数会在哪些阶段被执行,如哪些逻辑会在鉴权前执行这就是重点关注的点。

应用生命周期

经典模式和集成模式是有些区别的,在经典模式中,请求到达IIS后,普通请求由IIS直接处理,而一些动态页面的请求会由ISAPI及其扩展处理,这里主要关注ASP.NET扩展,在此后两个模式差不多。ASP.NET收到第一个请求时,ApplicationManager会创建一个应用程序域,而后者会创建HostingEnvironment实例,接下来ASP.NET创建并出示话核心对象(HttpConext及其内的HttpRequest,HttpReponse等),创建HttpApplication实例(此时还会创建相关模块),再调用应用实例的Init方法,接下来就是执行如下的一系列事件或行为[3]:

这里注意,Application Domain和Hosting Environment是第一个请求到达时创建一次,后面的HttpContext和HttpApplication是每请求都创建的(为了效率可能重用),用户可创建Global.asax继承HttpApplication来自定义,若存在该文件则实例化它,函数遵从Application_[eventname]命名约定时会被自动绑定到对应的事件,还容易遇到Application_StartApplication_End两个方法,没有Start/End事件可知它俩不是事件handler,它们只会在应用程序生命周期被调用一次!

页面与控件生命周期

控件在页面里它俩在编译后看着差不多,还是可以从事件来看,顺序如下[5] (细节见文档):

这些事件可以手动绑定也可以通过命名约定Page_[eventname]方式绑定,其中init/load都是递归的调用,前者自下而上,后者自上而下而下,如init是先调用所有控件的init再调用page的init。

注:命名约定的方式绑定事件需要<%@ Page AutoEventWireup=true %>才会生效,这在C#中默认为true。另外在审计时别忘了手动绑定的情形

配置文件

有多种配置:

1.machine.config:它是最顶级的配置,每个.net版本一个,相关应用都会继承它的内容

2.app.config: 应用配置

3.web.config: 位于网站目录下,作用于当前及子目录,修改后它会自动加载

4.ApplicationHost.conf: IIS的最顶级配置,可配置站点,应用程序池,应用等

参考

[0] The Two Interceptors: HttpModule and HttpHandlers -- Shivprasad koirala (2009)

[1] IIS 官方文档

[2] Let's Dance in the Cache:Destabilizing Hash Table on Microsoft IIS -- Orange Tsai (2022)

[3] ASP.NET Application Life Cycle Overview for IIS 5.0 and 6.0/ASP.NET Application Life Cycle Overview for IIS 7.0

[4] Develop a Native C\C++ Module for IIS 7.0/Developing a Module Using .NET

[5] ASP.NET Page Life Cycle Overview

ASP.NET

它提供多种开发方式,如下:

又一个 ASP.NET

WebPages

就是经典的开发方式,类似经典ASP/PHP,C#等代码嵌入HTML之中(InlineCode),这时可有两类文件:

1.aspx:它由HTML标签和<% [code] %>里的C#/VB代码组成

2.cshtml/vbhtml:它由HTML标签和Razor标记(以@开始)组成

类似JSP,ASPX被编译后当前还没工具可还原,只能当作CS看

WebForms

用过VB都明白拖动控件来写GUI程序,它是一个前后端都运行在本地的客户端程序,WebForm类似,但GUI运行在浏览器而后端运行在服务器上,它的核心就是控件control,控件是含runat="server"属性的标签,如此修饰后相关代码会在服务端运行,框架负责处理大量逻辑具体可见相关文档。有三种控件:

  • HTML 服务器控件:在已存在的html标签里添加runat="server"实现的控件,这样html标签程序可见可操作它的值
  • Web 服务器控件:新的控件,以<asp:[controlname] ...runat="server"形式出现的控件
  • Validation 服务器控件: 用于输入验证

有些控件找不到使用位置可能是使用loadControl动态加载的,要触发控件的事件可用__EVENTTARGET=&__EVENTARGUMENT=来指定控件ID(全限定名)和事件参数,也可以通过直接为控件赋参来触发。

在WebForms中还有个重要的概念isPostBack,它用于表示是否是对页面进行提交操作,第一次访问页面时会获取数据,之后用户触发一系列事件(如表单验证,查询数据)会postback回server处理,这里面还有个重要的词叫viewstate用于跟踪页面状态,可以类比成Cookie,在未加密时,可使用viewstatedecoder/HttpDebugger或burpsuite的插件viewstate-editor查看它,可在web.config或单个的page里设置EnableViewStateMac(从4.5.2及使用补丁的所有版本强制为true,该选项失效)和viewStateEncryptionMode来启用消息校验和加密,算法与密钥在web/machine.configmachinekey里设置。

⚠️:若存在session会从session中获取viewstate

WebForms通常采用Code Behind形式,使用aspx作为前端视图,相应的aspx.cs文件实现后端逻辑,.aspx继承.aspx.cs,在程序集中前者位于ASP命名空间,由它处理请求。Page是漏洞挖掘的入口,在没有源码时(采用预编译),原始文件依然会保留占位,由它可知有哪些页面,也可以看Preservation File中的[page].aspx.[folder-hash].compiled来获取所有的Page。

⚠️:记得.aspx.aspx.cs两个文件(或其对应的反编译结果)都要看,有些代码会把处理逻辑写.aspx里。

⚠️:控件的参数会由框架自动绑定,不要忘了(审起来贼98麻烦)。

ASP.NET MVC

这是ASP.NET提供的MVC框架,可以对标SpringMVC等框架,默认还是Controllers/Views/Models来存放三种文件,重点如下:

1.每个控制器类必须以Controller结尾,它派生自System.Web.Mvc.Controller,里面的Action必须是public修饰的实例方法,可用属性修饰Action改变其行为[1]

2.路由在RouteConfig类的RegisterRoutes里配置,一眼顶真懂,匹配细节可见WebAPI的,原理一致

3.它使用Filter过滤请求,可类比为Interceptor,emmm似乎还是过滤器合适点,它支持如下过滤器,要实现相关功能时选择实现对应类型:

img

它们作用位置如下[2]:

img

注册过滤器时,有多种方法:

1.注册为全局过滤器,即在Application中使用GlobalFilters.Filters.Add方式添加(默认调用FilterConfig.RegisterGlobalFilters)

2.通过属性的方式,如过滤器实现属性则直接将它放在期待应用的位置,否则需借助特殊的属性来实现。

注:.NET里的属性attribute可类比Java里的注解Annotation

对啦,它用的是ExtensionlessUrlHandler 这个Handler!

WebApi

它极易与WebMVC混淆的,和上面三种不同用于编写普通网页的方法不同,它用于编写基于HTTP的API[1],开发时它和WebMVC类似,这里只列出不同点:

Rua WebAPI WebMVC
System.Web.Http System.Web.MVC
实现抽象类 ApiController Controller
路由 MapHttpRoute MapRoute
动作 必须是HTTP方法或被其注释 公共实例方法
返回 数据 渲染后的视图
过滤器 位于HTTP下 位于MVC下

⚠️:一定要区分这API与MVC它们太像了,审计时路由过滤器不同别搞混了...

其他

还有WebService等等,很简单不多说,不要漏了就行...

参考

[0] ASP.NET 官方文档

[1] ASP.NET WebMVC Tutorial /WebApi Tutorial

[2] Filters in ASP.NET Core

[3] One (more) ASP.NET

漏洞挖掘

分析时可先批量反编译,再用Rider分析,反编译可dnspy(我用的6.0.3,git上6.1.8似乎有问题):

dnSpy.Console.exe -o C:\out\path -r C:\some\path

之后理清其架构,使用的如上哪种模式,如webpages从ASP命名空间分析,而MVC从Application开始分析。

注:把文件从IIS目录拖出来分析时一定要小心,确保不遗漏(如有些虚拟目录不在同一个路径下)!

调试

若要调试IIS运行的asp.net程序:

1.可先用process explorer定位具体的w3wp.exe进程号,也可以执行C:\Windows\System32\inetsrv\appcmd.exe list wp获取

2.根据架构选择32/64位的dnspy attach到刚获取的pid

3.在Debug->Windows->Modules窗口中找到要调试的模块,下断点(不要直接在自己找的模块上下断,它们可能不一致)

注意:1.这里的大多数操作都需管理员权限,必须以管理员权限运行每一步! 2.若找不到模块可能是未加载客先发一次请求强制加载相关模块

在分析时基本都是预编译完成的,程序集位于bin目录,其他目录可能存在一些占位文件,如ashx,aspx,主要还是看bin下的compiled定位其程序集文件,

特性安全问题

反序列化

注:在分析下面的利用链时害得是Windows+VS,Mac+Rider很多库装不起来...

可通过如下正则查找所有反序列化位置:

# 通过反序列化点
(deserialize|readobject|toobject|((parse|load)(Async)?\W)|readxml)
# 反序列化库
(xaml(reader|service)|dataset|jsonconvert|(Soap|ObjectState|Los|Binary)Formatter|fastjson|((Net)?DataContract(Json)?|javascript|xml)Serializer)
序列化类 典型漏洞代码 说明
XamlReader [XamlReader|XamlService].[Parse|Load][Async]("用户可控") -
XmlSerializer new XmlSerializer(Type.GetType("用户可控")).Deserialize("用户可控") -
DataSet new DataSet().readXML("用户可控")且配置了允许类 若开启了audit模式/被添加的列类型/在配置文件中添加的类型里存在可利用的类则存在漏洞,详见DataSet 和 DataTable 安全指南
JavaScriptSerializer new JavaScriptSerializer(new SimpleTypeResolver()).Deserialize[Object]("用户可控") 当然如果用户自己实现了解析器而且可解析危险类则也有漏洞!
Newtonsoft.Json(Json.NET) JsonConvert.DeserializeObject("用户可控",new JsonSerializerSettings(){TypeNameHandling=!None}) -
[Binary|Soap|ObjectState|Los]Formatter new [Soap|ObjectState|Los]Formatter().Deserialize(“用户可控”)new BinaryFormatter().[Unsafe]Deserialize[MethodResponse]("用户可控") -
NetDataContractSerializer new NetDataContractSerializer().[ReadObject|Deserialize]("用户可控") -
DataContractSerializer new DataContractSerializer(Type.GetType("用户可控")).ReadObject("用户可控") -
FastJson new JSON.toObject("用户可控")

XamlReader

XAML是一种基于XML的声明性标记语言,用于简化.NET应用UI创建(作为前端实现前后端分离),可稍微类比HTML但是它还像程序语言可实例化对象,因此若能控制它的内容则及其危险。

XmlSerializer

XmlSerializer是.net内建的序列化功能,默认可以序列化实现了ICollection|IEnumerable的类里的集合/public类的public属性/XmlElement|XmlNode|DataSet对象,使用时需要先指定要操作的类,之后它会自动生成对应的程序集(类似Java的CGLib)来执行序列化和反序列化,反序列化时该代码会实例化相关对象然后使用普通的方式为其赋值,因此若某对象实现中赋值会触发一些行为则可能可以实现一些非预期的行为(类比Java中的Fastjson调setter/getter),如ObjectDataProvider,它的一些属性在每次赋值时会检查是否已经满足条件,满足则调用实例的方法[3]:

var objDat = new ObjectDataProvider();
objDat.ObjectInstance = new System.Diagnostics.Process();  // 检查有实例了,还没方法和参数
objDat.MethodParameters.Add("calc");  // 检查有参数还没方法,未满足
objDat.MethodName = "Start";  // 有了方法,参数也够了,则调用

实际利用时,由于ObjectDataProvider里存在Object类型的属性无法恢复出特定派生类,此时可通过封装来告知它派生类的信息,如使用ExpandedWrapper封装,另外要找一个可以被序列化且存在危险方法的类,Process对象存在危险方法但无法被XmlSerializer反序列化,通常是将其转换为其他反序列化组件的漏洞,如XamlReader/LosFormattter,详见ysoserial.net

DataSet/DataTable

DataSet/DataTable用于为关系型数据集提供一个托管视图,它提供序列化功能(底层调用的是XmlSerializer),默认情况只能反序列化基础类型[4]及已被添加列类型。

注:默认配置下readXML不存在XXE,除非手动修改xmlReader的设置令其支持DTD。

JavaScriptSerializer

JavaScriptSerializer是微软实现的Json序列化库,除了基本类型与数组字典子类,它也支持序列化自定义具体类(公共属性与域),可用[ScriptIgnore]注解忽略某个域,还有两个重要的抽象类,JavaScriptTypeResolver子类用于处理__type来实现类全限定名与Type的转换,而JavaScriptConverter用于自定(反)序列化某具体类的过程,这里主要关注前者,若自己实现了JavaScriptTypeResolver并允许解析危险类,并在构造JavaScriptSerializer时指定该解析器那么即可反序列化任意类而造成漏洞,微软自己提供了SimpleTypeResolver能解析任意类(直接调用了Type.GetType(__type))因此若遇到使用它则有问题。

Newtonsoft.Json(Json.NET)

Json.Net是一个非官方的高性能Json框架,它里面有两个类用于序列化操作,JsonSerializerJsonConvert,后者是前者的封装,除了基本类型列表字典等容器,它还支持多种自定义类型的序列化,这里特别关注实现了ISerializable的类吧,这样除了用ObjectDataProvider还可以利用WindowsIdentity

开发时可用[DataContract|JsonObject(OptOut|OptIn)]作用于类来决定域默认序列化与否,再在域上加[JsonProperty|JsonIgnore|DataMember]来特别指定(特别是默认只会处理public域,用它可处理非public域),可以使用[DefaultValue]来为域赋默认值,还有[JsonConverter]可指定特定类的转换器...除了属性它还有很多设置,其中尤其关注TypeNameHandling.[None|...],默认是None表示不包括类型名称此时无法使用$type反序列化任意类,而修改默认值为其他值则可以。

BinaryFormatter/SoapFormatter/ObjectStateFormatter/LosFormatter

这四个看着挺像,其中前三个都是实现了IFormatter接口,而LosFormatter只是ObjectStateFormatter的简单封装,[Serializable]表示可序列化,而

ViewState问题

在旧版禁用MAC和加密或新版启用了不安全反序列化/使用并泄漏了密钥时,可直接利用反序列化[2],也可通过伪造viewstate实现其他攻击。

SQLServer

因为IIS+.NET+SQLServer是常见组合,因此把它也放这里,记下它的最佳安全实践[1]:

1.使用参数化查询,如SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11)或在存储过程的静态语句中用@var

2.有时存储过程必须使用动态SQL,此时必须做好转义与过滤,可将存储过程导出,审计EXEC(UTE)?\s*\(SP_EXECUTESQL,后者更安全,除了转义还需要注意长度限制,防止溢出导致逃逸

3.LIKE语句需要转义[%_字符

注: 加密的存储过程可使用sqldecryptor解密。

危险函数

命令执行:Process.Start()

参考

[0] .NET 安全规则

[1] Microsofte SQL Inject

[2] Exploiting Deserialisation in ASP.NET via ViewState / .Net 反序列化之 ViewState 利用

[3] .NET 高级代码审计(第一课)XmlSerializer 反序列漏洞 / .Net 反序列化原理学习(下)

[4] SharePoint和Pwn:针对SharePoint Server滥用DataSet的远程代码执行