编译器在编译sql语句时,会依次进行词法分析、语法分析、语义分析等操作,
预编译技术会让数据库跳过编译阶段,也就无法就进行词法分析,关键字不会被拆开,注入语句也就不会被识别为SQL的关键字,从而防止恶意注入语句改变原有SQL语句本身逻辑。
在使用JDBC进行数据库操作时,有以下几种情况会产生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();//创建Statement对象用来执行sql语句
String sql = "select * from users where id = '" + id + "'"; // 拼接语句产生SQL注入
ResultSet rs = stmt.executeQuery(sql);//返回SQL语句执行结果
...
}
使用拼接方法进行SQL语句查询,预防SQL代码的方式可以采用黑名单过滤等方法
// 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;// 拼接语句产生SQL注入
PreparedStatement st = conn.prepareStatement(sql);//创建预编译对象接受SQL语句
ResultSet rs = st.executeQuery();//返回SQL语句执行结果
}
代码中虽然采用了预编译的方式进行SQL执行,但是依然在构造SQL语句时进行了拼接参数,这样无任何意义,依然可以进行拼接SQL注入
正确使用预编译即可防止SQL注入
// 正确的使用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();
}
Statement和PreparedStatement是Java JDBC API中两种常用的执行SQL语句的方式
Statement:
Statement对象用于执行静态SQL语句,即在编译时已经确定了SQL语句的结构。
Statement执行SQL语句时,直接将完整的SQL语句发送给数据库执行。
Statement对象适用于执行不带参数的简单SQL查询或更新操作。
由于Statement对象没有预编译阶段,每次执行SQL语句时都需要将SQL语句编译一次,这可能导致一定的性能开销。
由于没有参数化输入的机制,使用Statement对象时需要谨慎处理输入数据,以防止SQL注入攻击。
PreparedStatement:
PreparedStatement对象用于执行动态SQL语句,即在执行时才确定SQL语句的具体参数值。
PreparedStatement对象在创建时需要提供一个SQL模板,其中的参数使用占位符(例如,“?”)表示。
在执行PreparedStatement时,首先会对SQL语句进行编译和优化,然后只需提供参数值,而不需要重新编译SQL语句。
PreparedStatement适用于频繁执行带有参数的SQL语句,如参数化查询。
由于PreparedStatement具有预编译和参数化输入的特性,可以提高性能和安全性,并且能够防止SQL注入攻击。
#{name}和${name}是MyBatis中两种常用的参数传递方式,具有不同的行为和特点
#{name}(参数占位符):
是一种安全的参数传递方式,会自动进行参数值的转义和处理,防止SQL注入攻击
使用#{name}时,MyBatis会将参数值作为预编译的参数,将其安全地插入到SQL语句中。
-----------------------------------------------------------------------------
${name}(文本替换):
${name} 是一种简单的文本替换方式,它会将 ${name} 直接替换为参数值,不进行参数值的转义或处理。
使用${name}时,参数值会被直接拼接到SQL语句中,存在SQL注入的风险。
在使用MyBatis进行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);
// 模糊搜索时,直接使用'%#{q}%' 会报错,部分研发图方便直接改成'%${q}%'从而造成注入
@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);
关于MyBatis并不是所有语句都可以采用#{name}的方式,其实就是如果需要用数据库表的字段来替换的话就不能用参数绑定
更多MyBatis防止SQL注入参考
在进行代码审计时,由于MyBatisSQL错误语法的明显特征,可使用全局搜索特征关键字%${、${来查看是否存在注入,从而进一步分析。
在Java中的XXE注入与PHP中的XXE注入原理没有任何区别,只是在Java中所实现XML数据解析所用的方法不同,以下是在Java中常用于处理XML数据的相关接口、类、工具等
XMLReader
SAXReader
DocumentBuilder
XMLStreamReader
SAXBuilder
SAXParser
SAXSource
TransformerFactory
SAXTransformerFactory
SchemaFactory
Unmarshaller
XPathExpression
相应的防护措施为禁用外部实体或者使用黑名单过滤,所使用处理XML数据的相关接口等不同,禁用外部实体的语句也不同。
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
php常见的模板:twig,smarty,blade
python常见的模板有:Jinja2,tornado
java常见的引擎:FreeMarker, velocity
具体参考SSTI(模板注入)漏洞(入门篇)
说白了就是RCE命令执行
详情参考JAVA表达式注入漏洞
以上内容仅作学习记录,如有错误或瑕疵,欢迎批评指正,感谢阅读。