参考 https://javasec.org/javase/JDBC/
basic
Java连接数据库一般为:
String CLASS_NAME = "com.mysql.jdbc.Driver";
String URL = "jdbc:mysql://localhost:3306/mysql";
String USERNAME = "root";
String PASSWORD = "root";
Class.forName(CLASS_NAME);// 注册JDBC驱动类
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
Class.forName(CLASS_NAME)实际上是注册了对应的数据库驱动
为什么要个驱动呢?
和设计结构有关,开发只需要调用上层api即可,不用管下层具体是什么数据库,下层驱动负责实现上层的接口。总的来说扩展性强。
这里开发者真的不用关心底层是什么数据库了
SPI机制
大致感受一下:
简单来说就是服务提供商可以提供一个接口名称,然后客户端在调用时可以更方便去加载相应的接口。
且这个加载可以是自动的。
dataSource
datasouce和直接用driver有何区别?
看下面这个就理解了,driver只能连一个,datasource可以资源集中管理,更方便
可以直接用链接中的自动枚举datasource脚本利用 https://javasec.org/javase/JDBC/DataSource.html
注入与防范?
JDBC中有预处理机制可以防御sql注入,如:
// 获取用户传入的用户ID
String id = request.getParameter("id");
// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
// 语句当中,从而导致了SQL注入漏洞。
String sql = "select id, username, email from sys_user where id =? ";
// 创建预编译对象
PreparedStatement pstt = connection.prepareStatement(sql);
// 设置预编译查询的第一个参数值
pstt.setObject(1, id);
// 执行SQL语句并获取返回结果对象
ResultSet rs = pstt.executeQuery();
预处理使用错误会导致sql注入:
// 获取用户传入的用户ID
String id = request.getParameter("id");
// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
// 语句当中,从而导致了SQL注入漏洞。
String sql = "select id, username, email from sys_user where id = "+ id;
// 创建预编译对象
PreparedStatement pstt = connection.prepareStatement(sql);
// 执行SQL语句并获取返回结果对象
ResultSet rs = pstt.executeQuery();
同时预编译有两种:分为服务端和客户端
// 服务端预编译
jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=true
String sql = "select host,user from mysql.user where user = ? ";
PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);
// 客户端预编译
jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=false
String sql = "select host,user from mysql.user where user = ? ";
PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);
这里服务端指的是数据库服务,比如MySQL会提供预编译指令,那么上面的服务端预编译Java代码实际上就是执行了:
prepare stmt from 'select host,user from mysql.user where user = ?';
set @username='root';
execute stmt using @username;
而客户端预编译执行的为:
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
JDBC注入 - 直接拼接
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
Statement stmt = conn.createStatement();
String sql = "select * from users where id = '" + id + "'";
log.info("[vul] 执行SQL语句: " + sql);
ResultSet rs = stmt.executeQuery(sql);
JDBC注入 - 预编译使用错误
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
String sql = "select * from users where id = " + id;
log.info("[vul] 执行SQL语句: " + sql);
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
修复方法:
拼接之前加过滤
- JDBC预编译
- SAPI过滤
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