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("<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进行更抽象封装的持久化框架,如MyBatisHibernate。通常,框架底层已经实现了对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 = #{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();
            // 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<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后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。

  • 10
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平凡的学者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值