Java代码审计(一)-SQL注入

SQL Injection - JDBC

SQLI(SQL Injection), SQL注入是因为程序未能正确对用户的输入进行检查,将用户的输入以拼接的方式带入SQL语句,导致了SQL注入的产生。攻击者可通过SQL注入直接获取数据库信息,造成信息泄漏。JDBC有两个方法执行SQL语句,分别是PrepareStatement和Statement。


一、PrepareStatement和Statement的区别

# 当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。
# 当使用PreparedStatement对象时,无论多少次地使用同一个SQL命令,都只会解析和编译一次。

二、简单漏洞例子

1、漏洞代码 - 语句拼接(Statement),没有对语句进行过滤直接拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 采用原始的Statement拼接语句,导致漏洞产生

public String jdbcVul(String id) {
StringBuilder result = new StringBuilder();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);

Statement stmt = conn.createStatement();
// 拼接语句产生SQL注入
String sql = "select * from users where id = '" + id + "'";
ResultSet rs = stmt.executeQuery(sql);

while (rs.next()) {
String res_name = rs.getString("user");
String res_pass = rs.getString("pass");
String info = String.format("查询结果 %s: %s", res_name, res_pass);
result.append(info);
}

安全代码 - 过滤方法

1
2
3
4
5
6
7
8
9
10
11
12
// 采用黑名单过滤危险字符,同时也容易误伤(次方案)

public static boolean checkSql(String content) {
String black = "'|;|--|+|,|%|=|*|(|)|like|xor|and|or|exec|insert|select|delete|update|count|drop|chr|mid|master|truncate|char|declare|sleep|abs|rand|union";
String[] black_list = black.split("|");
for (int i=0 ; i < black_list.length ; i++ ){
if (content.contains(black_list[i])){
return true;
}
}
return false;
}


2、漏洞代码 - 语句拼接(PrepareStatement)
1
2
3
4
5
6
7
// PrepareStatement会对SQL语句进行预编译,但有时开发者为了便利,直接采取拼接的方式构造SQL,此时进行预编译也无用。

Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
String sql = "select * from users where id = " + id;
PreparedStatement st = conn.prepareStatement(sql);
System.out.println("[*] 执行SQL语句:" + st);
ResultSet rs = st.executeQuery();

安全代码 - 预编译

1
2
3
4
5
6
7
8
// 正确的使用PrepareStatement可以有效避免SQL注入,使用?作为占位符,进行参数化查询

String sql = "select * from users where id = ?";
PreparedStatement st = conn.prepareStatement(sql);

// 对sql语句进行赋值,其中第一个参数是参数的位置,即第几个参数,第二个参数是传给该位置的值
st.setString(1, id);
ResultSet rs = st.executeQuery();

3、安全代码 - ESAPI安全框架

1
2
3
4
5
6
7
8
// ESAPI (OWASP企业安全应用程序接口)是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序

Codec<Character> oracleCodec = new OracleCodec();

Statement stmt = conn.createStatement();
String sql = "select * from users where id = '" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + "'";

ResultSet rs = stmt.executeQuery(sql);

三、漏洞类型


POST型注入
1
String sql = "select * from users where username = '"+username+"' and password = '"+password+"' ";

Like型注入
1
2
String name = req.getParameter("name");
String sql = "select * from users where name like '%'+name+'%'";

Header注入
1
2
String referer = req.getHeader("referer");
String sql = "update user set referer ='"+referer+"'";

四、小结

代码审计时,若发现存在使用JDBC连接数据库可进一步跟踪其代码,在没有使用预编译的情况下且未定义过滤方法,则可能存在SQL注入。


SQL Injection - MyBatis框架

Mybatis 是一个基于 Java 的持久层框架,它内部封装了 jdbc,开发者只需要关注 sql 语句本身,不需要花费时间和精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。MyBatis 框架底层已经实现了对 SQL 注入的防御,但存在使用不当的情况下,仍然存在 SQL 注入的风险。


一、Mybatis特性

Mybatis 获取值有两种方法,${}#{},一般情况下 Mybatis 使用 #{} 获取值,因为能避免 SQL 注入漏洞的产生,若直接使用 ${} 获取值则不能有效的避免注入。

#{} 解析的是占位符问号,可以防止SQL注入,使用了预编译。
${} 直接获取值

二、漏洞类型

1、like注入

以下语句在运行时会报错

1
Select * from news where title like '%#{title}%'

该语句能正常运行,但由于使用了${}获取值,产生了SLQ语句拼接,即使使用了预编译,但若没有对语句进行过滤也会产生SQL注入漏洞

1
select * from user where name like '%${name}%'

正确语句

1
select * from user where name like #{name}

2、in后的参数注入

以下语句在运行时会报错

1
Select * from news where id in (#{id})

该语句则能正常运行,但由于使用了${}获取值,产生了SLQ语句拼接,即使使用了预编译,但没有对语句进行过滤则也会产生SQL注入漏洞

1
Select * from news where id in (${id})	

正确语句,使用forrach

1
2
3
<foreach collection="ids" item="item" open="("separatosr="," close=")">
#{ids}
</foreach>

3、order by 注入

以下语句在运行时会报错

1
Select * from news where title ='新闻' order by #{time} asc

该语句则能正常运行,但由于使用了${}获取值,产生了SLQ语句拼接,即使使用了预编译,但没有对语句进行过滤则也会产生SQL注入漏洞

1
Select * from news where title ='新闻' order by ${time} asc

正确语句

1
Select * from news where title ='新闻' order by #{time} asc

三、靶场学习

1、环境搭建
1
2
3
4
5
[+] IDEA     2021.1
[+] Mysql 5.7.26
[+] Tomcat 7
[+] JDK 1.8_3_01
[+] CMS inxedu_2.0.6

2、IDEA搭建网站

● IDEA配置Maven,下载Maven,版本选择尽量不要最新

1
https://archive.apache.org/dist/maven/maven-3/

image-20220915170827278

配置Maven环境变量,cmd打开:sysdm.cpl

image-20220915171439247

image-20220915171322760

测试是否成功安装

1
mvn -v

image-20220915171322760

配置阿里镜像和jdk,在Maven解压的地方创建一个新文件夹responsitory,打开Maven文件夹下的conf中的settings.xml的<settings>标签内添加以下内容

配置jdk1.8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>

配置阿里镜像源

1
2
3
4
5
6
7
8
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

image-20220915154833152

将配置的Maven适配到IDEA,File -》 Settings -》 Build,Execution,Deployment -》 Maven

image-20220915172209445

● IDEA配置JDK,File -》 Project Structure -》 Project -》 Project SDK

image-20220916103927914

● IDEA 添加 pom.xml 文件为 Maven 文件,由于部署环境不一样,我们需先修改 pom.xml 文件部分内容,首先将<systemPath>中的 basedir 修改为 pom.basedir ,共四处

image-20220916221526099

image-20220916221711148

添加 plugin 版本号

image-20220916221936799

image-20220916222014077

● 导入数据至 MYSQL ,利用 phpstudy 导入程序数据库,注意由于数据库文件中有效时间填写为 0000-00-00 00:00:00,但 timestamp 有效时间在:1970-01-01 00:00:002037-12-31 23:59:59,所以需要将所有的 0000-00-00 00:00:00 修改为有效时间内,否则导入文件会报错。

image-20220916102933090

image-20220916103017494

image-20220917103535006

注意数据库名必须与 src/resources/project.properties 相同,否则网站数据会缺失,按照以下操作避免出现错误

image-20220916230200246

导入数据

image-20220916230305522

image-20220916230321871

● 配置inxedu项目信息src/resources/project.properties

image-20220916233026648

● 项目运行前,配置相关 Maven 信息,File-》Project Settings -》Artifacts -》Web Application:Archive -》Empty

image-20220916223854118

image-20220916224009296

● 配置 Run/Debug Configurations ,配置 Tomcat

image-20220916223455798

image-20220916224207000

image-20220916224309538

image-20220916224349615

image-20220916224407884

配置Maven

image-20220916224841069

image-20220916225058940

image-20220916231424395

注意,若在过程中出现报错,修改了相关配置,记得 clean && install 清理一下缓存,否则 Run 会出现错误,网站无法起来。

image-20220916233109842

image-20220916232156934


● 编译运行

image-20220916224555430

编译成功

image-20220916224631034


● Run 启动网站,点击右上角运行符号运行,虽然有报错,但不影响运行

image-20220916233227501

image-20220916233245750


3、审计开始
| 目录结构            |  作用                                                  |
| ------------------ | -----------------------------------------------------  |
| src/main/java      | java的代码目录                                          |
| src/main/resources | 资源目录,存放一些配置文件,如properties、spring-mvc.xml等 |
| src/main/webapp    | 传统项目的WebContent目录                                 |
| target             | 编译后的文件                                             |

审计代码为 scr 目录下的代码,先查看其 web.xml ,分析其使用了哪些框架。 web.xml 提供了设置初始化参数的功能,开发者会将一些配置信息写到里面去,通读 web.xml 可以了解系统使用的框架等基本信息,有时候开发者还会把账号信息等情况写在里面。

通过分析,该网站使用了 SSM 框架,即 Spring+Spring Mvc+Mybatis

image-20220916144233146

查看其目录结构

image-20220916144417442


4、审计SQL注入

文件清楚看到系统的结构,点开dao文件下的任意文件,看看Mybatis是使用了注解开发还是配置文件开发。

1
2
注解开发:即可不使用任何的xml配置文件来开发java web,直接在代码中体现,不需要单独编写xml文件
配置文件开发:编写xml配置文件来映射相应的代码

文件只定义了一些方法,并没有详细的Mybatis的注解,所以该 CMS 使用了 XML 配置方法

image-20220916153458532

寻找 XML 配置文件,XML 配置的映射文件会和 dao 的接口在同层目录下 resource/mybatis/index/article

image-20220916153744387

根据上面的知识点可知,若使用了 ${} 则可能存在 SQL 注入,直接在 XML 配置文件寻找 $ 符号,发现deleteArticleByIds使用了 ${},符合上面知识点 in 参数后注入。

image-20220916155038180

dao文件夹找寻XML配置文件对应映射的 deleteArticleByIds 接口

image-20220916155406997

Ctrl + 鼠标左键选中 deleteArticleByIds 查看代码中哪些类调用了该方法,初步发现代码没有进行过滤处理

image-20220916155943404

跟进代码的 Controller(控制器),选择 deleteArticleByIds ,Ctrl+Alt+H快捷键去查询调用层次,去看Controller的位置

image-20220916160453470

查看 Controller 文件,寻找到目录路径为:/admin/article

image-20220916160956840

查看具体调用位置,位置为:/delete

image-20220916161203467

所以漏洞具体位置为: http://192.168.114.131:82/admin/article/delete , 访问后发现为后台注入,靶场密码为:admin/111111

image-20220917105051989

再次访问 http://192.168.114.131:82/admin/article/delete 网站页面自动跳转至下面以下链接
http://192.168.114.131:82/admin/article/showlist? 点击删除抓取数据包

image-20220916163457592

image-20220916163649650

通过sqlmap成功跑出数据,复现成功

image-20220916164206987

image-20220916164227132

坑点

1、数据库名称必须为 demo_inxedu_v2_0_open ,否则会报错
2、IDEA 搭建网站中会出现各种报错无法进行下一步操作,当找到解决方案后,记得清理缓存(clean && install)再 Run,否则会出现网站无法启动的情况
3、若 phpstudy创建 demo_inxedu_v2_0_open 数据库提示数据库已存在时,进入root数据库查看是否已经存在这个数据库,然后进行删除,再次导入即可

参考文章

https://github.com/j3ers3/Hello-Java-Sec
https://theoyu.top/2022/04/27/Java-Prepstatementared.html
https://www.cnblogs.com/nice0e3/p/13647511.html#0x01-jdbc-%E6%B3%A8%E5%85%A5%E5%88%86%E6%9E%90
https://mp.weixin.qq.com/s?__biz=MjM5OTk2MTMxOQ==&mid=2727827368&idx=1&sn=765d0835f0069b5145523c31e8229850&mpshare=1&scene=1&srcid=0926a6QC3pGbQ3Pznszb4n2q