Day 9 - Rabbit(str_replace函数过滤不当)
源码是这样的
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
Day 10 - Anticipation(程序未恰当exit导致的问题)
源码是这样的
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
Day 11 - Pumpkin Pie(unserialize反序列化漏洞)
源码是这样的
1 class Template { 2 public $cacheFile = '/tmp/cachefile'; 3 public $template = 'Welcome back %s'; 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 = ''; } 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:"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
Day 12 - String Lights(误用htmlentities函数引发的漏洞)
源码是这样的
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 "link";
这里考察的是个 xss漏洞 , 漏洞触发点在代码中的第13-14行。这两行代码的作用是直接输出一个html的 标签。代码中的第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的漏洞触发点在 标签。
本地做一下实验 test.php,payload:%27onclick%3dalert(1)//
php $query = $_GET['query']; echo "link"; ?>
(*)修复
htmlentities 函数,在使用的时候尽量加上可选参数,并且选择 ENT_QUOTES 参数。
参考:
https://xz.aliyun.com/t/2757