时间轴:
知识点
1、PHP 文件管理-下载&删除功能实现
2、PHP 文件管理-编辑&包含功能实现
php文件操作安全
文件包含、文件上传、文件下载、文件删除、文件写入、文件遍历
其中文件上传可以上传在本地服务器,也可以上传在oss对象存储。但是如果使用oss又不进行相关配置,在浏览器中f12后可以在upload.js文件中看到access id/key,再利用oss browser登陆,就可以看到上传的文件。
架构
1.上传至服务器本身的存储磁盘(传统方法)
2.借助云产品OSS存储对象去存储文件(不解析)(泄露安全)
由于只能存储而不能解析,因此即使上传木马也不能获取权限,就更加安全,但是如果在浏览器中f12,也有可能可以找到access id/key
3.把文件上传到其他域名
例如:www.xiaodi8.com—>upload.xiaodi8.com
OSS存储对象
1.在upload.js里配置accesskey(RAM访问控制->概述->accesskey)
文件查看url:
将accesskey等输入upload.js
设置好之后去oss上传,发现报错了
2.报错需要改为公共读写(权限控制-读写权限)
或者使用oss-browser.exe,输入access id和key连接上去,就可以看见具体数据
accesskey泄露:
如果在浏览器中f12,也有可能可以找到access id/key
演示案例:文件管理模块-加工后续-编辑&删除&下载&包含
相关知识
文件包含相关函数
include() 在错误发生后脚本继续执行
require() 在错误发生后脚本停止执行
include_once() 如果已经包含,则不再执行
require_once() 如果已经包含,则不再执行
文件包含存在的漏洞:如果含有变量,例如:include($_GET['page'];,在访问时?page=可以为任何文件,很容易被上传木马。
例如设置1.txt为
当直接访问?page=1.txt时,回显如下图所示
文件包含的优点:可以进行文件的调用,例如:如果不设置文件包含,在访问upload.php之前需要先访问upload.html,但如果在upload.php中include 'upload.html'后就可以直接访问upload.php了。
漏洞产生原因
1.使用什么函数去执行(函数方面的)
2.可以控制的值(用户提交的恶意数据)
文件上传机制
1、无过滤机制
2、黑名单过滤机制
3、白名单过滤机制
4、文件类型过滤机制
实现文件编辑、删除、下载、包含功能具体步骤
最终完整代码
<?php
ini_set('open_basedir',__DIR__);
$path=$_GET['path'] ?? './';
$action=isset($_GET['a']) ?$_GET['a']:'';
$path=isset($_GET['path']) ?$_GET['path']:'.';
if(is_file($path)){//判断是否是文件
//获得文件名
$file=basename($path);
//获得路径
$path=dirname($path);
}elseif(!is_dir($path)){//判断,不是目录
echo '无效的文件文件路径参数';
}
function getlist($path){
$hd=opendir($path);
while(($file_name=readdir($hd))!==false){
if($file_name!='.' and $file_name!='..'){
$file_path="$path/$file_name";
$file_type=filetype($file_path);
}
$list[@$file_type][] =array(
'file_name'=>@$file_name,
'file_path'=>@$file_path,
'file_size'=>round(filesize(@$file_path)/1024),
'file_time'=>date('Y/m/d H:i:s',filemtime(@$file_path)),
);
}
closedir($hd);
return $list;
}
$list=getlist($path);
//接收方法,判断怎么操作
switch($action){
case 'del':
unlink("$path/$file");//或者写成($file)或者可以用以下的cmd方式删除
// system("del $file");
break;
case 'down':
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $file . "\"");
header("Content-Length: " . filesize($file));
readfile($file);
break;
case 'edit';
$content=file_get_contents($file);
echo "<form name='form1' method='post' action=''>";
echo "文件名:".$file.'<br>';
echo "文件内容:<br>";
echo '<textarea name="code" style="resize:none;" rows="100" cols="100">'.$content.'</textarea><br>';
echo "<input type='submit' name='submit' id='submit' value='提交'>";
echo "</form>";
break;
}
if(isset($_POST['code'])){
$f=fopen("$path/$file",'w+');
fwrite($f,$_POST['code']);
fclose($f);
}
?>
<table width="100%" style="font-size: 10px;text-align: center;">
<tr>
<th>图标</th>
<th>名称</th>
<th>日期</th>
<th>大小</th>
<th>路径</th>
<th>操作</th>
</tr>
<?php foreach($list['dir'] as $value):?>
<tr>
<td><img src="./img/list.png" width="20" height="20"></td>
<td><?php echo $value['file_name'];?></td>
<td><?php echo $value['file_time'];?></td>
<td>-</td>
<td><?php echo $value['file_path'];?></td>
<td><a href="?path=<?php echo $value['file_path'];?>">打开</a></td>
</tr>
<?php endforeach;?>
<?php foreach($list['file'] as $value):?>
<tr>
<td><img src="./img/file.png" width="20" height="20"></td>
<td><?php echo $value['file_name'];?></td>
<td><?php echo $value['file_time'];?></td>
<td><?php echo $value['file_size'];?></td>
<td><?php echo $value['file_path'];?></td>
<td>
<a href="?a=edit&path=<?php echo $value['file_path'];?>">编辑</a>
<a href="?a=down&path=<?php echo $value['file_path'];?>">下载</a>
<a href="?a=del&path=<?php echo $value['file_path'];?>">删除</a>
</td>
</tr>
<?php endforeach;?>
</table>
具体过程
第一步
首先创建一个filemanager.php,在其中写入最基本的网页前端代码。
再在demo01文件夹下创建img文件夹,用于存放网页图标。
<table width="100%" style="font-size: 10px;text-align: center;">
<tr>
<th>图标</th>
<th>名称</th>
<th>日期</th>
<th>大小</th>
<th>路径</th>
<th>操作</th>
</tr>
<tr>
<td><img src="./img/list.png" width="20" height="20"></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><img src="./img/file.png" width="20" height="20"></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>
</td>
</tr>
</table>
效果如图所示:
之后要实现在网页上显示出文件和文件夹,代码和结果如下图所示
<?php
$path=$_GET['path'] ?? './';
$hd=opendir($path);
while(($file_name=readdir($hd))!==false){
if($file_name!='.' and $file_name!='..'){
echo $file_name."<br>";
}
}
?>
第二步
此时,显示出来的有文件夹也有文件,那么接下来需要把文件夹和文件分开,list.png图标显示文件夹,file.png图标显示文件。
代码变成
<?php
$path=$_GET['path'] ?? './';
$hd=opendir($path);
while(($file_name=readdir($hd))!==false){
if($file_name!='.' and $file_name!='..'){
// 完整路径
$file_path = "$path/$file_name";
// 获取文件或者文件夹类型
$file_type = filetype($file_path);
}
// 将文件、文件夹封装为数组,其中[@$file_type]为外部键,'file_name'为内部键,@$file_name是值
$list[@$file_type][] =array(
'file_name'=>@$file_name,//文件名存储键值file_name
'file_path'=>@$file_path,//文件路径存储键值file_path
'file_size'=>round(filesize(@$file_path)/1024),//通过换算文件大小存储键值file_path
'file_time'=>date('Y/m/d H:i:s',filemtime(@$file_path)),//获取文件时间并存储键值
);
}
?>
<table width="100%" style="font-size: 10px;text-align: center;">
<tr>
<th>图标</th>
<th>名称</th>
<th>日期</th>
<th>大小</th>
<th>路径</th>
<th>操作</th>
</tr>
<?php foreach($list['dir'] as $value):?> //foreach循环遍历
<tr>
<td><img src="./img/list.png" width="20" height="20"></td>
<td><?php echo $value['file_name'];?></td>
<td><?php echo $value['file_time'];?></td>
<td>-</td>
<!-- 文件夹没有大小-->
<td><?php echo $value['file_path'];?></td>
<td></td>
</tr>
<!-- 结束语句--> //记得结束语句
<?php endforeach;?>
<?php foreach($list['file'] as $value):?>
<tr>
<td><img src="./img/file.png" width="20" height="20"></td>
<td><?php echo $value['file_name'];?></td>
<td><?php echo $value['file_time'];?></td>
<td><?php echo $value['file_size'];?></td>
<td><?php echo $value['file_path'];?></td>
<td>
</td>
</tr>
<?php endforeach;?>
</table>
显示如下图
知识点1:filemtime()函数
返回文件内容上次的修改时间
知识点2:foreach()函数
foreach循环(只能用于数组):
第一种语法:
foreach ($array as $value){
code to be executed;
}
第二种语法:
foreach ($array as $key => $value){
code to be executed;
}
每进行一次循环迭代,当前数组元素的值就会被赋值给$value,并且数组指针会逐一移动,直到最后一个元素。
第三步
接下来要实现文件夹的打开功能。php代码中需要定义函数,并将html中的操作代码添加a标签。代码变成:
function getlist($path){
$hd=opendir($path);
while(($file_name=readdir($hd))!==false){
if($file_name!='.' and $file_name!='..'){
$file_path="$path/$file_name";
$file_type=filetype($file_path);
}
$list[@$file_type][] =array(
'file_name'=>@$file_name,
'file_path'=>@$file_path,
'file_size'=>round(filesize(@$file_path)/1024),
'file_time'=>date('Y/m/d H:i:s',filemtime(@$file_path)),
);
}
closedir($hd);
return $list;
}
$list=getlist($path);
?>
网页端可以看到:
第四步
实现文件的删除功能,代码如下:
网页端显示如下
现在尝试点击删除1.php,刷新页面后,1.php消失,说明删除功能实现
知识点3:unlink()函数
用于删除文件
unlink(filename[必需],content[可选:规定文件句柄的环境])
知识点4
href属性指定链接的目标url
知识点5
$action=isset($_GET['a']) ?$_GET['a']:'';的解释:
?:是三元运算符,类似于if_else语句
语法为:(condition) ? (true_expression) : (false_expression)
如果isset($_GET['a'])为true,即a存在,则将$_GET['a']的值赋给$action;如果isset($_GET['a'])为false,即a不存在,则将空字符串赋给$action。
知识点:6switch语句
用于根据多个不同条件执行不同动作
语法:
<?php switch (expression){ case value1:若expression的值等于某个case的值,就执行相应代码块 //代码块1 break;(用于终止switch语句,防止继续执行下一个case) case value2: //代码块2 break; //更多的case语句 default:(是可选的,用于指定当没有匹配的case时执行的代码块) //如果没有匹配的值 } ?>第五步
实现文件的下载功能
在switch语句下增加代码:
case 'down':
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $file . "\"");
// Content-Disposition用于指定内容处理方式
// attachment表示浏览器应将响应内容作为下载文件处理,而非显示
header("Content-Length: " . filesize($file));
readfile($file);
break;
点击下载1.txt,可以看到文件下载功能也实现了。
第六步
实现文件的编辑功能
修改后的代码参考上面的最终完整代码,效果如图
在文本框中输入内容点击下面的提交,刷新可以看到写入的内容
知识点7:basename()函数
返回路径中的文件名部分
basename(path[必需],suffix[可选,规定文件扩展名,若有文件扩展名,将不会显示该扩展名])
知识点8:dirname()函数
返回路径中的目录名称部分
知识点9:有关文件读、写的函数
1.file_get_contents():读取文件内容
2.fopen()、fread():文件打开、读入
3.fwrite(file[必需] string[必需,要写入的内容] length[可选,要写入的最大字节])
知识点10:ini_set()
ini_set('open_basedir',DIR);等价于dirname(FILE)
_DIR_是魔术常量,返回当前php文件所在的目录路径,值随着它们在代码中的位置改变而改变
知识点11
注释:
if(isset($_POST['code'])){
$f=fopen("$path/$file",'w+');
fwrite($f,$_POST['code']);
fclose($f);
}
第一行用于检查表单是否提交,并且code字段是否存在;
w+表示文件以读写方式打开,若文件不存在,则创建该文件,若文件存在,打开文件后文件内容会被清空,即文件大小变为0;
第三行中的$_POST['code']包含用户在文本区域中输入的内容。
文件下载漏洞(编辑、删除同理)
?a=down&path=
当等号后面的值不同时,就可以下载其他文件,同理,也可以删除、编辑其他文件。
例如,当访问?a=down&path=./1.txt时,会自动下载1.txt文件
文件使用系统指令删除:
switch($action){
case 'del':
// unlink($file);
$cmd="del $file";
system($cmd);
echo $cmd;
break;
和在cmd下使用一样的原理
dnslog回显
当不知道执行结果时,可以使用?path=del 1.txt | ping 127.0.0.1
例如,我删除了一个文件,但是不会立即反馈给我是否删除成功,此时就可以 | ping dnslog(get subdomain)。
但是也有可能被以下代码过滤掉:
if(is_file($path)){//判断是否是文件
//获得文件名
$file=basename($path);
//获得路径
$path=dirname($path);
}elseif(!is_dir($path)){//判断,不是目录
echo '无效的文件文件路径参数';
}
该文章由番薯小羊卷~和李豆豆喵共同完成。
版权归原作者 李豆豆喵 所有, 如有侵权,请联系我们删除。