看代码学安全(8 )preg_replace函数之命令执行

本文转载自 先知社区:https://xz.aliyun.com/t/2541

经红日安全审计小组授权。

-----------------------------------------------------------------------------------

本文由红日安全成员: 水清云影 编写,如有不当,还望斧正。

前言

大家好,我们是红日安全-代码审计小组。最近我们小组正在做一个PHP代码审计的项目,供大家学习交流,我们给这个项目起了一个名字叫 PHP-Audit-Labs 。现在大家所看到的系列文章,属于项目 第一阶段 的内容,本阶段的内容题目均来自 PHP SECURITY CALENDAR 2017 。对于每一道题目,我们均给出对应的分析,并结合实际CMS进行解说。在文章的最后,我们还会留一道CTF题目,供大家练习,希望大家喜欢。下面是 第8篇代码审计文章:

Day8 - Candle

题目叫蜡烛,代码如下

preg_replace:(PHP 5.5) 功能 : 函数执行一个正则表达式的搜索和替换 定义mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) 搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换

  • $pattern 存在 /e 模式修正符,允许代码执行
  • /e 模式修正符,是 preg_replace() $replacement 当做php代码来执行

漏洞解析

这道题目考察的是 preg_replace 函数使用 /e 模式,导致代码执行的问题。我们发现在上图代码 第11行 处,将 GET 请求方式传来的参数用在了 complexStrtolower 函数中,而变量 $regex$value 又用在了存在代码执行模式的 preg_replace 函数中。所以,我们可以通过控制 preg_replace 函数第1个、第3个参数,来执行代码。但是可被当做代码执行的第2个参数,却固定为 'strtolower("\\1")' 。时间上,这里涉及到正则表达式反向引用的知识,即此处的 \\1 ,大家可以参考 W3Cschool 上的解释:

反向引用 对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

本题官方给的 payload/?.*={${phpinfo()}} 实际上并不能用,因为如果GET请求的参数名存在非法字符,PHP会将其替换成下划线,即 .* 会变成 _* 。这里我们提供一个可用 payload\S*=${phpinfo()} ,详细分析请参考我们前几天发表的文章: 深入研究preg_replace与代码执行

实例分析

本次实例分析,我们选取的是 CmsEasy 5.5 版本,漏洞入口文件为 /lib/tool/form.php ,我们可以看到下图第7行处引用了preg_replace ,且使用了 /e 模式。如果 $form[$name]['default'] 的内容被正则匹配到,就会执行 eval 函数,导致代码执行。具体代码如下:

我们再来看看这个 getform() 函数在何处被引用。通过搜索,我们可以发现在 Cache/template/default/manage/guestadd.php 程序中,调用了此函数。这里我们需要关注 catid (下图 第4行 代码),因为 catid 作为 $namepreg_preolace() 函数中使用到,这是我们成功利用漏洞的关键。 guestadd.php 中的关键代码如下:

那么问题来了, catid 是在何处定义的,或者说与什么有关?通过搜索,我们发现 lib/table/archive.php 文件中的 get_form() 函数对其进行了定义。如下图所示,我们可以看到该函数 return 了一个数组,数组里包含了catidtypeid 等参数对应的内容。仔细查看,发现其中又嵌套着一个数组。在 第6行处 发现了 default字段,这个就是我们上面提到的 $form[$name]['default']

而上图 第6行get() 方法在 lib/tool/front_class.php 中,它是程序内部封装的一个方法。可以看到根据用户的请求方式, get() 方法会调用 front 类相应的 get 方法或 post 方法,具体代码如下:

front 类的 get 方法和 post 方法如下,看到其分别对应静态数组

继续跟进静态方法 getpost ,可以看到在 front 类中定义的静态属性

这就意味着前面说的 $form[$name]['default']namedefault 的内容,都是我们可以控制的。

我们屡一下思路,get_form 函数定义了 catid 的值, catid 对应的 default 字段又存在代码执行漏洞。而 catid 的值由 get('catid') 决定,这个 get('catid') 又是用户可以控制的。所以我们现在只要找到调用 get_form 函数的地方,即可触发该漏洞。通过搜索,我们发现在 /lib/default/manage_act.php 文件的第10行调用了 get_form() 函数,通过 View 模板直接渲染到前台显示:

这就形成了这套程序整体的一个执行流程,如下图所示:

漏洞验证

1、首先打开首页,点击游客投稿

2、进入到相应的页面,传给catid值,让他匹配到 /\{\?([^}]+)\}/e 这一内容,正则匹配的内容也就是 {?(任意内容)} ,所以我们可以构造payload: catid={?(phpinfo())}

修复方案

漏洞是 preg_replace() 存在 /e 模式修正符,如果正则匹配成功,会造成代码执行漏洞,因此为了避免这样的问题,我们避免使用 /e 模式修正符,如下图第7行:

结语

看完了上述分析,不知道大家是否对 preg_replace() /e 模式存在的代码执行有了更加深入的理解,文中用到的CMS可以从 这里 下载(密码:2xaf) 下载,当然文中若有不当之处,还望各位斧正。如果你对我们的项目感兴趣,欢迎发送邮件到 hongrisec@gmail.com 联系我们。Day8 的分析文章就到这里,我们最后留了一道CTF题目给大家练手,题目如下(这次放两道题):

// index.php
<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";

?>
// index2.php
<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>50){
        die("Too Long.");
    }
    if(preg_match("/[A-Za-z0-9_]+/",$code)){
        die("Not Allowed.");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";
?>

题解我们会阶段性放出,如果大家有什么好的解法,可以在文章底下留言,祝大家玩的愉快!

原文发布于微信公众号 - 玄魂工作室(xuanhun521)

原文发表时间:2018-11-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SeanCheney的专栏

《Python分布式计算》第2章 异步编程 (Distributed Computing with Python)协程一个异步实例总结

从本章开始,终于开始写代码了!本书中所有的代码都适用于Python 3.5及以上版本。当模块、语句或语法结构不适用于以前的版本时(比如Python 2.7),会...

696100
来自专栏ImportSource

并发编程-原子性

前面我们说了有关stateless的内容,那么如果我们在一个stateless的object中添加一个状态元素会发生什么呢?现在假设我们想要添加一个命中计数器(...

420110
来自专栏linux系统运维

原 shell脚本介绍, shell脚本

24230
来自专栏北京马哥教育

Python 函数库 APIs 编写指南

33840
来自专栏企鹅号快讯

《数据库系统概念》12-文件的组织

一个数据库被映射到多个不同的文件,这些文件由底层的操作系统来维护。每个文件分成定长的存储单元,称为块(bolck),块是存储分配和数据传输的基本单元。数据库默认...

28990
来自专栏技术随笔

Bufbomb缓冲区溢出攻击实验详解-CSAPP

72180
来自专栏JAVA技术zhai

并发编程的艺术

关于 Java 并发也算是写了好几篇文章了,本文将介绍一些比较基础的内容,注意,阅读本文需要一定的并发基础。

14520
来自专栏微信公众号:Java团长

Java虚拟机:Java内存区域及对象

为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念。

13420
来自专栏沈唁志

如何优化PHP性能呢?PHP性能优化总结

31630
来自专栏极客编程

Nodejs和Mongodb的连接器Mongoose

今天我们将学习Mongoose,什么是Mongoose呢,它于MongoDB又是什么关系呢,它可以用来做什么呢,介绍Mongoose之前,我们先简单了解一下Mo...

21240

扫码关注云+社区

领取腾讯云代金券