22.8.8更新

相关前言

简单来说因为开发者没有秉持“外部参数皆不可信的原则”进行开发,导致产品满足以下两个条件:

  • 攻击者可以控制要构造的参数
  • 参数带入数据库查询(直接拼接到SQL语句,没有作过滤处理)

SQL注入的常见类型

  • UNION query SQL injection(union查询注入)
  • Boolean-based blind SQL injection(布尔盲注)
  • Error-based SQL injection(基于报错的注入)
  • Time-based blind SQL injection(时间盲注)
  • Stacked queries SQL injection

MySQL一些重要数据库

在MySQL5.0版本后,默认在数据库中会存放一个”infomation_schema”的数据库。此库中有三个表:

  • SCHEMATA:存储该用户创建的所有数据库的库名。记录库名的字段名为SCHEMA_NAME
  • TABLES:存储该用户创建的所有数据库的库名和表名。字段名分别为TABLE_SCHEMA和TABLE_NAME
  • COLUMNS:存储该用户创建的所有数据库的库名、表名和字段名,字段名分别为TABLE_SCHEMA、TABLE_NAME和COLUMN_NAME

基础SQL语句

查询语句

1
2
3
4
5
6
# 不知道任何条件时:
SELECT columnsName FROM database.tableName
# 已知一条条件:
SELECT columnsName FROM database.tableName WHERE columnsName = ...
# 已知两条条件:
SELECT columnsName FROM database.tableName WHERE columnsName = ... AND columnsName = ...

limit用法

格式:limit m, n m为开始位置,从0开始。n为记录条数。

常见函数

  • database():当前网站数据库
  • version():当前MySQL版本
  • user():当前MySQL用户
  • concat相关函数:拼接字符串
  • substr(str, n, m):分割字符串,返回str从第n个开始的m个字符(从1开始)
  • length(str):返回字符串的长度
  • char(n):返回ascii码
  • ascii(c):返回编码
  • count():计数
  • mid():与substr()一致,后者被拦截时尝试
  • ord():与ascii()一致。二者在接收字符串参数时只编码第一位字符
  • left(str, n):从第一位向右截取n位

多行执行

sql通过分号来执行相应语句,但是一般不会允许通过一行执行多行。在Stacked queries注入中可以使用此方式

注释符

#--空格/**/

内联注释

1
2
/*!UNIOM*/
/*!SELECT*/

大小写

诸如DaTaBaSe()之类

Union注入

测试

访问id=1’返回结果与id=1不同

访问id=1 and 1=1 因为and 1=1为真,返回与id=1相同结果。

访问id=1 and 1=2 因为后者为假 返回与id=1不同结果。

结论:可能存在SQL注入。

ORDER BY 关键字

语法:

1
2
3
SELECT column_name,column_name
FROM table_name
ORDER BY column_name,column_name ASC|DESC;

有以下数据:

1
2
3
4
5
6
7
8
9
+----+--------------+---------------------------+-------+---------+
| id | name | url | alexa | country |
+----+--------------+---------------------------+-------+---------+
| 1 | Google | https://www.google.cm/ | 1 | USA |
| 2 | 淘宝 | https://www.taobao.com/ | 13 | CN |
| 3 | 菜鸟教程 | http://www.runoob.com/ | 4689 | CN |
| 4 | 微博 | http://weibo.com/ | 20 | CN |
| 5 | Facebook | https://www.facebook.com/ | 3 | USA |
+----+--------------+---------------------------+-------+---------+

下面的 SQL 语句从 “Websites” 表中选取所有网站,并按照 “alexa” 列排序:

1
2
SELECT * FROM Websites
ORDER BY alexa;

下面的 SQL 语句从 “Websites” 表中选取所有网站,并按照 “country” 和 “alexa” 列排序:

1
2
SELECT * FROM Websites
ORDER BY country,alexa;

此时会先对country字段排序,然后在每个country里对alexa进行排序。

查询字段数量

先使用'符号检查有无漏洞存在,然后查询版本信息、数据库名、用户名等,再从information_schema数据库中查询出数据库名、表名、字段名,就可以知道字段数量。

接下来使用order by 1-99语句查询表的字段数量

访问id=1 order by 3返回与id=1相同结果。访问id=1 order by 4返回不同结果,则字段数为3。

https://www.runoob.com/sql/sql-union.html)

查询相关语句

查询一般会使用like关键字:

1
select ... form ... where ... like '%x%';

like前为字段名,%表示任意匹配。

判断前后端分离

现在网站一般都是前后端分离,前端进行请求时会请求后端api接口。我们可以通过网络数据包请求的url判断是否为前后端分离。再判断是哪种请求方式

基于时间的盲注

是否存在注入

当没有明显回显时无从判断是否存在sql注入,可以使用sleep(1)这样的语句结合响应的时间来判断是否存在注入。

注意:sleep(1)并不意味着语句真的只会执行1秒。例如如果延时函数存在于where中被执行,那么每条匹配的结果都会执行判断的延迟函数,因此实际上应该被执行n秒(n为sleep被实际执行的次数),且sql在逻辑判断中与其他编程语言类似,只有and逻辑运算前者为真时后者才有机会被执行,此时如果判断的第一条语句没有真的情况,and后的sleep将不会被执行,宏观上表现为没有延迟。

枚举数据库

结合字符串分割函数与条件判断,可以盲注出一些数据库的数据。

例如:

1
2
3
4
5
6
# 判断数据库名长度
select length(database()) = 10 and sleep(1); # 此时若前者未假,将不会延迟响应。反之可以得到数据库名长度为10

select substr(database(), n, 1) = "a" and sleep(1); # 此时若延迟,说明前者匹配,可以将n遍历1-10的值来枚举出数据库的完整名称。若没有明显延迟,说明需要继续枚举第n位的ascii码。

# 总结:通过... and sleep()这种形式,可以简单地枚举数据库信息。当时间延迟时判断前者匹配,反之继续枚举。

Bypass

在很多情况比如CTF中,可能会有一些敏感语句的拦截。

注释

--+#/**/

空格绕过

符号 url
空格 %20
%27
# %23
/ %2f
水平tab %09
\n %0a
垂直tab %0b
新的一页 %0c
return %0d
空格 %a0

常见用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**/代替空格
()代替空格
回车%0a
`
tab
两个空格

举例:
select/**/*/**/from/**/users;
select(id)from(users);
>>>注意空格中不能含有*
mysql> select
-> *
-> from
-> users;
select`id`from`users`where`id`=1;
select * from users;
select * from users;

内联注释

把一些特有的仅在MySQL上的语句放在/*!...*/

这样这些语句如果在其他数据库中是不会被执行,但在MySQL中会执行

1
select id from users where id = 1 union /*!select*/ 1,2,3,4,5,6,7,8;

函数替换

将敏感函数替换为等价其他函数。例如:

substr()、mid()、left();

ascii()、ord();

⬆︎TOP