Day 1 - Wish List(in_array函数缺陷)
靶场是红日安全的 https://www.ripstech.com/php-security-calendar-2017/ PHP SECURITY CALENDAR 2017
起因是最近在学一点内网渗透,问秦学长要个工具,他问:“源码审计你做了吗”,我说:“没有”,他:“可以开始了”,我:“好的”
虽然我人菜但还是很听话的嘛,源码审计只在 ctf 做题的时候会看一下,还没有专门找时间训练过,而且觉得自己并没有做到熟读源码,只是开发系统的时候现学现卖一点。小黄同学推荐了红日安全的靶场,那就学起来吧(顺便再记一些可能和漏洞无关但是源码涉及到的php函数,眼熟久了就会用了嘻嘻)
源码是这样的
class Challenge { const UPLOAD_DIRECTORY = './solutions/'; private $file; private $whitelist; public function __construct($file) { $this->file = $file; $this->whitelist = range(1, 24); } public function __destruct() { if (in_array($this->file['name'], $this->whitelist)) { move_uploaded_file( $this->file['tmp_name'], self::UPLOAD_DIRECTORY . $this->file['name'] ); } } } $challenge = new Challenge($_FILES['solution']);
在访问PHP类中的成员变量或方法时,如果被引用的变量或者方法被声明成const(定义常量)或者static(声明静态),那么就必须使用操作符::,反之如果被引用的变量或者方法没有被声明成const或者static,那么就必须使用操作符->。
move_uploaded_file 将上传的文件移动到新位置。
这是一个任意文件上传漏洞,而导致这一漏洞的发生则是不安全的使用 in_array() 函数来检测上传的文件名。由于该函数并未将第三个参数设置为 true ,这导致攻击者可以通过构造的文件名来绕过服务端的检测,例如文件名为 7shell.php 。因为PHP在使用 in_array() 函数判断时,会将 7shell.php 强制转换成数字7,而数字7在 range(1,24) 数组中,最终绕过 in_array() 函数判断,导致任意文件上传漏洞。(这里之所以会发生强制类型转换,是因为目标数组中的元素为数字类型,进行弱比较。如果第三个参数设置为 true ,还会比较前两个参数的类型是否相同)
(*)修复
这个漏洞的原因是弱类型比较问题,那么就可以使用强匹配进行修复。例如将 in_array() 函数的第三个参数设置为 true ,或者使用 intval() 函数将变量强转成数字,又或者使用正则匹配来处理变量。
参考:
Day 2 - Twig(filter_var函数缺陷)
源代码是这样的
1 // composer require "twig/twig"
2 require 'vendor/autoload.php';
3
4 class Template {
5 private $twig;
6
7 public function __construct() {
8 $indexTemplate = '<img ' .
9 'src="https://loremflickr.com/320/240">' .
10 '<a href="{{link|escape}}">Next slide »</a>';
11
12 // Default twig setup, simulate loading
13 // index.html file from disk
14 $loader = new TwigLoaderArrayLoader([
15 'index.html' => $indexTemplate
16 ]);
17 $this->twig = new TwigEnvironment($loader);
18 }
19
20 public function getNexSlideUrl() {
21 $nextSlide = $_GET['nextSlide'];
22 return filter_var($nextSlide, FILTER_VALIDATE_URL);
23 }
24
25 public function render() {
26 echo $this->twig->render(
27 'index.html',
28 ['link' => $this->getNexSlideUrl()]
29 );
30 }
31 }
32
33 (new Template())->render();
这道题考察XSS(跨站脚本攻击)漏洞。虽然题目代码分别用了 escape 和 filter_var 两个过滤方法试图保证传入的是一个真正的url,但是还是可以被攻击者绕过。在代码第10行中,程序使用 Twig 模板引擎定义的 escape 过滤器来过滤link,而实际上这里的 escape 过滤器,是用PHP内置函数 htmlspecialchars 来实现的
htmlspecialchars 将特殊字符转换为 HTML 实体
第二处过滤在第22行,这里用了 filter_var 函数来过滤 nextSlide 变量,且用了 FILTER_VALIDATE_URL 过滤器来判断是否是一个合法的url
filter_var 使用特定的过滤器过滤一个变量
这段源码可以用一段简单的 demo 来理解
<?php
$url = filter_var($_GET['url'],FILTER_VALIDATE_URL);
$url = htmlspecialchars($url);
echo "<a href='$url'>Next slide</a>";
?>
使用 payload :?url=javascript://comment%250aalert(1) ,可以执行 alert 函数
这里的 // 在JavaScript中表示单行注释,所以后面的内容均为注释,但是使用字符 %0a ,该字符为换行符,所以 alert 语句与注释符 // 不在同一行,就能执行。当然,这里还要对 % 百分号编码成 %25 ,因为程序将浏览器发来的payload:javascript://comment%250aalert(1) 先解码成:javascript://comment%0aalert(1) 存储在变量 $url 中,然后用户点击 a 标签链接就会触发 alert 函数。
(*)修复
XSS漏洞,最好的方式就是过滤关键词,将特殊字符进行HTML实体编码替换
参考:
Day 3 - Snow Flake(实例化任意对象漏洞)
源码是这样的
1 function __autoload($className) {
2 include $className;
3 }
4
5 $controllerName = $_GET['c'];
6 $data = $_GET['d'];
7
8 if (class_exists($controllerName)) {
9 $controller = new $controllerName($data['t'], $data['v']);
10 $controller->render();
11 } else {
12 echo 'There is no page with this name';
13 }
14
15 class HomeController {
16 private $template;
17 private $variables;
18
19 public function __construct($template, $variables) {
20 $this->template = $template;
21 $this->variables = $variables;
22 }
23
24 public function render() {
25 if ($this->variables['new']) {
26 echo 'controller rendering new response';
27 } else {
28 echo 'controller rendering old response';
29 }
30 }
31 }
文件包含漏洞,代码第8行中使用了 class_exists() 函数来判断用户传过来的控制器是否存在,默认情况下,如果程序存在 __autoload 函数,那么在使用 class_exists() 函数就会自动调用本程序中的 __autoload 函数,这题的文件包含漏洞就出现在这个地方。攻击者可以使用路径穿越来包含任意文件,当然使用路径穿越符号的前提是 PHP5~5.3(包含5.3版本)版本之间才可以。例如类名为: ../../../../etc/passwd 的查找,将查看passwd文件内容
代码第9行中,我们发现实例化类的类名和传入类的参数均在用户的控制之下。攻击者可以通过该漏洞,调用PHP代码库的任意构造函数。即使代码本身不包含易受攻击的构造函数,我们也可以使用PHP的内置类 SimpleXMLElement 来进行 XXE 攻击,进而读取目标文件的内容,甚至命令执行(前提是安装了PHP拓展插件expect)
SimpleXMLElement 用来表示XML文档中的元素,为PHP的内置类
这篇文章 http://www.manongjc.com/article/24000.html 介绍了XXE使用实例、应用技巧、基本知识点总结和需要注意事项
(*)修复
PHP中XXE漏洞的修复,我们可以过滤关键词,如: ENTITY 、 SYSTEM 等,另外,还可以通过禁止加载XML实体对象的方式,来防止XXE漏洞,代码如下
libxml_disable_entity_loader(true);
参考:
Day 4 - False Beard(strpos使用不当引发漏洞)
源码是这样的
1 class Login {
2 public function __construct($user, $pass) {
3 $this->loginViaXml($user, $pass);
4 }
5
6 public function loginViaXml($user, $pass) {
7 if (
8 (!strpos($user, '<') || !strpos($user, '>')) &&
9 (!strpos($pass, '<') || !strpos($pass, '>'))
10 ) {
11 $format = '<?xml version="1.0"?>' .
12 '<user v="%s"/><pass v="%s"/>';
13 $xml = sprintf($format, $user, $pass);
14 $xmlElement = new SimpleXMLElement($xml);
15 // Perform the actual login.
16 $this->login($xmlElement);
17 }
18 }
19 }
20
21 new Login($_POST['username'], $_POST['password']);
代码第11行和第12行,程序通过格式化字符串的方式,使用 xml 结构存储用户的登录信息。实际上这样很容易造成数据注入。然后第21行实例化 Login 类,并在第16行处调用 login 方法进行登陆操作。在进行登录操作之前,代码在第8行和第9行使用 strpos 函数来防止输入的参数含有 < 和 > 符号,猜测开发者应该是考虑到非法字符注入问题
strpos 函数返回查找到的子字符串的下标。如果字符串开头就是我们要搜索的目标,则返回下标 0 ;如果搜索不到,则返回 false 。在这道题目中,开发者只考虑到 strpos 函数返回 false 的情况,却忽略了匹配到的字符在首位时会返回 0 的情况,因为 false 和 0 的取反均为 true 。这样我们就可以在用户名和密码首字符注入 < 符号,从而注入xml数据
strpos 查找字符串首次出现的位置
sprintf 返回一个格式化字符串
如果构造 payload:
user=<"><injected-tag%20property="&pass=<injected-tag>
strpos 函数的返回结果是这样的
<?php
$user = '<"><injected-tag property=" ';
$pass = '<injected-tag>';
var_dump(strpos($user, '<')); //int 0
var_dump(!strpos($user, '<')); //boolean true
var_dump(strpos($user, '>')); //int 2
var_dump(!strpos($user, '<')); //boolean false
var_dump(
(!strpos($user, '<')) || !strpos($user, '>')) && (!strpos($user, '<')) || !strpos($user, '>'))
) //boolean true
//相当于var_dump((true || false) && (true || false))
?>
很明显是可以注入xml数据的
(*)修复
这个漏洞主要是开发人员对 strpos 函数理解不够造成的,区分开 0 和 false 即可避免,cms中还没有这个函数造成的漏洞,ctf题目里可能遇到
参考: