用友某产品最新版前台RCE
原创
ptr
ptr
UpRoot
2026年10月6日 18:36
江苏
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
入口断点,发现\*.do结尾的路由会经过nccloud.framework.web.action.entry.EntryController#doPost转发并且不会进行鉴权。
进一步跟进来到nccloud.framework.web.action.entry.Dispatcher#doAction,可以了解到最原始的一个请求/nccloud/platform/total/switch.do其是由/platform/total/switch三个部分组成的,其动作的实现类是nccloud.web.platform.gzip.action.QueryTotalSwitchAction。
通过类定位jar包,我们可以找到action的配置文件,由此,我们对于基本的路由形式心里便有了基本的认知了。
如果是正在进行红队评估,追求效率,不妨以此入口,寻找破局的办法。
我们重点关注可以通过EntryController进行转发的action并且该action的具体实现办法不会做强鉴权(如session、token等),从中寻找我们想要的sink点。
通过大量时间的寻找,我们可以定位到nccloud.web.hr.login.action.HRPubBusinessInternalAction#doAction鉴权较弱可被绕过。
并且该类具备对类初始化和调用类方法的能力。但前提是被初始化的类需要是AbstractInternalDataFactory的子类。
不妨以该点进行展开,发散,寻找哪些类是AbstractInternalDataFactory的子类,并且具备可以用来rce的能力。
由此,我们定位到了nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory。
nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#dataOperate方法内部可调用如下两个方法。
对于nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#downLoad方法。
这里先从字面上理解rtf后缀的文件是什么?bing搜索后了解到是富文本。
那downLoad方法大概想表达的意思是把doc转成rtf这种富文本的形式。我们继续跟进nc.itf.hr.tools.rtf.IGenerateRTFDocument#createRtfFile看看有没有操作空间。
来到nc.impl.hr.tools.rtf.RtfCommonGenerator#createRtfFile,继续跟进nc.impl.hr.tools.rtf.RtfCommonGenerator#buildRtfFile看看是怎么build这个rtf的。
来到nc.impl.hr.tools.rtf.RtfCommonGenerator#buildRtfFile,我们豁然开朗。
标准的Velocity\_SSTI。(具体能不能r,我们后续debug分析就好了,此处是我们看到了破局的点。)
回到nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#downLoad这个入口,我们看看这个数据流从哪可以填进去。
重点关注vo这个JavaBean,它承载了我们想要的主要数据。
bytes = ((IGenerateRTFDocument)ServiceLocator.find(IGenerateRTFDocument.class)).createRtfFile(context, pk_psnjob, \"psndoc\", vo, (String[])null);
vo是通过bai.queryByPk(pk_rpt_def)赋值的,重点分析nc.itf.hi.IRptQueryService#queryByPk做了什么事情。
那这里,我随便输入了个pk\_rpt\_def的值,最后得到的结果是null。由此,我们有了个初步的结论,vo是由查询并赋值得到的。
进而,我们的思路转变为:外部可控的pk\_rpt\_def参数,它是什么?其值如何获得?是否可以伪造?
带着这个疑问,来分析该类中的另一个方法:nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#upLoad,它也引用了pk\_rpt\_def参数。
外部传入该参数后同样是进入nc.itf.hi.IRptQueryService#queryByPk查询,那由此我们可以推断出pk\_rpt\_def参数是表中的字段的值。
这次我们仔细分析一下这个查询逻辑,看看是去哪个表里面查的,pk\_rpt\_def是固定的还是动态生成的。
此处,拿数据源,datasource外部传参而来,由此,我们这里需要知道一个点:需要一个接口可以返回系统的数据源(前台是有这样的接口,本文不会提及)。
随后,参数被送入hr\_rpt\_def表进行查询,取值判断是否存在。
我们去数据库中查看hr\_rpt\_def表中的键值情况。
找到了我们想要的pk\_rpt\_def。(注意,此处出厂配置如下,我验证过别的目标值也是如此。如果不一致,我们借助一个前台sql注入,即可拿到表中的值的,这里也可以透露一点,是存在这样的注入的,本文不再提及。)
由此,我们获得了一批rk\_rpt\_def。
1001Z710000000000JB3
1001Z710000000000R7Q
1001Z710000000000R9D
1001Z710000000000RA5
1001Z710000000000RB5
1001Z710000000000RBP
1001Z710000000000RCA
1001Z710000000000RCW
1001Z71000000000DNII
从前文中,我们已经了解到nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#downLoad是用来消费vo这个javabean的,那我们这里完全可以大胆的去猜,十步之内必有解药,那nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#upLoad是用来生产vo这个javabean的。
大胆猜想,细心求证。我们带着正确的rk\_rpt\_def去走完整个nccloud.web.hrhi.rptdef.factory.RtfDownloadFactory#upLoad逻辑。
方法的开始是从请求中拿到文件并转为流。注意这里的request.readFiles(),其内部实现是接收文件上传表单。
由此,得到一个点:构造的数据包需要是文件上传表单的形式。
随后就是根据传入的rk\_rpt\_def参数去给vo赋值了。
接着,针对上传的文件流进行保存,我们来看看这一块的逻辑是怎么样的。
将文件写入到用友产品的临时目录下,其实这里也可以认为是一个鸡肋的任意文件上传,没法解析。
从磁盘中读取文件并转为字节数组存起来,这里就存到vo这个javabean中了。
前文我们了解到了vo是javabean同数据库相联系起来,在这个方法的结尾,我们来看看vo的值如何。
至此,我们搞清了vo的数据流情况。回到Velocity\_SSTI这个破局点,其具体是怎么消费vo的呢?
跟进nc.itf.hr.tools.rtf.IGenerateRTFDocument#createRtfFile,最后发现通过velocity引擎解析了我们上传的文件。
由此,我们已经可以下结论了,文件上传模板,velocity解析模板形成SSTI。
最后武器化打内存马来完结撒花吧。
这里考验java安全功底的是用友定制JDK17+开启JavaSecurity+unsafe阉割。不过既然有了代码执行的能力,那结果必然还是“有志者、事竟成”。
- END -
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论