Java代码审计|SQL注入


注入漏洞

    • 前言
    • SQL注入
      • JDBC拼接不当造成SQL注入
      • 框架使用不当造成SQL注入
    • 防御不当造成SQL注入

前言 注入漏洞,是指攻击者可以通过HTTP请求将payload注入某种代码中,导致payload被当作代码执行的漏洞。例如SQL注入漏洞,攻击者将SQL注入payload插入SQL语句中,并且被SQL引擎解析成SQL代码,影响原SQL语句的逻辑,形成注入。同样文件包含漏洞、命令执行漏洞、代码执行漏洞的原理也类似,也可以看作代码注入漏洞。
SQL注入 SQL注入是因为程序未能正确对用户的输入进行检查,将用户的输入以拼接的方式带入SQL语句中,导致了SQL注入的产生。黑客通过SQL注入可直接窃取数据库信息,造成信息泄露。
JDBC拼接不当造成SQL注入
JDBC有两种方法执行SQL语句,分别是PrepareStatementStatement。两个方法的区别在于前者会对SQL语句进行预编译,而后者方法在每次执行时都需要编译,会增大系统开销。理论上前者的效率和安全性会比后者好,但是不意味着前者就绝对安全,不会产生SQL注入。
如下代码使用拼接的方式将用户输入的参数id带入SQL语句中,创建Statement对象来进行SQL语句的执行。经过拼接的语句,最终在数据库执行的语句为select * from user where id = 1 or 1=2,改变了程序想要查询id=1的语义,通过回显可以判断出存在SQL注入。
String sql = "select * from user where id ="+req.getParameter("id"); PrintWriter out = resp.getWriter(); out.println("Statement Demo"); out.println("SQL: "+sql); try {Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql); while (rs.next()){out.println("
id: "+ rs.getObject("id")); out.println("
name: "+ rs.getobject("name")); } } catch (SQLException throwables) {throwables.printStackTrace(); }

Java代码审计|SQL注入
文章图片

PrepareStatement方法支持使用"?"对变量位进行占位,在预编译阶段填入相应的值构造出完整的SQL语句,此时可以避免SQL注入的产生。但开发者有时为了方便,会直接采取拼接的方式构造SQL语句,此时进行预编译则无法阻止SQL注入的产生。PrepareStatement虽然进行了预编译,但在以拼接方式构造SQL语句的情况下仍然会产生SQL注入
String sql = "select * from user where id ="+req.getParameter("id"); PrintWriter out = resp.getWriter(); out.println("prepareStatement Demo"); out.println("SQL: "+sql); try {PreparedStatement pst = conn.prepareStatement(sql); ResultSet rs = pst.executeQuery(); while (rs.next()){out.println("
id: "+ rs.getObject("id")); out.println("
name: "+ rs.getObject("name")); } } catch (SQLException throwables) {throwables.printStackTrace(); }

Java代码审计|SQL注入
文章图片

正确使用PrepareStatement可以有效避免SQL注入的产生,使用?作为占位符时,填入对应字段的值会进行严格的类型检查。将前面的拼接构造SQL语句改为使用占位符构造SQL语句的代码片段,即可有效避免SQL注入的产生
PrintWriter out = resp.getWriter(); out.println("prepareStatement Demo3"); String sql = "select * from user where id = ?"; out.println(sql); try {PreparedStatement pstt = conn.prepareStatement(sql); // 参数已经强制要求是整型 pstt.setInt(1, Integer.parseInt(req.getParameter("id"))); ResultSet rs = pstt.executeQuery(); while (rs.next()){out.println("
id: "+rs.getObject("id")); out.println("
name: "+rs.getObject("name")); } } catch (SQLException throwables) {throwables.printStackTrace(); }

Java代码审计|SQL注入
文章图片

框架使用不当造成SQL注入
在实际的代码开发工作中,JDBC方式是将SQL语句写在代码块中,不利于后续维护。如今的Java项目或多或少会使用对JDBC进行更抽象封装的持久化框架,如MyBatisHibernate。通常,框架底层已经实现了对SQL注入的防御,但在研发人员未能恰当使用框架的情况下,仍然可能存在SQL注入的风险。
1. MyBatis框架
MyBatis框架的思想是将SQL语句编入配置文件中,避免SQL语句在Java程序中大量出现,方便后续对SQL语句的修改与配置。正确使用MyBatis框架可以有效阻止SQL注入,错误的使用则可能埋下安全隐患。MyBatis中使用parameterType向SQL语句传参,在SQL引用传参可以使用#{Parameter}${Parameter}两种方式。
使用#{Parameter}构造的SQL语句如下所示

使用#{Parameter}方式会使用?占位进行预编译,因此不存在SQL注入的问题。用户可以尝试构造name值为zlng or 1=1进行验证,由于程序未查询到结果出现了空指针异常,因此不存在SQL注入漏洞。
当输入的name值为Yu时,成功查询到结果,Debug的回显如下
Java代码审计|SQL注入
文章图片

Java代码审计|SQL注入
文章图片

使用${Parameter}构造SQL语句如下

当输入的name值为Yu时,成功查询到结果,Debug的回显如下
Java代码审计|SQL注入
文章图片

当我们输入的name值为'Yu' or 1=1 limit 0,1时,成功查询到结果
Java代码审计|SQL注入
文章图片

Java代码审计|SQL注入
文章图片

当语句为'Yu' or 1=1 limit 1,1时,查询的结果为
Java代码审计|SQL注入
文章图片

Java代码审计|SQL注入
文章图片

根据Debug的回显可以看出,name值被拼接进SQL语句中,因此存在SQL注入。从上面的演示可以看出,在底层构造完整SQL语句时,MyBatis的两种传参方式所采取的方式不同。#{Parameter}采用预编译的方式构造SQL,避免了SQL注入的产生。而${Parameter}采用拼接的方式构造SQL,在对用户输入过滤不严格的前提下,此处很有可能存在SQL注入。
2. Hibernate框架
Hibernate框架是Java持久化API规范的一种实现方式。Hibernate将Java类映射到数据表中,从Java数据类型映射到SQL数据类型。Hibernate是目前主流的Java数据库持久化框架,采用Hibernate查询语言注入。
HQL的语法与SQL类似,但有些许不同。受语法的影响,HQL注入在实际漏洞利用上具有一定的限制。Hibernate是对持久化的对象进行操作而不是直接对数据库进行操作,因此HQL查询语句由Hibernate引擎进行解析,这意味着产生的错误信息可能来自数据库,也可能来自Hibernate引擎。
import com.demo.bean.User; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import javax.persistence.Query; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; @WebServlet(name="HibernateDemo",urlPatterns = "/SQLDemo5") public class HibernateDemo extends HttpServlet {private SessionFactory factory; private Transaction tx = null; Session session; @Override public void init() throws ServletException {factory = new Configuration().configure().buildSessionFactory(); session = factory.openSession(); }@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {PrintWriter out = resp.getWriter(); out.println("Hibernate Demo"); try {tx = session.beginTransaction(); String parameter = req.getParameter("name"); List user = session.createQuery("FROM User where name='" + parameter + "'").getResultList(); // from user where name = 'Yu or 1=1' //Query query = session.createQuery("from com.demo.bean.User where name = ?1", User.class); // //query.setParameter(1, parameter); //List user = query.getResultList(); for (Iterator iterator = user.iterator(); iterator.hasNext(); ) {User user1 = (User) iterator.next(); out.println(user1.toString()); } tx.commit(); }catch (HibernateException e) {if (tx!=null) tx.rollback(); e.printStackTrace(); }finally {//session.close(); }}@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp); }@Override public void destroy() {session.close(); } }

通过Debug模式可以清晰地观察到变量parameter被拼接进语句中,将原本的语义改变,查询出结果。
Java代码审计|SQL注入
文章图片

Java代码审计|SQL注入
文章图片

正确使用以下几种SQL参数绑定的方式可以有效避免注入的产生
  • 位置参数
String parameter = req.getParameter("name"); Query query = session.createQuery("from com.demo.bean.User where name = ?1", User.class); query.setParameter(1, parameter);

Java代码审计|SQL注入
文章图片

  • 命名参数
String parameter = req.getParameter("name"); //List user = session.createQuery("FROM User where name='" + parameter + "'").getResultList(); //命名参数 Query query = session.createQuery("from com.demo.bean.User where name = :name",User.class); query.setParameter("name", parameter);

  • 命名参数列表
String parameter = req.getParameter("name"); //命名参数列表 List names = Arrays.asList(parameter); Query query = session.createQuery("from com.demo.bean.User where name in (:names)",User.class); query.setParameter("names", parameter); List user = query.getResultList();

通过Debug可以观察出,以上几种方式都采用了预编译的方式进行构造SQL语句,从而避免注入的产生。Hibernate支持原生的SQL语句执行,与JDBC的SQL注入相同,直接拼接构造SQL语句会导致安全隐患的产生,应采用参数绑定的方式构造SQL语句。
防御不当造成SQL注入 【Java代码审计|SQL注入】SQL注入主要的成因在于未对用户输入进行严格的过滤,并采取不恰当的方式构造SQL语句。在实际的开发中,有些地方难免需要使用拼接构造SQL语句,例如SQL语句中order by后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。

    推荐阅读