Java代码审计入门-sql注入漏洞剖析
0x00 写在前面
对于审计小白来说,在各种漏洞形式里,更吸引我的还是sql
注入和RCE
,特别是后者,也往往需要去做白盒审计才能发现漏洞;今天我就先跟着审一个sql
吧!
案例是panda
师傅的一个审计项目和ofcms
v1.1.2源码。
0x01 漏洞原理
SQL
注⼊,懂得都懂,通过将 SQL
命令插⼊应⽤程序的sql
语句即 http
请求中,并在服务器端被接收后⽤于参与数据库操作,最终达到欺骗服务器执⾏恶意的 SQL
命令的效果,Java
的 SQL
注⼊和 PHP
中的 SQL
注⼊,其实差别不⼤,理论上来讲,应⽤程序中只要是与数据库存在数据交互,⽆论是增删改查,只要传⼊的数据完全受⽤户控制,且应⽤程序对⽤户传⼊的数据没有进⾏妥当的处理,那么这些地⽅都是可能存在 SQL
注⼊的。
例如在panda
师傅的项目案例的UserInfoDaoImpl.java
中,存在如下部分代码:
public UserInfo UserInfoFoundDao(String id){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
UserInfo userinfo = null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sec_sql","root","admin888");
String sql = "select * from userinfo where id = " + id;
ps = conn.prepareStatement(sql);
//传值
//ps.setInt(1,id);
rs = ps.executeQuery();
while(rs.next()){
userinfo = new UserInfo();
userinfo.setId(rs.getString("id"));
userinfo.setName(rs.getString("name"));
userinfo.setAge(rs.getInt("age"));
userinfo.setContent(rs.getString("content"));
userinfo.setAddress(rs.getString("address"));
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
.......
}
一眼丁真,这种传统的jdbc
代码,在sql
语句中,存在着拼接的String
类型变量id
,没有做任何的过滤以及预编译处理;也就可以直接随便注了。
而对于没有用任何中间件的原生的java
代码来说,使用自带的预编译就可以杜绝绝大多数的sql
注入,如下:
String sql = "select * from userinfo where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,id);
预编译在此处确实解决了sql
注入问题;
不过如下图当使用order by
关键字时,order by
后面的语句,是不能进行预编译的,只能通过拼接来进行操作了,所以就得手动过滤。
String sql = "select * from userinfo where id = ?"+"order by ''" + age + "' asc'" ;
ps = conn.prepareStatement(sql);
ps.setInt(1,id);
这些都只是基于原生的servlet
来解析sql
注入,在实际生产环境中,中间件框架的使用占了绝大部分,但实际上原理还是类似的,只是表面形式不同,如Mybatis
中的like
、in
和order by
语句、Hibernate
中的createQuery()
函数等,如果使用不当,仍然可能造成sql
注入。
0x02 案例刨析-(CVE-2019-9615)
环境:tomcat8
+mysql8
+java8
+maven3.6
ofcms是国内一小型团队开发的一款基于java
研发的内容管理系统。
在gitee
上下载v1.1.2
版本的ofcms
后导入IDEA
,运行项目。项目部署这里就只简略截几个图了:
首先导入项目并配置好tomcat
后,把pom.xml
里的mysql
版本改成本机对应的,再刷新加载maven
进行下载jar
包。
在ofcms-admin/src/main/resources/dev/conf/
文件夹的db.properties
下更改自己的mysql
账号密码;
然后在数据库里创建一个ofcms
,将sql
文件导入即可。
启动项目
图片有点bug,不过起来了。
进入后台:admin/123456
根据漏洞描述,漏洞点为ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/system/SystemGenerateController.java
的create
函数
public void create() {
try {
String sql = getPara("sql");
Db.update(sql);
rendSuccessJson();
} catch (Exception e) {
e.printStackTrace();
rendFailedJson(ErrorCode.get("9999"), e.getMessage());
}
}
函数一进来,通过getPara
函数获取到sql
参数的值(getPara
函数是Controller
类里的函数,根据上图一眼丁真,就是调用的ServletRequest.getParameter()
,sql
参数也没做其他过滤。
然后紧接着就是update()
,一层一层往上找找看,最后定位到了DbPro.class
的int update(Config config, Connection conn, String sql, Object... paras)
先是获取数据库连接,然后创建一个PreparedStatement
对象,来预编译sql
,我们正常使用PreparedStatement
,应该如下,会用上占位符去
PreparedStatement pst = con.prepareStatement("update users set name = ? where id = ?");
pst.setString(1, "ikun");
pst.setInt(2, 38);
作者在此处虽然创建了PreparedStatement
对象来进行预编译,但在SystemGenerateController.create()
里并没有用上占位符,这就等于没有对传进来的sql
语句进行预编译处理,白用了,这也就是漏洞的根本原因,不复杂。
根据代码里的提示,找到了后台对应的功能点:系统设置->代码生成->增加
正常语句执行,可以看到控制台显示完整的执行的update
则可以直接进行报错注入了。
update of_cms_bbs set title = updatexml(0,concat(0x7e,(user())),0) where bbs_id = 4
主要来讲,一般审计项目可以先用工具来扫,扫完看重要函数在哪,然后一个个慢慢去跟,对于这里的ofcms
就是找到代码生成功能点的update
函数没有去用到占位符,也没有去做其他过滤,从而导致直接执行恶意的函数。
0x03 总结
本⽂简单讨论了Java
中的 SQL
注⼊漏洞,包括其原理、简单的 java
代码示例以及 CVE
实例,全篇下来也没啥重要姿势;希望对像我样初⼊java
代码审计的朋友有所帮助。
0 条评论