致远OA A8-V5 任意文件读取漏洞分析
漏洞环境
致远A8 V7.0
漏洞利用
读取./../../base/conf/datasourceCtp.properties
路径下的数据库配置文件
1 | POST /seeyon/officeservlet HTTP/1.1 |
读取出数据库密码/1.0/VWZ0dTIzNC8=
,经过加密,再github上下载解密脚本,解密就可以拿到明文密码。
https://github.com/Rvn0xsy/PassDecode-jar
漏洞分析
exp中的请求路径为/seeyon/officeservlet
在web.xml中搜索 officeservlet
找到接口映射到对应的类,跟入 com.seeyon.ctp.common.office.OfficeServlet
文件
1 | public class OfficeServlet extends HttpServlet { |
在这个继承了HttpServlet
类中,主要功能都实现在doGet
中。
代码当中有很多看不懂的地方很正常,我们只需要重点关注漏洞利用点,和恶意数据到达利用点的路径就可以了。
从前端拿到参数的关键 在33行
1 | handWriteManager.readVariant(request, msgObj); |
这条语句大致作用就是从给handWriteManager类中一些属性进行赋值,跟进这个方法中
调用了msgObj.ReadPackage(request)
去解析request中的数据,看看是怎么解析的,继续跟进
在这个方法中,先是判断http请求体的长度,再将http请求体的内容读取到this.FStream
属性中,然后判断this.FError
是否为空串,再初始化这个类时,会将this.FError
赋值为空串。
所以这里一定会调用this.StreamToMsg()
方法,跟进该方法中。
1 | private boolean StreamToMsg() { |
上面这段代码中就像是在http请求体中圈地一样,先是指定了http请求体前64为来存储this.FVersion
、this.FMsgText的长度
、this.FError的长度
、this.FMsgFile的长度
。后面则是根据指定的长度在http请求体64位之后切割并赋值给对应的属性,文件读取所获取的路径只需要通过这里this.FMsgText
里拿,我们只需要赋值给this.FMsgText
。其余则都属性的长度都赋值为0,这样可跳过对对应属性进行赋值。
完成而后又回到readVariant
方法中
在后续的赋值操作中,通过传递对应字符串调用msgObj.GetMsgByName
方法从this.FMsgText
属性中获取值。这里再获取值的时候也是做了一些操作的,我们跟入msgObj.GetMsgByName
方法中。
1 | public String GetMsgByName(String var1) { |
在这段代码中,其实就是在this.FMsgText
获取值,在this.FMsgText
这个属性中通过关键字=
的方式去获取关键字=xxx
中的xxx
,并且对xxx
进行了DecodeBase64
解密,这个解码并不是常见的base64解码方式,而且变种过的base64编码,也就是说当我们传递参数值的时候,还需要对xxx
进行EncodeBase64
加密。
这篇文章中详细讲了致远 OA 变种 BASE64 算法的加解密方法
文章中给出了一个加解密的脚本
1 | var a = "gx74KW1roM9qwzPFVOBLSlYaeyncdNbI=JfUCQRHtj2+Z05vshXi3GAEuT/m8Dpk6"; |
这个脚本a2b是解密,得到是正常的base64编码值,b2a是加密,得到的是加密后的值。c
保存的是需要被加密或者解密的值,d则是最后转换的结果。
所以在后续构造文件读取路径的时候,需要通过该加密脚本对路径进行加密。
经过前面的分析,我们已经知道了request请求是如何被解析了以及在获取值的时候做了解密的操作操作,接下来回到OfficeServlet
类中
这里获取OPTION
的值,而后会根据得到的值调用对应的方法。
关键点 在56行
这里调用了handWriteManager.taoHong
方法,跟入
也是获取了TEMPLATE
、COMMAND
、affairMemberId
、affairMemberName
的值,随后传入了officePath
调用了msgObj.MsgFileLoad(officePath)
,继续跟入
1 | public boolean MsgFileLoad(String var1) { |
这个方法大致功能传入一个文件路径,然后文件的值读取到this.FMsgFile
属性中。
而这也是照成文件读取关键的一个点,根据这里往后推,文件的路径可通过TEMPLATE
变量拿到,而TEMPLATE
则可以通过关键字TEMPLATE
在msgObj.GetMsgByName
方法中拿到,而GetMsgByName
这个方法本身是在this.FMsgText
这个属性中获取值,而this.FMsgText
则是在http请求体中截取的值,而http请求体我们可控。到目前为止,我们可以通过http请求体控制文件路径来读取对应文件,但是还差一步,怎么回显文件内容。
在 OfficeServlet.class 类
在101行调用了handWriteManager.sendPackage(response, msgObj)
,我们跟入
调用了msgObj.SendPackage(response)
再次跟入
这里会调用this.MsgVariant()
将结果写入到http响应中。我们跟入 this.MsgVariant()
这里返回了this.FStream
的值,作为返回到前端的内容。
跟入this.MsgToStream
中
通过FMsgText
、FError
、FFileSize
创建了字节数组输出流,然后写入了FMsgText
、FError
以及最关键也就是文件读取内容赋值的变量FMsgFile
,最后都赋值给了FStream
,然后在MsgVariant
方法中被返回到前端,以上就是整个任意文件读取漏洞的原理分析。