前言
php是网络安全学习里必不可少的一环,简单理解php的开发环节能更好的帮助我们去学习php以及其他语言的web漏洞原理
正文
在正常的开发中,文件的功能是必不可少,比如我们在论坛的头像想更改时就涉及到文件的上传等等文件功能。但也会出现漏洞,或者一些bug。这部分是php开发部分的文件上传、删除部分,为啥不写完?感觉有点多。
文件上传代码
在开发中,对于这种功能型的代码一般有两种办法来开发。一种比较复杂,另外一种比较简单。
自己写代码
这种办法是比较复杂、比较费时费力的。甚至在某些情况下是单纯的吃力不讨好。比如你花几个星期开发一个文本编辑器,最后开发出的文本编辑器性能还不怎么样,但是开源社区或者其他地方有符合你最终需求的文本编辑器代码,性能还优秀,这种情况下就真的吃力不讨好了。
PS:在开发时一点要做好技术选型这些,尽量避免重复造轮子
创建一个file_upload.php文件来实现一个简单文件上传功能,先编写一个html页面来方便查看结果,在file_index.php中写入下面的html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Head</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="header">
<a href="index.html" class="logo">
<h1>wushiyiwuzhong</h1>
</a>
<nav class="navbar">
<ul>
<li><a href="#">文件功能导航</a></li>
<li><a href="#">文件上传</a></li>
<li><a href="#">文件下载</a></li>
<li><a href="#">文件删除</a></li>
<li><a href="#">文件读取</a></li>
<li><a href="#">文件写入</a></li>
</ul>
</nav>
</div>
<div class="content">
</div>
</body>
</html>
页面展示效果如下:
接下来我们开始实现简单的文件上传功能,在file_upload.php中编写代码,同样,我们先设计前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Head</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="header">
<a href="index.html" class="logo">
<h1>wushiyiwuzhong</h1>
</a>
<nav class="navbar">
<ul>
<li><a href="file_index.php">文件功能导航</a></li>
<li><a href="file_upload.php">文件上传</a></li>
<li><a href="#">文件下载</a></li>
<li><a href="#">文件删除</a></li>
<li><a href="#">文件读取</a></li>
<li><a href="#">文件写入</a></li>
</ul>
</nav>
</div>
<div class="content">
<h1>文件上传 功能页面</h1>
<!-- 编写form表单上传文件 -->
<form action="" method="post"><!-- action留空,由当前页面处理 -->
<input type="file" name="file"><!-- 文件上传框 -->
<input type="submit" name="上传"><!-- 提交按钮 -->
</form>
</div>
</body>
</html>
页面效果如下:
开始php后端部分的编写,现在前端这些代码只负责让用户上传文件,并不负责验证这个是不是病毒等等有害文件、能不能上传、上传后放在那里。像后面这些功能都是有后端决定的。
前置知识
在PHP中,文件上传功能的实现步骤:
(1)在网页中上传表单,单击“上传”按钮后,选择的文件数据将发送到服务器。
(2)用$_FILES获取上传文件有关的各种信息。
(3)用文件上传处理函数对上传文件进行后续处理。
说明:
(1)如果表单中有文件上传域,则定义表单时必须设置enctype=“multipart/form-data”,且必须为POST方式发送。
(2)限制文件大小可在表单中添加一个隐藏域,该隐藏域必须放在文件上传域之前,否则会设置失效。
若文件上传域的name属性值为upfile,则可以使用$_FILES[‘upfile’]访问文件的有关信息。
相关信息:
$_FILES[‘upfile’][‘name’]; //客户端上传文件的原名称,不包含路径
$_FILES[‘upfile’][‘type’]; //上传文件的MIME类型
$_FILES[‘upfile’][‘tmp_name’]; //已上传文件在服务器端保存的临时文件名,包含路径
$_FILES[‘upfile’][‘error’]; //上传文件出现的错误号,为一个整数
F
I
L
E
S
[
′
u
p
f
i
l
e
′
]
[
′
s
i
z
e
′
]
;
/
/
已上传文件的大小,单位为字节说明:若同时上传多个文件,则上传域的
n
a
m
e
属性值应为
u
p
f
i
l
e
[
]
,
此时
_FILES['upfile']['size']; //已上传文件的大小,单位为字节 说明:若同时上传多个文件,则上传域的name属性值应为upfile[],此时
FILES[′upfile′][′size′];//已上传文件的大小,单位为字节说明:若同时上传多个文件,则上传域的name属性值应为upfile[],此时_FILES变成三维数组,保存第一个文件名的数组元素是$_FILES[‘upfile’][‘name’][0]。
保存上传文件到指定目录
move_uploaded_file(文件原来的路径和文件名,文件的目的路径和文件名);
功能说明:
(1)将上传的文件移动到网站指定目录内,并为它重命名。
(2)检查并确保由第一个参数指定的文件是合法的上传文件(即通过HTTP POST上传机制所上传),这对于网站安全是至关重要的。
下面是具体的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Head</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="header">
<a href="index.html" class="logo">
<h1>wushiyiwuzhong</h1>
</a>
<nav class="navbar">
<ul>
<li><a href="#">文件功能导航</a></li>
<li><a href="#">文件上传</a></li>
<li><a href="#">文件下载</a></li>
<li><a href="#">文件删除</a></li>
<li><a href="#">文件读取</a></li>
<li><a href="#">文件写入</a></li>
</ul>
</nav>
</div>
<div class="content">
<h1>文件上传 功能页面</h1>
<!-- 编写form表单上传文件 -->
<form action="" method="post" enctype="multipart/form-data"><!--enctype进行编码传输-->
<input type="file" name="file"><!--name参数-->
<input type="submit" name="submit" value="提交">
</form>
</div>
</body>
</html>
<?php
//获取上传文件名
@$file_name=$_FILES['file']['name'];
//获取上传文件类型
@$file_type=$_FILES['file']['type'];
//获取上传文件大小
@$file_size=$_FILES['file']['size'];
//获取上传文件临时文件名
@$file_tmpname=$_FILES['file']['tmp_name'];
//获取上传文件是否错误
@$file_error=$_FILES['file']['error'];
echo $file_name."<hr>";
echo $file_type."<hr>";
echo $file_size."<hr>";
echo $file_tmpname."<hr>";
echo $file_error."<hr>";
运行查看效果,如果代码正确的话上传一个文件会打印这个被上传的文件的相关信息
我这里上传一张图片做为测试
点击提交上传之后会打印这种图片相关信息
保存用户上传的文件
现在我们实现当用户上传文件后,比如用户上传了自己的头像后我们的服务器要保存用户的数据。这里我们设置当用户上传文件后将文件保存在当前目录下的upload目录里
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Head</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="header">
<a href="index.html" class="logo">
<h1>wushiyiwuzhong</h1>
</a>
<nav class="navbar">
<ul>
<li><a href="file_index.php">文件功能导航</a></li>
<li><a href="file_upload.php">文件上传</a></li>
<li><a href="#">文件下载</a></li>
<li><a href="#">文件删除</a></li>
<li><a href="#">文件读取</a></li>
<li><a href="#">文件写入</a></li>
</ul>
</nav>
</div>
<div class="content">
<h1>文件上传 功能页面</h1>
<!-- 编写form表单上传文件 -->
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="file"><!-- name是什么,那么后端接受的参数就是什么-->
<input type="submit" name="submit" value="提交">
</form>
</div>
</body>
</html>
<?php
// 检查是否有文件被上传
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['file'])) {
// 指定存放上传文件的目录
$uploadDirectory = "upload/";
// 获取文件相关信息
$fileName = $_FILES['file']['name']; // 文件原名
$fileTmpName = $_FILES['file']['tmp_name']; // 文件临时存放的位置
$fileError = $_FILES['file']['error']; // 错误码
$fileSize = $_FILES['file']['size']; // 文件大小
// 检查目录是否存在
if (!file_exists($uploadDirectory)) {
// 如果不存在则创建目录
mkdir($uploadDirectory, 0755, true);
}
// 检查文件上传过程中是否出错
if ($fileError === 0) {
// 检查文件是否已经存在
if (!file_exists($uploadDirectory . $fileName)) {
// 尝试移动文件到指定目录
if (move_uploaded_file($fileTmpName, $uploadDirectory . $fileName)) {
echo "文件上传成功!";
} else {
echo "文件上传失败,请检查目录权限。";
}
} else {
echo "文件已存在于目标目录中。";
}
} else {
// 错误处理
echo "上传过程中出错,错误码:" . $fileError;
}
} else {
echo "请通过有效表单提交文件。";
}
代码运行效果如下:
上传文件进行测试,看是不是保存在upload目录下
打开文件夹,确认是不是真的成功了
到这里成功实现了文件上传功能
PS:此时我们就完成了简单的文件上传功能的实现,但是现在的代码是没有任何过滤的,攻击者可以通过上传木马、webshell等等恶意文件来控制我们的服务器。
情景假设:
我是一名黑客,我在目标网站找到了上传漏洞。我现在先上传一个php的webshell到网站上去
现在我们使用蚁剑去连,连接编码为base64,密码为123
PS:当时写代码的时候忘记写回显了。。。。。。不过实战中还是有很多方法可以知道上传路径的
通过第三方组件进行文件上传
在实际的开发中我们可能会节省重复造轮子的时间而去使用其他第三方开发的第三方组件来快速实现我们需要的功能。下面的就是一个第三方组件的例子
(演示使用的第三方组件下载地址)[https://github.com/fex-team/ueditor/releases/tag/v1.4.3.3]
下载后解压到对应的网站目录
然后在代码里引入该第三方组件,在html代码里直接加入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>第三方组件的文件上传</title>
<link rel="stylesheet" href="index.css">
<!-- 集成 UEditor 的 CSS 文件 -->
<link href="ueditor/themes/default/css/ueditor.min.css" type="text/css" rel="stylesheet">
<!-- UEditor 配置文件 -->
<script type="text/javascript" charset="utf-8" src="ueditor/ueditor.config.js"></script>
<!-- UEditor 编辑器源码文件 -->
<script type="text/javascript" charset="utf-8" src="ueditor/ueditor.all.min.js"></script>
</head>
<body>
<a href="index.html" class="logo">
<h1>wushiyiwuzhong</h1>
</a>
<nav class="navbar">
<ul>
<li><a href="file_index.php">文件功能导航</a></li>
<li><a href="file_upload.php">文件上传</a></li>
<li><a href="#">文件下载</a></li>
<li><a href="#">文件删除</a></li>
<li><a href="#">文件读取</a></li>
<li><a href="#">文件写入</a></li>
</ul>
</nav>
</div>
<div class="content">
<h1>文件上传 功能页面</h1>
<!-- 编写form表单上传文件 -->
<form action="upload.php" method="post" enctype="multipart/form-data">
<!-- 初始化 UEditor 编辑器的容器 -->
<script id="editor" name="editor" type="text/plain" style="width:1024px;height:500px;"></script>
<input type="file" name="file"><!-- 文件上传的input -->
<input type="submit" name="submit" value="提交">
</form>
</div>
<script type="text/javascript">
// 实例化 UEditor
var ue = UE.getEditor('editor');
</script>
</body>
</html>
运行效果:
进行文件上传的测试
可以看到第三方组件过滤了简单的恶意文件
总结
到此文件上传漏洞的大致原理和简单利用就结束了,自己开发的代码漏洞出在自己开发的部分,而如果引用第三方组件,那么漏洞点在第三方组件。至于过滤、绕过这些都是后面的事情