0x00 起因
看见jmreport报表曝了aviator注入,在成功复现后,写入内存马时发现触发500,报错信息为 java.lang.StackOverflowError和pattern有关,搜了下解决办法对base64后的class数据再进行一层url编码即可,再次写入内存马发现确实写上了没报错。如果是base64后的字符会影响进入触发流程,那同样都是base64后的class数据,复现时弹计算器的base64没有触发,打内存马会触发,这是什么逻辑,当时就觉得很奇怪。下面的重点不在poc的权限绕过和具体漏洞分析,只解决这个疑惑。
0x01 正则
报错和pattern有关,可能进入了无限递归循环,于是看了下代码,重点关注正则,看写入内存马的payload到底是哪触发报错,整个漏洞触发流程的pattern正则有如下几个
- 匹配text的内容
2.从text中匹配
用上面的正则匹配了后发现并不会触发异常,并且弹计算器和内存马的payload两个都会有匹配内容,没什么特别区别。
0x02 分析
直接跟代码,从入口开始一步步看,根据曝出来的poc,先/save 再/show,在show的时候触发
{
"excel_config_id": "c4ca4238a0b923820dcc509a6f758474",
"rows": {
"4": {
"cells": {
"4": {
"text": "=(use org.springframework.cglib.core.*;use org.springframework.util.*;ReflectUtils.defineClass('injectSuperRegHandler',Base64Utils.decodeFromString('yyxxx'),ClassLoader.getSystemClassLoader());)",
"style": 0
}
},
"height": 25
},
"len": 96
},
"styles": [
{
"align": "center"
}
],
"cols": {
}
}
直接跟进show(),show()通过id去读取json数据,最后调用ExpressUtil.a(json)
跟进ExpressUtil.a()方法,用json数据创建a实例
a的构造方法如下,获取了json中的rows、styles数据,接着按顺序调用三个方法this.c、this.a、this.b
先看c方法,循环根据json键值对获取数据
然后通过正则匹配获取text内容
以等号开头则进入如下代码,调用ExpressUtil.a(str,str,str)
ExpressUtil.a(str,str,str)如下,初始化了大量的类,最终调用var4.a(b)
a(b)方法如下进行正则匹配,会把text中的字符串匹配像A123、BB1这样字符串出来,把他们和0作为键值对通过SetEnv()把这个结果存到环境变量中(W13=0,A4=0)
如
=(use org.springframework.cglib.core.*;use org.springframework.util.*;ReflectUtils.defineClass('test',Base64Utils.decodeFromString('test123AsdsaW13sdA4'),ClassLoader.getSystemClassLoader());)
匹配结果为W13、A4
c方法最后把上面ExpressUtil.a(str,str,str)的返回对象b和var2_var7放进this.e的map中,var2和var7分别为rows和cells的键名(只能为数字),this.e根据poc内容键值对为\<4_4=b>。
其中返回对象b为Script(text=xxx,expression=xxx,xxxxxx,row=4, col=4)这样,其中text为数据包中text中的值=(use xxx)
this.c()结束后接着是this.a()方法,获取刚才的this.e的键名,然后调用this.a(map, str),参数为(\<4_4=b>,4_4)
a(map, str)方法如下,获取map中b对象Script(text=,expression=,xxxxxx,row=4, col=4),和获取开始写入的环境变量的键名{W13=0,A4=0}
进入for循环遍历{W13=0,A4=0},取出键名传入this.b(str)
b(str)如下,分别把大写字母和数字分别取出来如[W,13]、[A,4],然后把它们做运算后返回,这里this.b(str)最后返回[12,22]、[3,0]
c(str)具体运算如下把传入的W、A进行一系列计算后返回一个int类型,按照数字顺序例如A返回1,Z返回26,AA返回27
然后是this.a(str, str),根据传入的str作为键名去原始payload查值,根据上面的例子这里为this.a(12,22)
接着根据12_22键名从var1也就是\<4_4=b>获取键值,结果为null,然后var12为空,进入下一次for循环
for循环结束后调用ExpressUtil.a(b);
最终ExpressUtil.a(b)触发表达式注入
按照这样的流程走,直到触发表达式注入,没有进入java.lang.StackOverflowError
0x03 报错原因
经过使用写入内存马的payload发现
问题在for循环中的代码,如果var15不为null时则再次调用this.a(map,str),前面分析过根据payload这里的var1为\<4_4=b>,也就是当var13=4_4时,var15不为null进入else条件调用this.a(map,str),也就是再次调用this.a(\<4_4=b>,4_4),上面最开始进入这个逻辑是调用的this.a(\<4_4=b>,4_4),从而进入死循环。
要让var13=4_4也就是this.b(str)计算结果为[4,4],根据前面的计算逻辑和同时要符合正则[A-Z]{1,2}[0-9]{1,3},E5符合上述条件,也就是只要text中含有符合正则的E5字符串,即可进入死循环(所以用上面的poc,rows和cells=4,内存马的poc中含有E5字符串触发报错)
验证如下,在源poc中class位置用E5替代
触发500堆栈异常
如果rows和cells为0和0时,匹配到A1即可触发异常
避免报错的方式,可以按照网上的使用url编码,也在打内存马时发现触发报错了后修改rows或者cells的值。
或者根据上面计算逻辑,在poc中设置rows的键名大于701或者cells的键名大于998便不会触发java.lang.StackOverflowError