各位师傅可能对于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注入漏洞可以执行各种恶意操作,包括但不限于:
-
绕过身份验证:攻击者可以修改应用程序的登录验证查询,以允许未经授权的访问或提升其权限。
-
盗取数据:攻击者可以通过注入恶意的SQL查询来检索数据库中的敏感信息,如用户凭证、个人信息或其他敏感数据。
-
修改数据:攻击者可以修改数据库中的数据,包括插入、更新或删除记录,从而对应用程序的正常功能造成破坏。
-
拒绝服务:通过执行恶意的SQL查询,攻击者可以使数据库服务器过载,导致拒绝服务(DoS)攻击。
SQL注入的条件
1、参数可控
2、和数据库有交互
SQL注入分类
略
执行SQL语句的几种方式
-
使用JDBC的java.sql.Statement执行SQL语句
-
使用JDBC的java.sql.PreparedStatement执行SQL语句
-
使用Mybatis执行SQL语句
-
使用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语句
-
添加依赖:首先,在项目的 pom.xml 文件中添加 MyBatis 的依赖。
-
配置 MyBatis:设置 MyBatis 配置文件和数据库连接信息。
在 src/main/resources 目录下创建 mybatis-config.xml 文件
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:
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注入
// 会有语句错误
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'
order by 注入利用
常用的方法有:
-
case when 语句
-
if语句
-
报错
关于order by,当注入点在后面时,是不能连接union的
在SQL中是不允许union直接跟在order by后面的,所以我们可以考虑使用盲注或报错注入。
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中的#{}和${}
#是使用预编译, $ 是类似于拼接
$ 效果:
# 效果
order by 注入
上面说过order by 不能使用预编译,那如果在order by 后使用 # 拼接,能不能解决这个问题呢?实验看一下
具体利用见上文 order by 注入利用方式。
like 查询
使用 # , 直接报错
使用 $ 的效果
同上述 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=")">
</foreach>
</select>
总结:
常见注入关键字:
Statement
createStatement
PrepareStatement
like '%${
in (${
order by ${
原文始发于微信公众号(安全随心录):代码审计系统学习:sql注入之预编译下可能存在sql注入的地方
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论