JAVA中的SQL注入
环境
直接用的 springboot 搭建的测试环境
jdbc 字符串拼接
JDBC 有两种方式执行 SQL 语句,分别为 PreparedStatement 和 Statement。
-
Statement 方法在每次执行时都需要编译 -
PreparedStatement 会对SQL语句进行预编译,后续无需重新编译
Statement 会直接拼接 sql 语句造成 SQL 注入漏洞
Statement
@RestController
@RequestMapping("/test1")
public class test {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/test")
public String jdbc_sql(@RequestParam("id") String id){
String sql = "select * from tb_user where id =" + id;
System.out.println(sql);
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return "result:"+maps;
}
}
这里存在 sql 注入 直接用拼接的方式:http://127.0.0.1:8080/test1/test?id=1%20or%201
PreparedStatement
预编译会将传入的值用单引号包裹起来,利用占位符 ? 及传入的参数,而不是直接 sql 语句拼接到语句 使输入的字符串是 数值 而不是 关键字
@GetMapping("/test1")
public String jdbc_presql(@RequestParam("id") String id){
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/txh", "root", "root");
String sql = "select * from tb_user where id = ?";
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1,id);
System.out.println(st.toString());
String res = "";
ResultSet rs = st.executeQuery();
while (rs.next()){
res += rs.getString("username")+" ";
}
return res;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
这里肯定会想到包裹了单引号 用单引号来进行闭合
id=5%27%20or%20%271%27=%271
控制台打印
这里显示是闭合了对吧 然后这语句复制到 navicat 是可以执行的
但是页面没有任何东西
因为 jdbc 对一些特殊字符进行了转义
但是即使用了预编译还是可能存在 sql 注入的风险
在没有使用占位符而是使用拼接的时候
拼接输入
@GetMapping("/test2")
public String jdbc_presql1(@RequestParam("id") String id){
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/txh", "root", "root");
String sql = "select * from tb_user where id =" + id;
PreparedStatement st = conn.prepareStatement(sql);
// st.setString(1,id);
System.out.println(st);
String res = "";
ResultSet rs = st.executeQuery();
while (rs.next()){
res += rs.getString("username")+" ";
}
return res;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
是可以进行注入的
In
String sql = “delete from user where id in(” + Id + “);
因为一般使用 in 的时候因为不确定 id 有多少个 相当于也是用的拼接的方式 所以易造成 sql 注入
like 语句
也和 in 类似 都是进行拼接
String sql = “select * from user where name like ‘%” + a + “%'”;
Order by
在使用 order by 的时候是无法进行预编译的,因为 order by 后面要跟字段名,预编译之后会包裹上单引号就不会被识别为字段名 使用在用 order by 的时候就只能不用预编译 易存在 sql 注入
框架使用不当造成SQL注
Mybatis
#
的$
的区别
-
#
号会点语句进行预编译 -
${ }
只是进行string 替换,动态解析 SQL 的时候会进行变量替换
${}
pojo
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private String password;
}
Dao
@Mapper
public interface UserDao {
@Select("select * from tb_user where id =${id}")
public List<User> getById(String id);
}
controller
@Autowired
private UserDao userDao;
@GetMapping("/test1")
public List<User> mybatis_sql(@RequestParam String id){
return userDao.getById(id);
}
进行注入
http://127.0.0.1:8080/test2/test1?id=1%20or%201
${} 也是进行语句拼接 加上过滤不严格存在sql注入
#{}
dao
@Select("select * from tb_user where id =#{id}")
public List<User> getById1(String id);
controller
@GetMapping("/test2")
public List<User> mybatis_sql2(@RequestParam String id){
return userDao.getById1(id);
}
可以看到是采用的占位符的方式 无法注入成功
易存在注入的情况
和 jdbc 差不多 在使用 in like orderby 的时候 因为无法使用 #{},在用 ${} 进行拼接的时候易造成 sql 注入
<select id="getById" parameterType="String" resultMap="User">
select * from user where id like '%${id}%'
</select>
这种情况就易出现
可以用这种方式写进行防止
<select id="getById" parameterType="String" resultMap="User">
select * from user where id like concat('%',#{id}, '%')
</select>
In也是如此
<select id="getById" parameterType="String" resultMap="User">
select * from user where id in (${id})
</select>
这种写法是危险的
较好的可以用 foreach
mybatis-plus
关于
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
相关sql注入场景
mybatis-plus 已经对之前一些可能存在 sql 注入的场景进行了预编译处理
比如 like in 等
但是还是依旧存在一些拼接的情况在开发的过程中需要注意
配置环境
pojo
@Data
public class User {
private Integer id;
private String username;
private String password;
}
pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
dao
@Mapper
public interface UserDao1 extends BaseMapper<User> {
}
like模糊查询
@RestController
@RequestMapping("/test3")
public class test3 {
@Autowired
private UserDao1 userDao1;
@GetMapping("/test1")
public List<User> mybatis_plus(@RequestParam String name){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("*").like("username",name);
List<User> user = userDao1.selectList(queryWrapper);
return user;
}
}
可以看到是用占位符
in
@GetMapping("/test2")
public List<User> mybatis_plus1(@RequestParam String name,String name1){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("*").in("username",name,name1);
List<User> user = userDao1.selectList(queryWrapper);
return user;
}
注入场景
以 QueryWrapper 为例,在开发的时候有些场景需要用到拼接的地方,在官方的文档(https://baomidou.com/pages/10c804/#apply)也给出了警示
apply
如果直接使用 apply() 拼接 sql 语句,则存在 sql 注入
@GetMapping("/test3")
public List<User> mybatis_plus2(@RequestParam String name,String password){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",name).apply("password= "+ password);
List<User> user = userDao1.selectList(queryWrapper);
return user;
}
http://127.0.0.1:8080/test3/test3?name=admin&password=%27admin%27%20or%201
造成注入
使用 {} 进行预编译就不存在注入
@GetMapping("/test3")
public List<User> mybatis_plus2(@RequestParam String name,String password){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("username",name).apply("password= "+ password);
queryWrapper.eq("username",name).apply("password={0}",password);
List<User> user = userDao1.selectList(queryWrapper);
return user;
}
last
last(String lastSql)
last(boolean condition, String lastSql)
-
无视优化规则直接拼接到 sql 的最后
注意事项:
只能调用一次,多次调用以最后一次为准,有sql注入的风险,请谨慎使用
-
例:
last("limit 1")
exists
exists(String existsSql)
exists(boolean condition, String existsSql)
-
拼接 EXISTS ( sql语句 ) -
例: exists("select id from table where age = 1")
—>exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
-
拼接 NOT EXISTS ( sql语句 ) -
例: notExists("select id from table where age = 1")
—>not exists (select id from table where age = 1)
这几个都是进行 sql 拼接的 如果输入可控没有用 {} 进行预编译的话 就存在sql注入的风险
参考:https://www.sec-in.com/article/1073
原文始发于微信公众号(海狮安全团队):JAVA中的SQL注入