应用程序和数据交互的地方:
Authentication(认证页面)
Search Fields (搜索页面)
Post Fields (Post请求)
Get Fields (Get请求)
HTTP Header(HTTP头部)
Cookie
and exists(select count(*) from msysobjects)
mysysobjects
表为access特有,一般返回权限不足
and exists(select count(*) from sysobjects)
sysobjects
表为SqlServer特有,一般返回正常
and (select count(*) from information_schema.TABLES)>0
information_schema
为MySQL 5.0 及以上版本特有
Mysql支持/*
如果/**/
返回错误,说明不是MySQL数据库
Sql Server
– 返回正常,说明是MSSQL数据库或者Oracle
;-- 返回正常,说明是MSSQL;错误,基本就是Access数据库
SQL注入在线靶场: http://redtiger.labs.overthewire.org/
DVWA: https://dvwa.co.uk/
sqli-labs源码: https://github.com/Audi-1/sqli-labs
SSH组合是Struts+Spring+Hibernate
基于MVC模式的开发,其中Struts2 对应前台的控制层,
Spring负责实体bean的业务逻辑处理,
Hibernate则是负责数据库的交接以及使用Dao接口来完成操作。
SSM组合是Spring-MVC+Spring+MyBatis
标准的MVC模式,使用Spring MVC负责请求的转发和视图管理,
Spring实现业务对象管理,
Mybatis作为数据对象的持久化引擎。
SSH vs SSM
SSM和SSH不同主要在MVC实现方式,以及ORM持久化方面不同(Hibernate与Mybatis)。
SSM越来越轻量级配置,将注解开发发挥到极致,且ORM实现更加灵活,SQL优化更简便;
SSH较注重配置开发,其中的Hibernate对JDBC的完整封装更面向对象,
对增删改查的数据维护更自动化,但SQL优化方面较弱,且入门门槛稍高。
执行对象是SQL的执行者。 目前常用的执行对象接口有三种:
Statement
PreparedStatement
CallableStatement
Statement 主要用于执行静态SQL语句,即内容固定不变的SQL语句。
Statement 每执行一次都要对传入的SQL语句编译一次,效率较低。
String name = "tom";
String sqlString = "select * from student_table where student_name = '" + name + "'"; Connection conn = open();
若 name 为用户可控参数,当用户传入 ’ or 1 = 1 # 则sqlString为:
select * from student_table where student_name = ' ' or 1 = 1 # '
可以看到程序输出 student_table 表中所有条目:
预编译
参数化查询执行SQL语句的方式。String sql = "select * from student_table where name = ?";
Connection conn = open();
PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql);
//对占位符进行初始化
pstmt.setString(1, "tom");
pstmt.executeQuery();
看到使用PreparedStatement之后,特殊符号被转义,无法按设想的SQL语句执行了。
CallableStatement接口提供了执行存储过程的方法。
在线数据库练习平台:http://sqlfiddle.com/
create procedure sp_query @name varchar(50) as begin
declare @sql nvarchar(500)
set @sql = 'select * from ForgeRock where productName =''' + @name + ''''
exec(@sql)
end;
为@name赋值 ' or 1=1 --
,语句成为 exec sp_query " ' or 1=1 --
";
成功查询到该表中所有数据
注意:`--`后面需要有空格。
在存储过程中进行参数化查询
create procedure sp_query @name varchar(50) as begin
select * from ForgeRock where productName = @name
end;
为@name赋值' or 1=1 -- ,语句成为 exec sp_query "' or 1=1 -- ";
但此时没有返回任何数据,说明对SQL已有防御。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML
或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,
普通老式 Java 对 象)为数据库中的记录。
官方教程: https://mybatis.org/mybatis-3/zh/getting-started.html
实体类Student
各个参数与数据库中目标表的列名一一对应,包括参数名、参数类型
Dao接口文件:
package mybatis.dao;
import mybatis.pojo.Student; public interface StudentDao {
public Student selectStudent(@Param("name")String name);
}
动态 SQL
是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,
在查询之前mybatis 会对其进行动态解析。 Mybatis框架中,接受用户参数有两种方式:
通过${param}方式
通过#{param}方式
#{ }会自动传入值加上单引号, 而${ }不会。
即,“ ${} ”更容易导致存在sql注入
后端语句:
select * from student_table where student_name = '${name}'
提交
'or1=1#
最终执行:
select * from student_table where student_name = '' or 1=1 #'
SQL注入防御 --> 使用 " #{} "
后端代码是;
select * from student_table where student_name like '%#{name}%'
则传入一个“ ? ”很容易引起程序报错,
因为引号内的?会被当作值处理,所以这里会报错找不到占位符。
常见的解决方法
把“ #{} ”换为“ ${} ”,如:
select * from student_table where student_name like '%${name}%'
新的问题
但是这样又会容易引起注入,
正确的做法(使用concat方法进行连接
)
select * from student_table where student_name like concat('%', #{name}, '%')
后端代码
select * from student_table where student_id in (#{id})
提交 id=“1,2” ; 实际执行的语句如下:
select * from student_table where student_id in ('1,2’)
常见的解决方法
把“ #{} ”换为“ ${} ”,
新的问题
但是这样又会容易引起注入,
正确的解决方法
略微复杂,咱不记录。
凡是字符串但又不能加引号的位置都不能预编译参数化,这种场景不仅order by,还有group by。
防御
这种场景下,通常采用白名单,只开放有限集合,使用间接对象引用,
如果传来的参数不在白名单列表中,直接返回错误即可。
Mybatis框架下易产生SQL注入漏洞的场景有:
Like 模糊查询:
Select * from student where name like ‘%${name}%’
IN 查询:
Select * from news where id in (${id})
order by、group by 查询:
select * from studentwhere id order by ${id}
inxedu 因酷教育软件v2.0:https://www.inxedu.com/downloadCode
将下载的源代码,按照源代码的教程先将数据库导入。然后将整个项目直接导入IDEA。
等待IDEA右下角的进度条跑完,继续按照教程将数据库链接的问题配置好,
然后右击项目,选择“重构模块”,等待进度条跑完,
这里我一开始报了一个错,
百度之后发现是jdk版本的问题,直接右击项目的设置,调整jdk版本为1.8
继续按照教程进行配置,
然后项目直接跑起来,
根据项目的提示得到以下结论:
看到项目使用mybatis框架(resources下边),则使用“ ${ ”这种包括的sql语句都是可能存在注入的。
直接全局搜索,限制文件为“xml”,然后重点关注一些映射文件(Mapper),如下图标记的:
从上边,随意点进去一个,查看代码。
这里,我们可以先记住表名“ EDU_COURSE_FAVORITES ”。
然后根据这个“deleteCourseFavoritesById”关键词,
去查找对应的dao层定义该接口的代码,这里我们再次限制类型为“ .java ”,任意找到一处点击去看看,
看到接收到参数是 String ids,
然后搜索哪里调用过“ deleteCourseFavoritesById ”方法,
选择第一个结果前跟过去,
然后注意到这个237行的路由“/deleteFaveorite”,
在然后接着看该接口所属类最上边的路由“/webapp”,
即我们访问“ url/webapp/delectFaveorite ”路径,就可以走到上述实现接口的方法。
我们看到该方法,需要一个“id”参数,且对这个传入参数进行了一些为空判断等逻辑。
在不为空之后,就调用了我们最开始的“删除”方法,
我们使用burp抓包,修改路径,构造参数去请求, 、、经过测试,这里无需登陆也可以
后台注入立马变为前台注入。
另外,因为接收参数的方法是“ request.getParameter ”,所以我们使用GET/POST请求都可以。
这个sql语句,我们也可以在下边找到对应的记录。
此时我们也可以去看看表里对应的数据是否被删除。
在表内看到,确实被删除了,
这里有一个小知识点:
当我们在次构造传入语句“ 1 and sleep(2) ”会发现他沉睡了2S,
//这里在实际的测试中,发现传入1S则沉睡19S,我们先假设沉睡的是1S。
但是我们构造语句如下图,第七行的payload,会发现他没有沉睡而直接秒返回,
这是因为拼接了第7行之后,sql语句变为第5行,而“ in ”会优先执行,即“ AAA and sleep(2)# ”
但是我们在表中id=1的已经被删除过,所以“ AAA ”是false,即 sleep(2)不会执行。
当然,此时我们可以不闭合或者将“ and ”改为“ or ”;在或者“ id=存在的参数 ”。
此时,在看下边那个方法,先构造路由,
尝试不同的参数,发现也存在注入,
先寻找疑似存在注入的dao层代码,“ ${ ”
、、筛选“ .xml ”文件
根据dao层的id属性的值“ deleteCourseFavoritesById ”去查找这个方法在哪里定义的
、、筛选“ .java ”文件
然后看看是哪里调用过“ deleteCourseFavoritesById ”
、、直接右击“ 查找用法 ”
然后,根据调用的方法,构建路由进行测试
、、此时可以看看idea的日志,执行的sql语句是否为预想的。