资讯专栏INFORMATION COLUMN

SQL注入

RiverLi / 2799人阅读

摘要:黑客通过注入可直接窃取数据库信息,造成信息泄露。虽然进行了预编译,但在以拼接方式构造语句的情况下仍然会产生注入正确使用可以有效避免注入的产生,使用作为占位符时,填入对应字段的值会进行严格的类型检查。

前言

注入漏洞,是指攻击者可以通过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(); }

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进行更抽象封装的持久化框架,如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后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/121742.html

相关文章

  • SQL注入学习笔记

    摘要:结果反馈分类盲注入盲注入不会展现任何数据库报错内容,它是依据构造真或假的问题对数据库进行提问,注入方式主要有两种基于布尔值与基于时间。 文章目录 前言SQL注入是...

    sihai 评论0 收藏0
  • 注入攻击-SQL注入和代码注入

    摘要:注入攻击将注入攻击和跨站脚本攻击列入网络应用程序十大常见安全风险。这种类别的攻击包括跨站脚本攻击注入攻击头部注入攻击日志注入攻击和全路径暴露。注入攻击目前最常见的注入攻击形式是臭名昭著的注入攻击。 注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险。实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功。虽然这是最明显的组合关系,但是注入攻击...

    lwx12525 评论0 收藏0
  • mybatis-plus源码分析之sql注入

    摘要:下面我会详细地从源码的角度分析下文简写成是如何实现自动注入的原理。文件解析器,解析对应的文件信息,并将文件信息注册到中。节点解析器,用于构建节点信息。注册与绑定类,将的类信息与绑定。 微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车! mybatis-plus是完全基于myba...

    gougoujiang 评论0 收藏0
  • php操作mysql防止sql注入(合集)

    摘要:参考中如何防止注入参数化查询为什么能够防止注入上面提供的资料比较多,下面根据自己的理解整理出来。使用的预处理参数化查询可以有效防止注入。我们在上面预处理参数化查询是在中进行防注入操作的,其实也内置了一个预处理的模拟器,叫做。 由于segmentfault在处理特殊字符时也并非完美,所以下面文章中有些符号被转换了,请到本人博客下载原文txt http://www.yunxi365.cn/...

    kviccn 评论0 收藏0
  • php操作mysql防止sql注入(合集)

    摘要:参考中如何防止注入参数化查询为什么能够防止注入上面提供的资料比较多,下面根据自己的理解整理出来。使用的预处理参数化查询可以有效防止注入。我们在上面预处理参数化查询是在中进行防注入操作的,其实也内置了一个预处理的模拟器,叫做。 由于segmentfault在处理特殊字符时也并非完美,所以下面文章中有些符号被转换了,请到本人博客下载原文txt http://www.yunxi365.cn/...

    AZmake 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<