文件上传漏洞-upload靶场1-2关 通过笔记(区分前段验证和后端验证)
前言
upload是一个文件上传的专用靶场,搭设也非常简单,只需要把相关源码文件放到apache的网站目录下即可使用,或者去github下载一键绿化包进行安装链接如下:
[Releases · c0ny1/upload-labs (github.com)]
下载后按照使用说明安装即可,在安装该靶场时最好安装在虚拟机中,在此也不做过多的解析了,如有不明白可以私聊我。
upload靶场能帮助我们,实践复现文件上传漏洞,帮助我们更好的学习该漏洞形成的原因,该工具一共20关,除了第一关是前段验证,其他19关都是关于后端验证。
upload的难度也是逐步上升,第十关是一个分水岭,后面的内容也是非常硬核
在开始挑战关卡前,我们先了解下什么才是一次成功的攻击:
- webshell要成功的上传到服务器中
- 要知道webshell在服务器中的路径
- 上传的webshell能被正常解析
只有满足这三条条件,才能算一次成功的攻击。
upload第一关(JS验证)
javascript是一个前端常用的语言,用它写的代码在一般情况都是属于前端,不过当你使用node.js为服务器环境时候,javascript也可以作为后端语言来使用。
判断思路
在上传webshell时,我们并不知道该网站使用了那些方法来验证上传的文件,所以我们要从分析源码、抓取流量、随意上传一个文件等方法去分析它的验证方式,以便找到适合的方法去进行攻击。
第一种方法:分析源码
html
<h3>任务</h3>
<p>上传一个<code>webshell</code>到服务器。</p>
</li>
<li>
<h3>上传区</h3>
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>
以上源码是从该网站中找到的,其中有一个form表单和一个js脚本较为关键,在form表单中又有两个input标签
form表单有三个属性分别是:
- enctype属性:表单中用于指定在提交表单数据时使用的编码类型
- 它的值为multipart/form-data:
- 表单数据会被分割为多个部分(multipart),每个部分都包含一个表单字段的数据。这种编码类型可以同时传输文本数据和二进制数据,比如上传的文件
- 它的值为multipart/form-data:
- mothod属性:表示这个表单接收数据的方法
- 它的值为post:
- 使用post方法来接收数据
- 它的值为post:
- onsubmit属性:用于指定在提交表单时执行的 JavaScript 代码
- 它的值为return checkFile():
- 用来指定在表单提交之前执行的 JavaScript 函数
checkFile()
- 用来指定在表单提交之前执行的 JavaScript 函数
- 它的值为return checkFile():
input标签解析:
-
- 该代码可以用于创建一个文件上传的输入字段,允许用户选择本地计算机上的文件并上传到服务器。它的表示name值为upload_file
-
- 创建一个提交按钮,并在用户界面中显示 "上传" 文本。当用户点击此按钮时,表单的提交动作将被触发,将表单数据发送到服务器。
从下面的js代码中我们发现了 checkFile()
的函数,它的第一句,就是使用 document.getElementsByName()
方法来获取带有 name="upload_file"
属性的元素,并通过索引 [0]
来获取第一个匹配的元素,然后使用 .value
属性获取其值,如果没有上传文件,或上传空文件则提示请选择要上传的文件。
在下半段js代码中,首先限定了上传文件后缀,只能上传这三种后缀的文件,又通过调用 file.lastIndexOf(".")
方法,可以获取文件名中最后一个点(.)的索引。然后,通过调用 file.substring()
方法,可以从该索引位置开始截取字符串,得到文件名中的扩展名进行判断,如果上传的文件不符合要求,则提示用户上传正确的格式的文件
通过这一段代码的分析,我们就可以判断它是一个前端验证的文件上传,确定了是前段验证,那么问题就简单了,所谓前端验证都是纸老虎可不是开玩笑的哈哈。
js代码函数分析
document.getElementsByName()
它是JavaScript 中的一个 DOM 方法,用于根据元素的
name
属性获取文档中所有匹配的元素集合。该方法接收一个字符串参数,表示要获取的元素的
name
属性的值,返回一个类数组对象,包含所有与指定name
属性匹配的元素。
对象.lastIndexOf()
- 它是 JavaScript 字符串的方法,用于获取字符串中最后一个出现的指定字符或子字符串的索引位置。
对象.substring()
- 它是 JavaScript 字符串的方法,用于获取字符串中指定索引位置之间的子字符串。
第二种方法抓包
首先我们打开Burp抓取一个upload上传的包
把这个包放到repeater(重放器)中进行分析
在此处我们发现,我们上传了一个名为123.jpg的文件,
Content-Type: image/jpeg 表示我们发送的是一个图片文件
在此我们对我们上传的文件后缀名进行更改
- 如果是前端验证,更改文件后缀名后依旧能正常发送,因为它已经经过了验证。
在修改文件后缀为asp后,也是成功发送给后端服务器,这也能证明它只使用前端验证
第三种方法:随意上传文件分析
这种方法是最为快捷的,但是判断是否是前段验证或后端验证,这种方法就非常考验我们的经验。
如果是前端验证,它的报错响应速度会非常快,因为它一直在你本地的前端,如果是后端服务器验证,相反它的响应速度就会有一定的延迟,具体还得看网络情况,为什么会有延迟呢,我统计了一下情况供大家参考:
-
网络延迟:前端验证是在客户端执行的,而后端验证涉及将数据发送到服务器并等待服务器响应。这意味着在进行后端验证时,会出现网络延迟的情况,这可能会导致更长的验证时间。
-
请求处理时间:后端验证涉及将数据发送到服务器上的处理程序,然后由服务器上的应用程序进行验证并返回响应。与前端验证相比,服务器的请求处理时间会更长,因为服务器可能涉及其他任务、处理其他请求或执行其他复杂的验证逻辑。
-
数据传输量:后端验证涉及将数据上传到服务器进行处理,这需要将数据通过网络传输到服务器,这可能会涉及较大的数据量,因此相对于前端验证来说可能更慢。
攻击思路
前端验证最大的缺点,就是它可以被客户端篡改,这文件就在我本地计算机中,我想咋改就咋改,而后端服务器就比较麻烦了,在后面也会详细的去介绍如何成功上传。
前段验证的攻击方法:
1、直接修改源码:
使用浏览器自带的检查工具,找到前端验证的代码,直接删除。
2、使用bp抓包工具:
直接在抓包工具中修改文件后缀名,因为它已经通过了前段的验证,这时候修改对上传的文件毫无影响。
3、使用br禁用网页的js功能
4、使用浏览器自带的禁用js功能(火狐浏览器)
1.在火狐浏览器的url界面中输入 about:config 进入高级设置界面
找到javascript.enabled
将javascript.enabled的true改为false
同样也能达到br抓包禁用的效果,不过在这里不建议使用浏览器的禁用方法,它会把所有的js全部都禁用掉,一些正常的js也会变层无法使用。
最后上传一个简单的webshell脚本,来测试是否成功。
已经成功获取的网站权限!
upload第二关(MIME验证)
什么是MIME?
MIME(Multipurpose Internet Mail Extensions)是一种用于标识文件类型的标准。它是在互联网上发送邮件和其他数据的一种常用方式。
MIME 类型由两部分组成:主类型和子类型,之间用斜杠(/)分隔。主类型表示文件的大类别,而子类型表示具体的文件类型。例子如下:
- 文本文件:
text/plain
- HTML 文件:
text/html
- JPEG 图像文件:
image/jpeg
- PNG 图像文件:
image/png
- JSON 数据文件:
application/json
- PDF 文档:
application/pdf
MIME 类型主要用于在 HTTP 协议中指定传输的数据类型,并且还在其他应用程序中进行使用,例如电子邮件、文件上传等。通过使用正确的 MIME 类型,服务器和客户端能够理解传输的数据类型,并进行相应的解析和处理。
MIME 类型还可用于指定数据的字符编码、语言和其他相关信息,这些信息用于确保数据的正确显示和处理。
在 HTTP 协议中,MIME 类型常用于请求报文和响应报文的 Content-Type
头字段中,用于指定将要发送或接收的数据的类型。
判断思路
首先还是随意上传一个文件,来判断一下它究竟是前段验证,还是后端服务器验证。
查看源码发现,from表单也有一个onsubmit属性和第一关的一模一样,难道它也是前段验证?抱着怀疑的我们按第一关的操作,把这个属性删除在尝试上传,看看是否能成功。
删除该属性后,尝试上传文件,任然提示文件类型不正确,此时我们就拿出最终绝招,用bp抓个数据包,也按照第一关的方法,去尝试是否能成功上传。
我们上传一个jpeg的图片文件,经过bp后把后缀名改为php,然后放行,看看是否能够成功上传。
居然成功上传了,难道它也是前段验证?那为什么我们删除验证的js脚本函数后,又不能成功上传呢?带着这些疑问,我又开始抓包研究。
在此我有了发现,在第一关我开启Bp抓包的时候,上传一个任意文件,如果上传的文件类型错误,bp就抓不到数据包,而是在网页中直接弹出一个警告框
而到了第二关,我上传一个随意文件时,Bp能抓到一个请求包,并在Content-Type中显示了当前上传文件的类型,text/plain,在此我就可以判断第二关的验证是后端服务器,下面是我画的一张前端验证、后端验证的流程图。
总结一下思路,
- 如果是前段验证,当bp开启抓包的时候,文件类型错误,将抓不到任何请求包。
- 如果是服务器后端验证,无论是否成功,Bp都能抓到对应的响应包。
- 这也就是前段验证和后端验证的最大区别。
攻击思路:
当我们分清楚前端验证和后端验证的区别后,就可以开始分析如何在后端验证的情况,上传一个webshell。
在判断思路中,我们使用第一关的攻击思路,上传一个图片格式的文件,用bp抓包并修改文件后缀,能够成功将webshell上传到站点,但是使用其他格式缺总是失败,而且我们还发现上传不同类型的文件,content-Type的值也是不一样的,这一点证明的后端服务器,可能是开启了一个白名单模式,只有指定的文件类型才能成功上传,那么我们是否可以用bp抓包,对content_type的值进行篡改,来实现攻破后端服务器验证的关卡呢?心动不如行动,我们一起来实践一下。
首先我们先上传一个webshell,后缀为php,在用bp抓住这个包,使用repeter进行验证我们的想法。
我们只修改Content-type的值尝试,能不能正常上传。
果然和我想的一样,只要我们修改了Content-type的值为图片类型,我们就可以上传任意文件,包括webshell。再来尝试上传的webshell是否能正常解析。
使用post方法进行传参,能够成功执行phpinfo();函数,这叫代表我们已经成功闯过了第二关,也就初步了掌握了文件上传的基础技巧,后面还有18个关卡,我们还要继续努力。
后端源码分析
php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
在解析源码前,我们先看看这串源码中,使用了哪些函数
isset()
:用于检查变量或表单字段是否已设置和存在。file_exists()
:用于检查指定文件或目录是否存在。$_POST
:是一个包含通过 POST 方法提交的表单字段和值的关联数组。在这里,$_POST['submit']
用于检查表单是否通过提交按钮进行了提交。$_FILES
:是一个包含通过文件上传的文件信息的关联数组。在这里,$_FILES['upload_file']
包含了关于上传文件的信息,如文件名、临时路径、文件大小、文件类型等。move_uploaded_file()
:用于将上传的文件移动到目标位置。
以下是对代码的解释和说明:
在这段代码中,首先定义了一个布尔变量 $is_upload
并初始化为 false
,用于表示文件是否成功上传。还定义了一个变量 $msg
,用于存储上传过程中的提示信息。
通过使用 isset($_POST['submit'])
检查表单是否通过提交按钮进行了提交。
使用 file_exists(UPLOAD_PATH)
来检查指定的上传目录是否存在。UPLOAD_PATH
是一个用于存储上传文件的目录路径。
使用 $_FILES['upload_file']['type']
来获取上传文件的 MIME 类型(例如:image/jpeg
、image/png
、image/gif
)。
通过使用 $_FILES['upload_file']['tmp_name']
获取上传文件的临时路径。
通过使用 $_FILES['upload_file']['name']
和 UPLOAD_PATH
来创建存储上传文件的目标路径。
使用 move_uploaded_file($temp_file, $img_path)
将上传的临时文件移动到目标路径。
当移动文件成功时,将 $is_upload
设置为 true
,如果移动文件失败,则将 $msg
设置为 '上传出错!'。
如果上传文件的类型不是 'image/jpeg'、'image/png' 或 'image/gif',将 $msg
设置为 '文件类型不正确,请重新上传!'。
如果上传目录不存在,将 $msg
设置为 UPLOAD_PATH 所指定的目录路径后面跟上 '文件夹不存在,请手工创建!'。
这个代码片段主要用于处理文件上传过程,并设置content-type的值为image/jpeg、png、gif,也就是说只要请求头中的content-type字段的值是上诉的文件类型,都可以成功上传。
文件移动到目标路径。
当移动文件成功时,将 $is_upload
设置为 true
,如果移动文件失败,则将 $msg
设置为 '上传出错!'。
如果上传文件的类型不是 'image/jpeg'、'image/png' 或 'image/gif',将 $msg
设置为 '文件类型不正确,请重新上传!'。
如果上传目录不存在,将 $msg
设置为 UPLOAD_PATH 所指定的目录路径后面跟上 '文件夹不存在,请手工创建!'。
这个代码片段主要用于处理文件上传过程,并设置content-type的值为image/jpeg、png、gif,也就是说只要请求头中的content-type字段的值是上诉的文件类型,都可以成功上传。