又拍云上传文件漏洞

背景

客服小姐姐突然告诉我,我们网站域名下有一段带颜色的视频。比较奇怪,网站上好像没有开放用户上传MP4的口。排查下来发现是又拍的一个 文件上传漏洞(其实也怪自己没仔细读文档),找了公司对接的又拍售后给到的答复大概意思是 影响大无法修改

知识扩展

上传漏洞定义

文件上传漏洞是指用户上传功能没有做好检验或者过滤不严,用户上传了可执行脚本或者未经约定的文件类型。以往的定义中此类漏洞要一直完成到提权才算一次完整的渗透注入,个人认为只要用户行为与我们约定的行为相违背就可以称为一次成功的操作不一定是需要提权。拿本次事件举例,我们使用CDN并没有提权一说但是公司可能面临管局约谈的风险,并且攻击者达到了自己的目的。

常见手段

限制方法 绕过方法
服务端设置blaklist 寻找list以外的扩展名别名或者混淆大小写
filename后缀白名单 双重后缀 xxx.png.php
MIME类型检测 修改上传http包content-type值
头文件内容检测 修改头部那几个字节让它看着像正常的

又拍上传漏洞复现

我们先计算出form api 需要的 policyauthorization

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
<?php
function sign($key, $secret, $method, $uri, $date, $policy=null, $md5=null)
{
$elems = array();
foreach (array($method, $uri, $date, $policy) as $v)
{
if ($v)
{
$elems[] = $v;
}
}
$value = implode('&', $elems);
//echo $value."\n";
$sign = base64_encode(hash_hmac('sha1', $value, $secret, true));
return 'UPYUN ' . $key . ':' . $sign;
}
function main()
{
$key = '操作员名';
$secret = '密码';
$uri = '/服务空间';
$method = 'POST';
$date = gmdate('D, d M Y H:i:s \G\M\T');
$md = md5($secret);
$mybucket = "phpzjj"; //空间名
$mytime = time() + 600;
$mykey = '/opinion/1.png'; //详情看官方文档
$allow = 'jpg,jpeg,png';//文件类型限制
$limit = 1024000 * 3;//大小限制
$leng = "0,{$limit}";
$option = array('x-upyun-meta-ttl'=>1,'bucket'=>$mybucket,'expiration'=>$mytime,'save-key'=>$mykey,'allow-file-type'=>$allow,'content-length-range'=>$leng,'date'=>$date);
$option = json_encode($option,JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$policy = base64_encode($option);
// 上传,处理,内容识别有存储
echo sign($key, md5($secret), $method, $uri, $date,$policy) . "\n";
echo $policy;
}
main();

然后用 CURL 上传不同的文件

1
2
3
4
5
curl http://v0.api.upyun.com/phpzjj \
-F authorization="UPYUN blogtest:JxWvy+4Wi/THxzpMzW2v6U/vSxg=
" \
-F file=@黑产 \
-F policy=eyJ4LXVweXVuLW1ldGEtdHRsIjoxLCJidWNrZXQiOiJwaHB6amoiLCJleHBpcmF0aW9uIjoxNjU5MjUyMTM1LCJzYXZlLWtleSI6Ii9vcGluaW9uLzEucG5nIiwiYWxsb3ctZmlsZS10eXBlIjoianBnLGpwZWcscG5nIiwiY29udGVudC1sZW5ndGgtcmFuZ2UiOiIwLDMwNzIwMDAiLCJkYXRlIjoiU3VuLCAzMSBKdWwgMjAyMiAwNzoxMjoxNSBHTVQifQ==

正常情况下


符合预期

传一个html试试

符合预期

我们把html换成jpg再看下

已经上传成功并且在又拍的控制台也识别为了html文件(浏览器打开也是html)

当时黑产也是利用这种方式给我们挂了个小黄页,打开这个html文件发现不仅是我们这样的小站,像小红书这样的大站也没幸免 m3u8的播放list就放他们的CND上

这里我找过又拍的企业对接技术,他告诉我们是通过filename的方式来限制文件上传类型的,表示理解但是,既然是filename区分我也确实限制死了文件后缀类型。为什么去掉后缀依然能上传???

不过又拍也是积极响应修改了文档并且增加了 file-ext-required 作为弥补措施

我事后也仔细阅读了又拍的文档

https://help.upyun.com/knowledge-base/form_api/#_2

好的,严格上说起来除了无后缀可上传外其他的好像是我们的问题 :-(

修复建议

对于这个只需要传图的业务场景修复方案其实很简单

  1. 走后端上传,先服务器判断下上传类型 (我都用form api干嘛还要传到自己服务器?
  2. 顺便加个图片处理参数比如 x-gmkerl-quality 又拍会去强验证是否是一张图片(推荐)
  3. 只让上传一种图片格式并且写死content-type

总结

事后我对比其他做CND的厂商,他们的做法其实大体和又拍差不多。唯一不同的是他们有些策略降低了程序员对安全方面的心智负担,个人认为在做一个需求时除非对安全方面知识腌制入味要不很难从文档中读出什么影响安全的猫腻,特别是我这种蹩脚的三流野生PHPER。

oss 测试

1
2
3
policy

{"expiration":"2022-07-31T15:47:50Z","conditions":[["content-length-range",0,20971520],["starts-with","$key","ips_user_asset\/16\/59\/25\/36\/40\/78\/788028ff7b085391fee5967bd982d500.jpg"]]}

不过值得一提的是比如阿里的oss虽然在用户上传时候可以通过修改后缀的方式成功上传一个恶意源文件,但是它返回的content-type头是根据后缀来的而不是读头文件,这样避免了恶意文件被用户直接打开。其实常见的媒体头也没多少,我们做个返回时后缀匹配content-type就可以在大多场景下解决这个问题。

1
2
3
4
5
6
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式

文件后缀 优先级 大于读文件头这种方式更适合直觉,毕竟jpg的后缀给 html的content-type是不合理的。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~