代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

admin 2024年7月11日22:22:23评论12 views字数 8481阅读28分16秒阅读模式

    各位师傅可能对于sql注入,在打ctf或者打靶场的时候一顿操作猛如虎,但是在实战中,几乎遇不到,为什么?首先,随着开发人员的安全意识提升,现在sql注入确实比较少,还有就是,即使存在sql注入,也很难像靶场那样,直接 1 ' or 1=1 -- 就给一把梭脱裤了。本文就从代码角度,看在预编译下可能存在sql输入的地方。

    视频地址:https://space.bilibili.com/482887446/channel/collectiondetail?sid=3340840&ctype=0

入群私信即可

漏洞描述

    SQL注入漏洞是一种常见的网络安全漏洞,它允许攻击者通过在应用程序的输入字段中插入恶意的SQL代码,从而执行未经授权的数据库操作。这种漏洞通常存在于未正确验证和过滤用户输入的Web应用程序中,特别是那些使用动态生成SQL查询的应用程序。

攻击者利用SQL注入漏洞可以执行各种恶意操作,包括但不限于:

  1. 绕过身份验证:攻击者可以修改应用程序的登录验证查询,以允许未经授权的访问或提升其权限。

  2. 盗取数据:攻击者可以通过注入恶意的SQL查询来检索数据库中的敏感信息,如用户凭证、个人信息或其他敏感数据。

  3. 修改数据:攻击者可以修改数据库中的数据,包括插入、更新或删除记录,从而对应用程序的正常功能造成破坏。

  4. 拒绝服务:通过执行恶意的SQL查询,攻击者可以使数据库服务器过载,导致拒绝服务(DoS)攻击。

SQL注入的条件

1、参数可控

2、和数据库有交互

SQL注入分类

执行SQL语句的几种方式

  1. 使用JDBC的java.sql.Statement执行SQL语句

  2. 使用JDBC的java.sql.PreparedStatement执行SQL语句

  3. 使用Mybatis执行SQL语句

  4. 使用Hibernate执行SQL语句

java.sql.Statement执行SQL语句

// 1.导入驱动jar包// 2.注册mysql8.0.11 的驱动// 3.获取数据库连接// 4.定义sql语句// 5.获取执行sql语句的对象// 6.执行sql语句// 7.处理结果// 8.释放资源
// 1.导入驱动jar包        // 2.注册mysql8.0.11 的驱动        // 3.获取数据库连接        // 4.定义sql语句        // 5.获取执行sql语句的对象        // 6.执行sql语句        // 7.处理结果        // 8.释放资源        Connection conn = null;        Statement pstmt = null;        ResultSet rs = null;        try {            // 获取链接            conn = getConnection();            String id = "1 or 1=1 ";            // 4. 定义 SQL 语句            String sql = "SELECT * FROM user WHERE id = " + id;//            System.out.println(sql);            // 5. 获取执行 SQL 语句的对象            pstmt = conn.createStatement();            System.out.println(sql);            // 6. 执行 SQL 语句            rs = pstmt.executeQuery(sql);            // 7. 处理结果            while (rs.next()) {                System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("userName") + ", Password: " + rs.getString("password"));            }        } catch (ClassNotFoundException e) {            System.err.println("Driver not found.");            e.printStackTrace();        } catch (SQLException e) {            System.err.println("Database connection error.");            e.printStackTrace();        } catch (Exception e) {            throw new RuntimeException(e);        } finally {            // 8. 释放资源            try {                if (rs != null) rs.close();                if (pstmt != null) pstmt.close();                if (conn != null) conn.close();            } catch (SQLException e) {                System.err.println("Error closing resources.");                e.printStackTrace();            }        }

java.sql.PreparedStatement执行SQL语句

    PreparedStatement是Statement的子接口,表示预编译的 SQL 语句的对象,SQL 语句被预编译并存储在PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。

    PreparedStatement对象是如何防止sql注入的,mysql数据库产商,在实现PreparedStatement接口的实现类中的setString(int parameterIndex, String x)函数中做了一些处理,把单引号做了转义(只要用户输入的字符串中有单引号,那mysql数据库产商的setString()这个函数,就会把单引号做转义)

// 1.导入驱动jar包// 2.注册mysql8.0.11 的驱动// 3.获取数据库连接// 4.定义sql语句// 5.获取执行sql语句的对象// 6.执行sql语句// 7.处理结果// 8.释放资源Connection conn = null;try {    String id = "1 ' or 1=1 -- ";    conn = getConnection();    String sql = "select * from user where id = ?";    java.sql.PreparedStatement pstmt = conn.prepareStatement(sql);    pstmt.setString(1, id);    ResultSet rs = pstmt.executeQuery();    // 打印预编译之后的sql语句    System.out.println(pstmt);    while (rs.next()) {        System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("userName") + ", Password: " + rs.getString("password"));    }    pstmt.close();}catch (Exception e){    e.printStackTrace();}

使用Mybatis执行SQL语句

  1. 添加依赖:首先,在项目的 pom.xml 文件中添加 MyBatis 的依赖。

  2. 配置 MyBatis:设置 MyBatis 配置文件和数据库连接信息。

在 src/main/resources 目录下创建 mybatis-config.xml 文件

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <settings>        <setting name="logImpl" value="STDOUT_LOGGING"/>    </settings>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC" />            <dataSource type="POOLED">                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://xxx:3306/codeReview?useSSL=false&serverTimezone=UTC"/>                <property name="username" value="xx"/>                <property name="password" value="xx"/>            </dataSource>        </environment>    </environments><!--    注册映射文件-->    <mappers>        <mapper resource="mapper/UserMapper.xml"/>    </mappers></configuration>

3. 创建 SQL 映射文件:编写 SQL 映射文件,定义 SQL 语句

在 src/main/resources/mapper 目录下创建映射文件,例如 UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper">    <select id="selectUser" resultType="com.example.model.User">        SELECT * FROM user WHERE id = #{id}    </select></mapper>

4. 编写 Mapper 接口:定义 Mapper 接口,用于映射 SQL 语句。

在 src/main/java/com/example/mapper 目录下创建 Mapper 接口,例如 UserMapper.java:

public interface UserMapper {     User selectUser(String id);}

5. 使用 MyBatis:在代码中使用 MyBatis 执行 SQL 语句。

SqlSessionFactory sqlSessionFactory = null;SqlSession sqlSession = null;{    String resource = "mybatis-config.xml";    Reader reader = null;    try {        // 通过资源路径获取Reader对象,用于读取MyBatis的配置文件        reader = org.apache.ibatis.io.Resources.getResourceAsReader(resource);        // 使用SqlSessionFactoryBuilder根据配置文件构建SqlSessionFactory对象        // SqlSessionFactory是MyBatis的核心对象,用于创建SqlSession        sqlSessionFactory = new org.apache.ibatis.session.SqlSessionFactoryBuilder().build(reader);        // 打开一个新的SqlSession,用于执行SQL操作        sqlSession = sqlSessionFactory.openSession();    } catch (IOException e) {        throw new RuntimeException(e);    }}public void testSelectser(){    User user = sqlSession.selectOne("com.llu.mapper.UserMapper.selectUser", "1");    System.out.println(user);}public static void main(String[] args) {    new Test1().testSelectser();}

常见SQL注入

sql语句参数直接动态拼接

String sql = "SELECT * FROM user WHERE id = " + id;

预编译有误

预编译并不能解决所有sql注入问题,有些sql语句是没办法进行预编译的。

like注入

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

// 会有语句错误String sql = "select * from user where userName like '%?%'";// 语句正确,但是存在注入String sql = "select * from user where userName like '%" + userName + "%'";// 正确写法String sql = "select * from user where userName like ?";PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.setString(1, "%" + userName + "%");ResultSet rs = pstmt.executeQuery();

order by 注入

order by 是将结果按照指定列排序,order by 后面需要加字段名或者字段位置,而字段名是不能带引号的,如果使用预编译会被单引号包裹,导致order by 失效。

但是如果order by 根据列数排序,使用setInt() ,是可以正常预编译的。因此,对于order by的使用,需要手动过滤,或者采用白名单机制。

String orderBy = "name";            String userName = "admin";            conn = getConnection();            String sql = "select * from user where id = ? order by ?";//           String sql = "select * from user where userName like '%" + userName + "%'";            java.sql.PreparedStatement pstmt = conn.prepareStatement(sql);            pstmt.setString(1, id);            pstmt.setString(2, orderBy);// select * from user where id = '1' order by 'name'

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

order by 注入利用

常用的方法有:

  1. case when 语句

  2. if语句

  3. 报错

关于order by,当注入点在后面时,是不能连接union的

在SQL中是不允许union直接跟在order by后面的,所以我们可以考虑使用盲注或报错注入。

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

select * from users order by (case when (1=1) then user else id end ); //根据user排序select * from users order by (case when (1=2) then user else id end ); //根据id排序select * from users order by if null(null,user);select * from users order by if null(null,id);select * from users order by rand(1=1);select * from users order by rand(1=2);

未知字段

在实际注入时,可能字段并不是我们提前知道的,那么可能就需要用另外的方式了。

    同样以if()为例,只需要改下第三个参数使其查询报错select 1 union select 2,第二个参数也需要改为1(true)。

select * from users order by if(1=1,1,(select 1 from information_schema.tables)); select * from users order by if(1=2,1,(select 1 from information_schema.tables));select * from users order by (select 1 regexp if(1=1,1,0x00)); //正则表达式select * from users order by (select 1 regexp if(1=2,1,0x00));  //0x00为空导致报错select * from users order by (select if((ascii(substr(current,1,1))<0),1,sleep(2)) from (select user() as current) as tb1);  //利用sleep延时注入

利用报错函数

报错注入的条件是需要服务端代码中将SQL报错语句输出。

主要是利用extractvalue和updatexml

select * from users order by extractvalue(1,(select concat(0x7e,user()))); select * from users order by updatexml(1,(select concat(0x7e,user())),1);select * from users order by (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a); //floor报错注入,Duplicate key

% 和 _ 模糊查询

java预编译中,不会对 %和_ 进行转义,而 % 和 _ 刚好是like查询的通配符,如果没有做相关的过滤,就可能导致恶意的模糊查询,占用服务器性能。

%(百分号)是一个多字符通配符,它可以匹配任意数量的字符,包括零个字符。这意味着如果你在查询中使用%,它会匹配任何位置的任意字符序列

如果没有通配符,则 LIKE 的行为与 = 类似,但通常性能稍低,因为 LIKE 的设计是为了支持更复杂的匹配。

_(下划线)是一个单字符通配符,它只匹配一个单个字符。例如,如果你执行以下查询:

SELECT * FROM user WHERE userName LIKE 'a_d_m_n';  //这将返回所有userName字段以a开始,并且在d和m之间以及m和n之间各有一个任意字符的记录。// 如果查询admin , 就是 a_ _ _ _  (没有空格)String userName = "admin' or 1=1";// 是被转义的//select * from user where userName like 'admin' or 1=1' String userName = "%admin%"; // 未被转义// select * from user where userName like '%admin%' // 查出了好多数据String sql = "select * from user where userName like ?";java.sql.PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.setString(1,  userName );

Mybatis中的#{}和${}

#是使用预编译, $ 是类似于拼接

$ 效果:

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

# 效果

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

order by 注入

上面说过order by 不能使用预编译,那如果在order by 后使用 # 拼接,能不能解决这个问题呢?实验看一下

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

具体利用见上文 order by 注入利用方式。

like 查询

使用 # , 直接报错

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

使用 $ 的效果

代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

同上述 like注入

# 正确写法select * from user where userName like concat('%',#{like},'%')

in 参数

现在// 调用的时候的参数List<User> users = sqlSession.selectList("com.llu.mapper.UserMapper.selectAllUserTestIn", "'admin','jack'");//使用 # 拼接,最终执行的语句 , 把'admin','jack' 看成一个整体,不符合业务要求select * from user where userName in (''admin','jack'')// 使用 $ 拼接,最终执行的语句是期望的语句select * from user where userName in ('admin','jack')<select id="selectAllUserTestIn" parameterType="list" resultType="com.llu.bean.User">    select * from user where userName in (${inName})</select>// 正确写法<select id="selectAllUserTestIn" resultType="com.llu.bean.User">    select * from user where id in    <foreach collection="list" item="id" open="(" separator="," close=")">        #{id}    </foreach></select>

总结:

常见注入关键字:

Statement

createStatement

PrepareStatement

like '%${

in (${

order by ${

原文始发于微信公众号(安全随心录):代码审计系统学习:sql注入之预编译下可能存在sql注入的地方

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月11日22:22:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   代码审计系统学习:sql注入之预编译下可能存在sql注入的地方https://cn-sec.com/archives/2940507.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息