最近工作太忙了,博客都长草了,拿出来之前Bloody发我一个洞分析一下,水一篇博文。

目前禅知Pro 1.6.1 已经修复了这个漏洞。

D:\phpStudy\WWW\chanzhipro.full.5.4\www\file.php

<?php
$pathname   = '';
$objectType = '';
$imageSize  = '';
$extension  = '';
$version    = '';

if(isset($_GET['pathname']))   $pathname   = $_GET['pathname'];
if(isset($_GET['objectType'])) $objectType = $_GET['objectType'];
if(isset($_GET['imageSize']))  $imageSize  = $_GET['imageSize'];
if(isset($_GET['extension']))  $extension  = $_GET['extension'];

if(isset($_GET['f'])) $pathname   = $_GET['f'];
if(isset($_GET['o'])) $objectType = $_GET['o'];
if(isset($_GET['s'])) $imageSize  = $_GET['s'];
if(isset($_GET['t'])) $extension  = $_GET['t'];
if(isset($_GET['v'])) $version    = $_GET['v'];

$dataRoot = rtrim(dirname($_SERVER['SCRIPT_FILENAME']), '/') . '/data/';

首先初始化一些配置,定义我们传进来的参数。$dataRoot 我们可读取文件的路径

if($objectType == 'source' or $objectType == 'slide')
{
    if($objectType == 'slide' and !preg_match('/^slides\/[0-9_0-9]/', $pathname)) die('The file does not exist!');
    $savePath = $dataRoot;
}
else
{
    if(!preg_match('/^[0-9]{6}\/f_[a-z0-9]{32}/', $pathname)) die('The file does not exist!');
    $savePath = $dataRoot . 'upload/';
}

$realPath = $savePath . $pathname;

这段代码中我们可控的参数有$objectType $pathname 

 $objectType  传入字符串 source  后 $dataRoot 会赋给$savePath

接着走出判断,$realPath 把我们传入的 $pathname拼接和 $savePath 拼接起来

if(!file_exists($realPath))
{
    $realPath = $savePath . (strpos($pathname, '.') === false ? $pathname : substr($pathname, 0, strpos($pathname, '.')));
}

$filePath = $realPath;
if($imageSize == 'smallURL')  $filePath = str_replace('f_', 's_', $realPath);
if($imageSize == 'middleURL') $filePath = str_replace('f_', 'm_', $realPath);
if($imageSize == 'largeURL')  $filePath = str_replace('f_', 'l_', $realPath);

if(!file_exists($filePath)) $filePath = $realPath;

if(!file_exists($filePath)) die('The file does not exist!');

$seconds = 3600 * 24 * 30; 
$expires = gmdate("D, d M Y H:i:s", time() + $seconds) . " GMT";
header("Expires: $expires"); 
header("Pragma: cache");
header("Cache-Control: max-age=$seconds");

$mime = getMimetype($extension);
header("Content-type: $mime");

$handle = fopen($filePath, "r");

再经过一些没有什么卵用的判断后,我们的 $realPath 变量又被赋值到$filePath

接着往下走到 $mime 这里,看一下 getMimetype 函数

function getMimetype($extension)
{
    $mimeTypes = array(
        'ez' => 'application/andrew-inset',
        'anx' => 'application/annodex',
        'atom' => 'application/atom+xml',
        'atomcat' => 'application/atomcat+xml',
        'atomsrv' => 'application/atomserv+xml',
        'lin' => 'application/bbolin',
        'cu' => 'application/cu-seeme',
        'davmount' => 'application/davmount+xml',
        'dcm' => 'application/dicom',
        'tsp' => 'application/dsptype',
        'es' => 'application/ecmascript',
        'otf' => 'application/font-sfnt',
        'ttf' => 'application/font-sfnt',
        'pfr' => 'application/font-tdpfr',
        'woff' => 'application/font-woff',
        'spl' => 'application/x-futuresplash',
        'gz' => 'application/gzip',
        'hta' => 'application/hta',
        'jar' => 'application/java-archive',
        'ser' => 'application/java-serialized-object',
        'class' => 'application/java-vm',
        'js' => 'application/javascript',

这个函数只是定义http mime头,这函数太长了,我只截取了一部分。OK我们在回到 $mime 这里。

$extension 变量在代码开头被定义为$_GET[‘o’] ,我们读文件的话自然以txt的格式读。

那么再往下 fopen() 直接读取了我们的路径

$handle = fopen($filePath, "r");

那么最终 payload 为:

http://localhost/www/file.php?pathname=../file.php&t=txt&o=source

漏洞修复

在最新版拼接路径处加入 realpath() 函数去除目录跳跃的操作

并使用 strpos() 判断我们传入的路径 ($realPath) 是否在可读取的路径($dataRoot)

<?php
$pathname   = '';
$objectType = '';
$imageSize  = '';
$extension  = '';
$version    = '';

if(isset($_GET['pathname']))   $pathname   = $_GET['pathname'];
if(isset($_GET['objectType'])) $objectType = $_GET['objectType'];
if(isset($_GET['imageSize']))  $imageSize  = $_GET['imageSize'];
if(isset($_GET['extension']))  $extension  = $_GET['extension'];

if(isset($_GET['f'])) $pathname   = $_GET['f'];
if(isset($_GET['o'])) $objectType = $_GET['o'];
if(isset($_GET['s'])) $imageSize  = $_GET['s'];
if(isset($_GET['t'])) $extension  = $_GET['t'];
if(isset($_GET['v'])) $version    = $_GET['v'];

$dataRoot = rtrim(dirname($_SERVER['SCRIPT_FILENAME']), '/') . '/data/';

if($objectType == 'source' or $objectType == 'slide')
{
    if($objectType == 'slide' and !preg_match('/^slides\/[0-9_0-9]/', $pathname)) die('The file does not exist!');
    $savePath = $dataRoot;
}
else
{
    if(!preg_match('/^[0-9]{6}\/f_[a-z0-9]{32}/', $pathname)) die('The file does not exist!');
    $savePath = $dataRoot . 'upload/';
}

$realPath = realpath($savePath . $pathname);
if(!file_exists($realPath))
{
    $realPath = realpath($savePath . (strpos($pathname, '.') === false ? $pathname : substr($pathname, 0, strpos($pathname, '.'))));
}

if(strpos($realPath, realpath($dataRoot)) === false) die('The file does not exist!');

$filePath = $realPath;
if($imageSize == 'smallURL')  $filePath = str_replace('f_', 's_', $realPath);
if($imageSize == 'middleURL') $filePath = str_replace('f_', 'm_', $realPath);
if($imageSize == 'largeURL')  $filePath = str_replace('f_', 'l_', $realPath);

if(!file_exists($filePath)) $filePath = $realPath;

if(!file_exists($filePath)) die('The file does not exist!');

$seconds = 3600 * 24 * 30; 
$expires = gmdate("D, d M Y H:i:s", time() + $seconds) . " GMT";
header("Expires: $expires"); 
header("Pragma: cache");
header("Cache-Control: max-age=$seconds");

$mime = getMimetype($extension);
header("Content-type: $mime");

$handle = fopen($filePath, "r");

 

1 thought on “禅知Pro 1.6 前台任意文件读取分析”

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Are you human? Click the Pineapple...