这是一个系列
正式学习之前,先问下自己一下几个问题
1.sql注入是什么
2.在代码中具有什么特征
3.如何发现他
4.如何修复
5.如何绕过各种限制
我们正式开始解答上面的那些疑问
1.sql注入
sql是因为代码的不严谨,信任前端传输过来的参数,没有经过校验,或者不严谨,导致的sql语句被拼接,执行了意想不到的sql语句代码,泄露了不应被展示的数据。
更官方的定义是: 攻击者通过浏览器或者其他客户端将恶意SQL语句插入到网站参数中,网站应用程序未经过滤,便将恶意SQL语句带入数据库执行。
2.sql语句的特点
首先,我们需要找到常用的sql语句代码,例如select,update,delete,insert等语句,同时要注意在代码中的一些特征
Statement
createStatement
PreparStatement
like’%${
select
update
insert
同时在语句下造成漏洞的原因也不尽相同
在jdbc中的java.sql.prepared-statement中,如果使用了+进行拼接则可能存在sql注入
// 采用Statement方法拼接SQL语句,导致注入产生
public String vul1(String id) {
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);
…
而看到这个地方我们可以想想,在id后面我们我们用“闭合,然后增加我们需要运行的sql语句1′ and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)– +,updatexml报错注入,在错误日志中返回了规定的数据
而如何修复呢,常见的方法是过滤,就像下面这样
// 采用黑名单过滤危险字符,同时也容易误伤(次方案)
public static boolean checkSql(String content) {
String[] black_list = {“‘”, “;”, “–“, “+”, “,”, “%”, “=”, “>”, “*”, “(“, “)”, “and”, “or”, “exec”, “insert”, “select”, “delete”, “update”, “count”, “drop”, “chr”, “mid”, “master”, “truncate”, “char”, “declare”};
for (String s : black_list) {
if (content.toLowerCase().contains(s)) {
return true;
}
}
return false;
}
但是这种编码容易限制原本应传入的的数据,而且当使用不同编码格式,也有可能被绕过
常用的策略是使用预编译进行处理,但是当使用不善时
// PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。
public String vul2(String id) {
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;
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
}
预编译整条语句,而正确的应该是
// 正确的使用PrepareStatement可以有效避免SQL注入,使用?作为占位符,进行参数化查询
public String safe1(String id) {
String sql = “select * from users where id = ?”;
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, id);
ResultSet rs = st.executeQuery();
}
使用占位符
而在一些特殊情况下 order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名。Pre-pareStatement是使用占位符传入参数的,传递的字符都会有单引号包裹,“ps.setString(1,id)”会自动给值加上引号,这样就会导致order by子句失效。 所以还需要配合代码过滤一起使用,同时如果不对 %_进行转义则可能造成服务器资源占用的情况
对于框架中sql语句,例如spring框架中JdbcTemplate的sql注入代码,同样使用拼接就会造成注入
// JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入
public Map<String, Object> vul3(String id) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
…
JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);
String sql_vul = “select * from users where id = ” + id;
// 安全语句
// String sql_safe = “select * from users where id = ?”;
return jdbctemplate.queryForMap(sql_vul);
}
而我们的修复则可以
// ESAPI 是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序
// 官网:https://owasp.org/www-project-enterprise-security-api/
public String safe3(String id) {
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);
ESAPI 是一个免费、开源的、网页应用程序安全控件库,它提供了一些编码和加密功能来防御一些安全威胁,如 XSS 和 SQL 注入等。
在这个例子中,首先使用 OracleCodec 来创建一个 ESAPI 编码器,然后使用 ESAPI 的 encoder() 方法对 id 参数进行编码,最后将编码后的 id 参数插入 SQL 查询语句中。这样可以确保输入的参数不会被当作 SQL 语句的一部分来执行,从而防止 SQL 注入攻击
而因为Java是强数据类型,所以当输入的参数不是string也可以防护sql注入
// 如果参数类型为boolean,byte,short,int,long,float,double等,sql语句无法拼接字符串,因此不存在注入
public Map<String, Object> safe4(Integer id) {
String sql = “select * from users where id = ” + id;
return jdbctemplate.queryForMap(sql);
}
而在框架myBatis中,他是通过XML描述符或者注解把对象和存储过程或者sql语句联系在一起的,支持自定义sql以及高级映射
类如
/ 由于使用#{}会将对象转成字符串,形成order by “user” desc造成错误,因此很多研发会采用${}来解决,从而造成SQL注入
@GetMapping(“/vul/order”)
public List<User> orderBy(String field, String sort) {
return userMapper.orderBy(field, sort);
}
// xml方式
<select id=”orderBy” resultType=”com.best.hello.entity.User”>
select * from users order by ${field} ${sort}
</select>
// 注解方式
@Select(“select * from users order by ${field} desc”)
List<User> orderBy2(@Param(“field”) String field);
而使用${}这种形式,就会造成拼接sql语句,而在orderby后面是不能使用#{}占位符进行排序,原因同上面orderby后面的参数不能“”一致,这时需要在代码传输过程中过滤,以及使用强类型过滤
在mybatis的like子句中使用#{}会报错 只能使用${},例如:“se-lect*from users where name like’%${user}%’”
@Select(“select * from users where user like ‘%${q}%'”)
List<User> search(String q);
// 安全代码,采用concat
@Select(“select * from users where user like concat(‘%’,#{q},’%’)”)
List<User> search(String q);
在in参数中,使用#{}与${},类似于 “’user1’,’user2’,’user3’,’user4’” ,具体原因是#{}会将多个参数当做一个整体
知道了具体特征,我们依据特征进行全局搜索,并分析数据的流转中,是否有校验,而后如何绕过即可
请登录后查看评论内容