前言
注入漏洞,是指攻击者可以通过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("<br> id: "+ rs.getObject("id"));
out.println("<br> 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("<br> id: "+ rs.getObject("id"));
out.println("<br> 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("<br> id: "+rs.getObject("id"));
out.println("<br> 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语句如下所示
<select id="getUsername" resultType="com.zlng.bean.User">
select id,name,age from user where name =
</select>
使用#{Parameter} 方式会使用? 占位进行预编译,因此不存在SQL注入的问题。用户可以尝试构造name 值为zlng or 1=1 进行验证,由于程序未查询到结果出现了空指针异常,因此不存在SQL注入漏洞。
当输入的name值为Yu时,成功查询到结果,Debug的回显如下
使用${Parameter} 构造SQL语句如下
<select id="QueryByName" parameterType="String" resultType="com.demo.bean.User">
select * from user where name = ${name}
</select>
当输入的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();
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 {
}
}
@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");
Query query = session.createQuery("from com.demo.bean.User where name = :name",User.class);
query.setParameter("name", parameter);
String parameter = req.getParameter("name");
List<String> 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注入
SQL注入主要的成因在于未对用户输入进行严格的过滤,并采取不恰当的方式构造SQL语句。在实际的开发中,有些地方难免需要使用拼接构造SQL语句,例如SQL语句中order by后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。
|