由于比较泛,所以就单独拿一篇文章来记录,仅是入门
参考赵师傅b站:https://www.bilibili.com/video/BV1XE411f7C8
pockr教程
利用wampserver的集成套件,使用里面的mysql数据库
cmd中输入mysql -u root -p
回车后再输入密码
基础命令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16show databases;
create database test;
user test;
create table users(id int(11),name varchar(255));
show tables;
desc users;
insert into users values(1,'dgz'),(2,'admin'),(4,'test');
select * from users;
select * from users order by id;
select * from users order by id\G; 的作用是将查到的结构旋转90度变成纵向
select * from users order by id desc; #根据id大小倒序排
select * from users limit 1 offset 0; #偏移0选一行值
select * from users limit 2 offset 1; #偏移1选两行值,即选第二、三行数据
update users set name='test2' where id = 1;
update users set name='test2' where id = 1\G; \G的作用是将查到的结构旋转90度变成纵向
delete from users where id=2;
由于本地mysql对于字符型与整数型,会存在一个自动转换的过程,所以对于常见sql注入复现的题目本地测试会有点差别。
参考:https://blog.csdn.net/yjgithub/article/details/74534066
原本是VARCHAR类型,不加引号,在搜索时需要加多一个转换操作,将INT转换VARCHAR,会多花一点时间,但是对于我这么小的表几乎没有影响,搜索的结果都是一样的。
DVWA靶场sql injection
Low级别
查看源代码
1 |
|
id是字符型的,所以构造sql语句时需要单引号闭合,而且看得出没有过滤,没有白名单黑名单
当然也可以测试一下,输入1' and 1=1#
和1' and 1=2#
对于此处这个sql语句,代入之后是SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=1#';
#号的作用是注释掉后面的引号,而没有注释掉分号,
但是本地测试可以看到#对分号有影响,注释掉分号了,因为分号没了就不是完整的sql语句了所以无法运行搜索,所以是有区别的。
输入1' and 1=2#
没有回显
对于1' or 1=1#
某一些服务只会回显id=1,而不会所有都显示出来,所以最好可以改成0' or 1=1#
,让前面的查询无效,在dvwa下此时是所有信息都会显示出来。
对于order by
查字段数,原理其实是order by
是一个排序的功能符?(我可以这么说吗)
如图的测试,相同id以age大小排序
而在sql注入中测试时
order by
实际上是以字段序数和字段名称进行排序,如图order by 3
和order by age
结果是一样的,而order by 4
报错是因为没有第四列,所以通过order by
可以查字段数,常用的方法是二分法查询。
所以在dvwa中,这样的语句可以有正确回显,而当order by 3
时就会报错,因为"SELECT first_name, last_name FROM users WHERE user_id = '$id';";
只有两个字段,超过2就会出错
对于union select
联合查询,union运算符可以将两个或者两个以上select语句的查询结果集合合并成一个结果集显示,即执行联合查询。需要注意在使用union查询的时候需要和主查询的列数相同,其需要前后搜索的字段个数相同,也就是一般用order by
知道字段数之后才union select
如图,前后select的字段必须相同,不然无法联合查询,排序1,2,3是要知道字段对应的位置,大概是因为原表中没有id=1,name=2,age=3的数据才这么回显出来。
当我们输入1' union select version(),@@version_compile_os#
在第二个结果返回了当前数据库的版本和当前操作系统。
之后就是通过union select
获取到我们想要的关键信息
其中,information_schema是mysql自带的一个库,可以叫元数据库,这个库保存了mysql所有数据库的信息,如数据库名、数据库的表与访问权限等等,该数据库拥有一个名为TABLES的数据表,该表包含两个字段table_name和table_schema,分别记录DBMS中的存储的表名和表名所在的数据库。
更详细介绍可见:https://www.cnblogs.com/lcbwwy/p/12670465.html
所以接下来的关键信息获取,要通过元数据库获取
利用database()获得数据库名称
然后1' union select 1,table_name from information_schema.TABLES where table_schema='dvwa'#
,得到库里面的表名users和guestbook,然后猜想users实际一点
接下来1' union select 1,column_name from information_schema.COLUMNS where table_name='users'#
对大小写不敏感,其实全部表名库名小写大写都行
1' union select user,password from users#
得到了用户名和密码
Medium级别
变成了选择框
查看源代码
1 |
|
看到id是整数型的,而且选择后url没有对应id显示,应该是post型传参
burp抓包,谷歌浏览器需要在设置里面的管理证书-受信任的根证书颁发机构-导入burp的证书
残废型选手上线,一直抓不到dvwa的包,后来一看
都不代理了怎么还能抓到包?赶紧删掉然后就可以抓包了
源代码中$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
会转义字符串中的特殊字符:包括:NUL(ASCII 0)、\n、\r、\、’、” 和 Control-Z。
会把我们常用的单引号给转义了,前面会加一个反斜杠
然后我们这里可以利用16进制绕过引号的过滤
输入2 union select 1, group_concat(table_name) from information_schema.tables where table_schema=database()#
对比上面low级别的,我们也可以看到group_concat的作用就是将输出结果用逗号连在一起,
2 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#
2 union select user,password from users#
High级别
变成另开的一个输入框,回显还是在原页面
查看源代码:
1 | <?php |
可以看到依然是没有过滤,只是通过LIMIT 1
限制了返回一行值,但是我们依然可以通过#注释掉,所以没有什么用
因此直接用low级别的payload就可以了
需要特别提到的是,High级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap工具注入,因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
Impossible级别
查看源代码:
1 | <?php |
这种防御据称是不可以注入的,因为利用了PDO技术
PDO技术:PHP数据对象(PHP Data Object)。在生成网页时,许多PHP脚本通常都会执行除参数之外其他部分完全相同的查询语句,针对这种重复执行一个查询,每次迭代使用不同的参数情况,PDO提供了一种名为预处理语句(prepared statement)的机制。它可以将整个SQL命令向数据库服务器发送一次,以后只有参数发生变化,数据库服务器只需对命令的结构做一次分析就够了,即编译一次,可以多次执行。会在服务器上缓存查询的语句和执行过程,而只在服务器和客户端之间传输有变化的列值,以此来消除这些额外的开销。这不仅大大减少了需要传输的数据量,还提高了命令的处理效率。可以有效防止SQL注入,在执行单个查询时快于直接使用query()或exec()的方法,速度快且安全,推荐使用。
1 | $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); |
我的理解就是由于使用的是同一个sql语句进行查询,只有参数发生变化,利用PDO就可以编译一次,多次执行,不用重复编译,减少时间,此外划清了代码与数据的界限,有效防御SQL注入。
还有利用is_numeric($id)函数来判断输入的id是否是数字or数字字符串,满足条件才知晓query查询语句
1 | if( $data->rowCount() == 1 ) { |
只有当返回的查询结果数量为一个记录时,才会成功输出,这样就有效预防了暴库
Anti-CSRF token机制的加入了进一步提高了安全性,session_token是随机生成的动态值,每次向服务器请求,客户端都会携带最新从服务端下发的session_token值向服务器请求作匹配验证,相互匹配才会验证通过