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语句,分别是
PrepareStatement
和Statement
。两个方法的区别在于前者会对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();
}
文章图片
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();
}
文章图片
正确使用
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();
}
文章图片
框架使用不当造成SQL注入
在实际的代码开发工作中,JDBC方式是将SQL语句写在代码块中,不利于后续维护。如今的Java项目或多或少会使用对JDBC进行更抽象封装的持久化框架,如
MyBatis
和Hibernate
。通常,框架底层已经实现了对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的回显如下
文章图片
文章图片
使用
${Parameter}
构造SQL语句如下
当输入的name值为Yu时,成功查询到结果,Debug的回显如下
文章图片
当我们输入的name值为
'Yu' or 1=1 limit 0,1
时,成功查询到结果文章图片
文章图片
当语句为
'Yu' or 1=1 limit 1,1
时,查询的结果为文章图片
文章图片
根据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
被拼接进语句中,将原本的语义改变,查询出结果。文章图片
文章图片
正确使用以下几种SQL参数绑定的方式可以有效避免注入的产生
- 位置参数
String parameter = req.getParameter("name");
Query query = session.createQuery("from com.demo.bean.User where name = ?1", User.class);
query.setParameter(1, parameter);
文章图片
- 命名参数
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后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- CVE-2020-16898|CVE-2020-16898 TCP/IP远程代码执行漏洞
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 不废话,代码实践带你掌握|不废话,代码实践带你掌握 强缓存、协商缓存!
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- 工具|后天就是七夕节,你准备好了吗(送上几个七夕代码,展示你技能的时候到了!)
- Java|Java基础——数组