安全开发-PHP-文件管理
token模块核心
注意:
1.浏览器的插件可能会影响token的使用,例如插件在你发出请求后重新刷新请求,导致数据包永远是不唯一的。
2.token使用的是session的会话维持
3.token是唯一的。
我的个人理解:
1.session的会话维持的特性,加入session的值在一定时间内是恒定不变的
2.利用生成算法等方式,创建一个唯一的token,并且传递给表单,让表单隐性提交过来
3.当表单提交的token值与服务器对不上时(!==),服务器更新token值。
4.当表单正确时,将token再次更新,确保数据包的唯一性。
作用:确保数据包的唯一性,相当于强制用户必须使用当前页面登录。
(反正每一次访问都会刷新token,并且想要破解token几乎不可能,除非程序员的设计有问题等。)
<?php session_start();
$token = $_POST['token'] ?? '';
if ($token !== $_SESSION['token']) { header('HTTP/1.1 403 Forbidden'); $_SESSION['token'] = bin2hex(random_bytes(16)); echo 'Access denied'; exit; }else{ $_SESSION['token'] = bin2hex(random_bytes(16)); if($_POST['username']=='admin' && $_POST['password']=='123456'){ echo '登录成功!'; echo '你是管理员可以访问文件管理页面!'; }else{ echo '登录失败!'; } }
|
PHP文件管理
目的:为后期代码审计做铺垫。
界面设计
前端部分
<!DOCTYPE html> <html>
<head> <meta charset="utf-8"> <title>文件列表</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <style type="text/css"> ul { list-style: none; padding: 0; margin: 0; }
li { margin-bottom: 10px; }
i { margin-right: 10px; }
a { text-decoration: none; color: #333; } </style> </head>
<body> <h1>当前目录下的文件列表</h1> <ul></ul> </body> </html>
|
后端部分
<?php
$dir = $_GET['path'] ?? './';
function filelist($dir) { if ($dh = opendir($dir)) { while (($file = readdir($dh)) !== false) { if (is_dir($file)) { echo "<li><i class='fa fa-folder'></i> <a href='?path=$file'>" . $file . '</a></li>'; } else { echo '<li><i class="fa fa-file"></i> <a href="#">' . $file . '</a></li>'; } } } }
filelist($dir);
function del($file) { if (!is_dir($file)) { unlink($file); echo "<script>alert('删除成功')</script>"; } }
if (isset($_GET['del'])) { del($_GET['del']); }
function down($filepath) { $fileName = basename($filepath); header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=\"" . $fileName . "\""); header("Content-Length: " . filesize($filepath)); readfile($filepath); }
if (isset($_GET['down'])) { down($_GET['down']); } ?>
|
漏洞
php.ini文件: 如果不慎关闭这个设置,会导致全局目录遍历漏洞出现。导致本地的所有文件都会被攻击者浏览和下载。 关闭下面设置:形成文件遍历漏洞 open_basedir
防止被翻目录的方法:使用代码内语法限制,或者打开上面的open_basedir对访问目录进行限制
|
黑名单+白名单+文件类型限制
黑名单白名单
黑名单:写好不能上传的文件类型。
白名单:指定好能上传的文件类型(推荐)。
$black_ext = array('php','aps','jsp','aspx','js');
$chaifeng = explode('.',$_POST['file']['name']);
$exts = end($chaifeng);
if(is_array($exts,$black_ext)){ echo '非法后缀文件'; } else { move_uploaded_file($tmp_name,'upload/'.$name); }
$allow_ext = array('php','aps','jsp','aspx','js');
$chaifeng = explode('.',$_POST['file']['name']);
$exts = end($chaifeng);
if(is_array($exts,$allow_ext)){ move_uploaded_file($tmp_name,'upload/'.$name); } else { echo '非法后缀文件'; }
|
文件类型限制
下面数数据包
POST /PHPStorm/demo02-file/upload.php HTTP/1.1 Host: localhost:63342 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*
|
经过观察,可以发现Content-Type指定上传文件的文件类型是image/jpeg。
安全风险:改下文件类型的数据包就可以了。需要验证逻辑严谨些才能显得安全些。
$type = $_POST['file']['type'];
$allow_type=array('image/png','image/jpg','image/jpeg','image/gif'); if(in_array($type,$allow_type)){ move_uploaded_file($tmp_name,'upload/'.$name); echo '<script>alert("上传成功")</script>'; }else{ echo '非法文件类型';
}
|
文件包含:
include() 在错误发生后脚本继续执行 功能:'包含'哪个文件就执行那个文件 把任意一个文件调用过来执行。 => 任意脚本执行漏洞:如下 include($_GET['page']); require() 在错误发生后脚本停止执行 include_once() 如果已经包含,则不再执行 require_once() 如果已经包含,则不再执行
|
文件删除:
unlink() 文件删除函数
调用命令删除:system shell_exec exec等
php调用WindowsCMD删除文件 system('del 1.txt'); 安全问题:任意命令执行漏洞。
|
文件下载:
修改HTTP头实现文件读取解析下载:
header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=""); header("Content-Length: " . filesize($file)); readfile($file);
|
文件编辑:
1、file_get_contents() 读取文件内容
2、fopen() fread() 文件打开读入
文件编辑下载删除实现
switch ($action){ case 'del': echo "<script>console.log('$fname')</script>"; unlink("$path/$fname"); header("Location: http://127.0.0.1:8028/all_file_read-x.php?path=$path"); echo "<script>window.location.href='http://127.0.0.1:8028/all_file_read-x.php?path=$path'</script>"; break; case 'down': header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment;filename=\"".$fname."\""); header("Content-Length: ".filesize($fname)); break; case 'edit': $content = file_get_contents("$path/$fname"); echo '<form name="form1" method="post" action="all_file_read-x.php">'; echo "文件名:".$fname."<br>"; echo "文件内容:<br>"; echo '<textarea name="codes" style="resize:none;" rows="100" cols="100"">'.$content.'</textarea><br>'; echo '<input type="submit" name="submit" id="submit" value="提交">'; echo "<input type='hidden' value='$path/$fname' name='pf'>"; echo '</form>'; break; } if (isset($_POST['submit'])){ if(isset($_POST['codes'])){ file_put_contents($_POST['pf'],$_POST['codes']); } }
|