知识点:
- PHP文件管理 - 下载 | 删除功能实现
- PHP文件管理 - 编辑 | 包含功能实现
PHP文件操作安全
文件包含、文件上传、文件下载、文件删除、文件写入、文件遍历
文件包含指的是PHP文件包含其他文件,通过include、require将一个PHP文件插入到另一个文件中
require在脚本执行失败的时候会抛出错误并停止执行脚本
include在脚本执行失败的时候仅会产生警告并继续执行脚本
架构
1、传统的文件上传方式是将文件直接上传到服务器,保存在本地磁盘中
2、借助云产品OSS对象存储区存储文件[ 使用云存储的话,所有脚本都不会被执行 ] [ 云存储可能会出现AccessKey泄露风险 ]
3、把文件上传到其他域名
例如:www.xiaodi8.com -> upload.xiaodi8.com
OSS存储演示略。
注意:在配置OSS的时候,需要配置储存桶(Bucket)公共读写权限
如果没有对access id\key进行妥善处理的话,通过f12可能会出现accesskey泄露
案例演示:文件管理模块 - 架构后续 - 编辑 | 删除 | 下载 | 包含
相关知识
文件包含相关函数
include()在错误发生后继续执行脚本
require()在错误发生后脚本停止执行
include_once()如果已经包含则不再执行
require_once()如果已经包含则不再执行
文件包含可能存在的漏洞:如果包含文件的语句中存在变量:例如include ($_GET['page']);在访问?page=可以是任何文件,容易被上传木马
例如:如果网站禁止了php文件的上传,但是我们将脚本写入到txt文件中,然后使用有文件包含漏洞的网页中通过修改page=后面的文件名执行脚本
我直接使用了upload.php来做这个实验,因为include语句没有传入参数,所以这里有一个报错(第三行是我的include语句)
我在1.txt文件中写入了phpinfo(),这时候引入的文件被作为脚本执行成功了
包含的文件会在脚本执行之前被执行
文件包含的优点:可以进行文件调用,例如,如果不设置文件包含,在访问upload.php之前需要先访问upload.html,但如果在upload.php中include 'upload.html'后就可以直接访问upload.php了。
实现文件编辑、删除、下载、包含功能的具体步骤
最终代码:
<?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 != '.' && $file_name != '..'){
$file_path = "$path/$file_name";
$file_type = filetype($file_path);
}
$list[$file_type][] = array( //$file_type = dir 和 file $list['dir'] 和 $list['file']
'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)), //获取文件时间并存储键值file_path
);
}
closedir($hd);
return $list;
}
$list=getlist($path);
//接受方法 判断是怎么操作
//echo $action;
switch ($action){
case 'del':
unlink($file);
//$cmd="del $file";
//system($cmd);
//echo $cmd;
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 $v): ?>
<tr>
<td><img src="./img/list.png" width="20" height="20"></td>
<td><?php echo $v['file_name']?></td>
<td><?php echo $v['file_time']?></td>
<td>-</td>
<td><?php echo $v['file_path']?></td>
<td><a href="?path=<?php echo $v['file_path']?>">打开</a></td>
</tr>
<?php endforeach;?>
<?php foreach ($list['file'] as $v): ?>
<tr>
<td><img src="./img/file.png" width="20" height="20"></td>
<td><?php echo $v['file_name']?></td>
<td><?php echo $v['file_time']?></td>
<td><?php echo $v['file_size']?></td>
<td><?php echo $v['file_path']?></td>
<td>
<a href="?a=edit&path=<?php echo $v['file_path']?>">编辑</a>
<a href="?a=down&path=<?php echo $v['file_path']?>">下载</a>
<a href="?a=del&path=<?php echo $v['file_path']?>">删除</a>
</td>
</tr>
<?php endforeach;?>
</table>
步骤:
首先写html框架
<?php
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<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="./image/文件夹.png" width="20" height="20"></td>
<!-- 这里按需写路径 -->
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
上面的代码只能显示出一个html页面,然后要开始实现显示目录下文件和文件夹的功能以及点击文件夹可以打开文件夹并显示出文件夹下文件夹和文件功能
//获取路径参数,默认是当前目录
$path = $_GET['path'] ?? './';
$path = isset($_GET['path'])? $_GET['path'] : '.'; //判断在path参数中是否有路径参数
//判断是文件或者是目录
if(if_file($_GET['path']){ //如果path指向的是文件
$file = basename($path);
$path = dirname($path);
}elseif(!is_dir($path)){
//如果不是文件也不是目录,则输出吃瓜
echo '吃瓜';
}
以上网页还不能显示出文件,然后下面将显示文件的功能写成一个函数
大致上和上节课写的功能类似。这里进了一些拓展
function getlist($path){
$hd = opendir($path); // 打开文件
while(($file_name = readdir($hd))!==false){ //逐行读取路径下的文件并赋值到变量$file_name中
if($file_name != '.' && $file_name != '..'){
$file_path = "$path/$file_name";
$file_type = filetype($file_path);
}
//上面的if语句是将文件的名字与类型提取出来
$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这里是一个二维数组,分别为不同文件类型储存对应的信息
同时php的数组的存储结构是键值对类型的,也就是类似于Python中的字典类型的格式
如果没有指定键名默认从0开始用数组作为键
参考链接:https://www.php.net/manual/zh/language.types.array.php //官方文档,貌似需要爬墙
https://www.runoob.com/php/php-arrays.html //菜鸟教程,不要爬墙
filemtiem:函数 返回文件上次被修改时间
foreach:函数 专用于数组和对象的循环
例如:
foreach (iterable_expression as $value) statement foreach (iterable_expression as $key => $value) statement //参考链接:http://php.net/manual/zh/control-structures.foreach.php //需要爬墙 //https://www.runoob.com/php/php-looping-for.html //不用爬墙
然后需要开始实现对文件操作的功能了,但是在这之前需要将文件列表在网页中显示出来,现在要对html代码进行操作了
//文件列表我们需要输出到第二个<tr>标签中,这里我们就用上面提到的foreach读取$list中的内容 然后记得先调用一下getlist函数 在php中
//因为有dir和file两种文件类型,所以这里需要两个foreach循环
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<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 $v): ?>
<tr>
<td><img src="image/文件夹.png" width="20" height="20"></td>
<td><?php echo $v['file_name'] ?></td>
<td><?php echo $v['file_time'] ?></td>
<td>-</td>
<!-- 文件夹没有大小-->
<td><?php echo $v['file_path'] ?></td>
<td><a href="?path=<?php echo $v['file_path'] ?>">打开</a></td>
<!-- 这里点击之后发起一个get请求,然后需要包含需要请求的文件夹的路径,这里直接从上面获取到的文件路径拼接-->
</tr>
<?php endforeach;?>
<!-- 遍历输出文件类型-->
<?php foreach($list['file'] as $v): ?>
<tr>
<td><img src="image/文件.png" width="20" height="20"></td>
<td><?php echo $v['file_name'] ?></td>
<td><?php echo $v['file_time'] ?></td>
<td><?php echo $v['file_size']?></td>
<td><?php echo $v['file_path'] ?></td>
<td>
<a href="?a=edit&path=<?php echo $v['file_path'] ?>">编辑</a>
<a href="?a=down&path=<?php echo $v['file_path'] ?>">下载</a>
<a href="?a=del&path=<?php echo $v['file_path'] ?>">删除</a>
</td>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
OK现在已经可以在网页中看到完整的页面了,现在还需要实现文件的编辑、下载、删除功能
在进行下面的代码之前需要了解1个php中的语法
switch语句(多分支选择语句)
通过switch接收一个参数,然后通过下面case值的不是确定执行不同语句,在这里就是通过接收不同的关键字然后执行对应的语句对文件进行编辑、删除、下载操作
例如:
$a = 'goodbye'; switch($a){ case 'hello' | 'Hello': echo 'Speak Hello'; break; case 'goodbye' | 'Goodbye': echo 'Speak Goodbye'; break; default: echo 'OK'; }
这里的话需要完善一下php中的代码
首先为了避免像昨天那种跨目录访问,这里使用一个php中的一种语句修改php.ini中的open_basedir的配置
ini_set('open_basedir',__DIR__);
然后声明一个变量用于接收值区分用户对文件的操作
$action = isset($_GET['a']) ? $_GET['a'] : '';
switch($action){
case 'del':
break;
case 'down':
break;
case 'edit':
break;
}
//首先写出swithc框架,然后开始写功能
//del
case 'del':
unlike($path.'/'.$file);
break;
/*
推荐使用unlike函数进行删除,也可以使用windows或者Linux的删除命令删除,但是,使用系统删除命令可能会导致被恶意利用删除系统中其他文件
使用系统命令删除的写法如下:
$cmd = "del $file"
system($cmd) //通过system函数执行系统命令
*/
//down
/* 下载操作是通过对头文件进行修改实现的 */
case 'down':
header('Content-type: application/octet-stream');
// 设置响应内容的 MIME 类型为二进制流
header("Content-Disposition: attachment; filename=\"".$file."\"");
// attachment:强制作为附件下载
// filename="xxx":指定下载时显示的文件名(变量 $file 的值)
header("Content-Length:".filesize($file));
// 声明文件大小
readfile($file);
// 读取文件内容并直接输出到响应流
break;
//编辑文件
// 这里是将文件内容加入到一个可编辑表单
case 'edit':
$content = file_get_contents($file); //将文件中的内容读取到变量中
echo '<from name = "form1" method="post" action = "">';
echo '文件名:'.$file.'<br>';
echo '文件内容:<br>';
echo '<textarea>'.$content.'</textarea>';
echo '<input type="submit" name = "submit" value="提交">';
echo '</form>';
//这里也可以使用另一种方法写
echo <<<HTML
<form name="form1" method="post" action="">
文件名:{$file}<br>
文件内容:<br>
<textarea name="code" style="resize:none;" rows="100" cols="100">{$content}</textarea><br>
<input type="submit" name="submit" id="submit" value="提交">
</form>
HTML;
//将文件重新写入磁盘
if(isset($_POST['code'])){ //检测post请求中的code字段
$f = fopen($file, 'w+'); //以读写模式打开文件,如果文件存在则清空原文件
fwrite($f, $_POST['code']); //将从post请求中code字段中的内容写到原文件中
fclose($f); //关闭文件写入磁盘
}
实现就在以上,然后就是可能会存在的一些漏洞
在编辑、下载中存在的漏洞
在url中没有做限制,如果修改url后面的字段,则可以下载服务器中的任意文件
然后还可以通过字符串拼接执行一下代码
然后我的代码好像有写问题,好像出现和更多问题,比如我在删除我的d盘下的一个txt文件的时候,直接显示出了d盘下的所有文件
然后就这样吧,听又听不懂学又学不会