参考 https://javasec.org/javase/JDBC/

basic

Java连接数据库一般为:

  1. String CLASS_NAME = "com.mysql.jdbc.Driver";
  2. String URL = "jdbc:mysql://localhost:3306/mysql";
  3. String USERNAME = "root";
  4. String PASSWORD = "root";
  5. Class.forName(CLASS_NAME);// 注册JDBC驱动类
  6. Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

Class.forName(CLASS_NAME)实际上是注册了对应的数据库驱动
image.png
为什么要个驱动呢?
和设计结构有关,开发只需要调用上层api即可,不用管下层具体是什么数据库,下层驱动负责实现上层的接口。总的来说扩展性强。

这里开发者真的不用关心底层是什么数据库了

SPI机制

大致感受一下:
image.png
简单来说就是服务提供商可以提供一个接口名称,然后客户端在调用时可以更方便去加载相应的接口。
且这个加载可以是自动的。

dataSource

datasouce和直接用driver有何区别?
看下面这个就理解了,driver只能连一个,datasource可以资源集中管理,更方便
image.png
可以直接用链接中的自动枚举datasource脚本利用 https://javasec.org/javase/JDBC/DataSource.html

注入与防范?

JDBC中有预处理机制可以防御sql注入,如:

  1. // 获取用户传入的用户ID
  2. String id = request.getParameter("id");
  3. // 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
  4. // 语句当中,从而导致了SQL注入漏洞。
  5. String sql = "select id, username, email from sys_user where id =? ";
  6. // 创建预编译对象
  7. PreparedStatement pstt = connection.prepareStatement(sql);
  8. // 设置预编译查询的第一个参数值
  9. pstt.setObject(1, id);
  10. // 执行SQL语句并获取返回结果对象
  11. ResultSet rs = pstt.executeQuery();

预处理使用错误会导致sql注入:

  1. // 获取用户传入的用户ID
  2. String id = request.getParameter("id");
  3. // 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
  4. // 语句当中,从而导致了SQL注入漏洞。
  5. String sql = "select id, username, email from sys_user where id = "+ id;
  6. // 创建预编译对象
  7. PreparedStatement pstt = connection.prepareStatement(sql);
  8. // 执行SQL语句并获取返回结果对象
  9. ResultSet rs = pstt.executeQuery();

同时预编译有两种:分为服务端和客户端

  1. // 服务端预编译
  2. jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=true
  3. String sql = "select host,user from mysql.user where user = ? ";
  4. PreparedStatement pstt = connection.prepareStatement(sql);
  5. pstt.setObject(1, user);
  6. // 客户端预编译
  7. jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=false
  8. String sql = "select host,user from mysql.user where user = ? ";
  9. PreparedStatement pstt = connection.prepareStatement(sql);
  10. pstt.setObject(1, user);

这里服务端指的是数据库服务,比如MySQL会提供预编译指令,那么上面的服务端预编译Java代码实际上就是执行了:

  1. prepare stmt from 'select host,user from mysql.user where user = ?';
  2. set @username='root';
  3. execute stmt using @username;

而客户端预编译执行的为:

  1. select host,user from mysql.user where user = 'root\'';

sql注入场景

参考: https://github.com/j3ers3/Hello-Java-Sec/tree/master/src/main/java/com/best/hello/controller/SQLI

jdbc

  1. JDBC注入 - 直接拼接

    1. Class.forName("com.mysql.cj.jdbc.Driver");
    2. Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
    3. Statement stmt = conn.createStatement();
    4. String sql = "select * from users where id = '" + id + "'";
    5. log.info("[vul] 执行SQL语句: " + sql);
    6. ResultSet rs = stmt.executeQuery(sql);
  2. JDBC注入 - 预编译使用错误

    1. Class.forName("com.mysql.cj.jdbc.Driver");
    2. Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
    3. String sql = "select * from users where id = " + id;
    4. log.info("[vul] 执行SQL语句: " + sql);
    5. PreparedStatement st = conn.prepareStatement(sql);
    6. ResultSet rs = st.executeQuery();

    修复方法:

  3. 拼接之前加过滤

  4. JDBC预编译
  5. SAPI过滤
    1. String sql = "select * from users where id = '" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + "'";

    MyBatis

    // TODO:: 以后遇到再仔细看
    https://github.com/j3ers3/Hello-Java-Sec/blob/master/src/main/java/com/best/hello/controller/SQLI/MyBatis.java ```java // unsafe @Select(“select from users where id = ${id}”) @Select(“select from users where user like ‘%${q}%’”)

// @poc http://127.0.0.1:8888/SQLI/MyBatis/vul/order?field=id&sort=desc,1 log.info(“[vul] mybaits: order by “ + field + “ “ + sort); return userMapper.orderBy(field, sort); orderBy(String field, String sort);

@ApiOperation(value = “vul:Mybatis order by 注入(注解方式)”) public List order2(String field) { log.info(“[vul] mybaits: “ + field); return userMapper.orderBy2(field); } @Select(“select * from users order by ${field} desc”) ```