叠甲

这一部分包含了相当多的细碎的知识点,本文只是简单地整理一下目前学到的一些漏洞,并不全面,场景主要以一句话木马为主。主要以PortSwigger的lab为例。

什么是文件上传漏洞

文件上传漏洞指的是服务器没有对上传文件的名称、类型、内容、大小等进行充分的验证,这种情况下,即使是最简单的图片上传功能也可以被用于上传危险文件。对于这种漏洞的攻击不只有上传,通常还涉及后续对文件的HTTP请求,用于触发服务器执行文件。

就验证而言,如果名称没有被充分验证,就可以借助上传同名文件覆盖关键文件;如果类型没有被验证,如.php文件就可能以代码形式运行;如果内容没有被充分验证,比如没有查验文件头或者或者是否符合标准结构,那么可以通过伪造文件头或者元数据注入上传文件;如果大小没有被验证,可以通过上传大量大型文件占满服务器的硬盘,也算是某种DoS攻击。

此外想要利用这种漏洞还需要对服务器处理上传文件的这个过程有一定的理解。一般分为处理静态文件和处理动态文件两种。前者包括了.png.css.js.html等。后者包括.php.jsp等。

静态文件的处理流程分为三步:

  • 映射:假设请求的网址为https://example.com/image/cat.png,服务器会先解析这个URL地址,并映射到硬盘上的实际存储位置,/var/www/html/images/cat.png。
  • 确认类型:服务器提取扩展名.png并在配置表中查找,确认是图片格式image/png且不可执行。
  • 读取与返回:服务器从硬盘读取原始内容,然后在Content-Type中加上image/png的响应头,发给用户的浏览器,浏览器收到后渲染成图片。

动态文件的处理与静态类似,比如请求https://example.com/api/user.php?id=1,经过映射以后找到实际位置,确认的配置类型是可执行,然后将用户发来的请求头中的数据发到解析引擎,运行代码后将结果返回到用户浏览器上。

动态文件的处理其实透露出一种泄露源代码的可能性,如果请求的动态文件配置类型是不可执行,那么源码会被直接读取原始内容发给用户。

从攻击角度看,利用漏洞的关键在于上传web shell(恶意脚本),如果能成功上传web shell,实际上就已经控制了服务器。

常见的漏洞类型

文件类型验证漏洞

一段简单的文件类型验证漏洞的后端逻辑可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def handle_file_upload(request):
# 1.从请求处接收用户上传的文件
uploaded_file = request.get_file("upload_field")

# 2.读取文件类型
user_provided_type = uploaded_file.content_type

# 3.直接利用读取的信息做判断
if user_provided_type == "image/png" or user_provided_type == "image/jpeg":

# 4.提取文件原本名字
file_name = uploaded_file.filename

# 5.存到服务器
save_path = "/var/www/uploads/" + file_name
save_to_disk(uploaded_file, save_path)

return "上传成功"

else:
return "上传失败"

正如前文所说,这种漏洞最明显的就是只验证文件类型而不验证其他,其中文件类型Content-Type是客户端生成的,可以直接用代理工具篡改。

简单介绍一下文件类型,正式名称是MIME类型,标准格式由主类型和子类型组成,中间用正斜杠隔开:主类型/子类型(比如image/jpeg)。常见的有图片类、文本与网页类、表单类、应用类、特殊类型。

其中表单类是文件上传的核心:

  • application/x-www-form-urlencoded:适用于普通的表单提交,比如只有账号密码,会把数据变成username=admin&password=123的格式
  • multipart/form-data:如果要同时提交文本和二进制文件(图片),浏览器会把整个HTTP请求体切块。

还有一个特殊的二进制数据流类型,

做个简单的演示。

这是一个简单的上传头像的场景。已知存在类型验证漏洞,那我们可以先上传一张图片,用代理工具抓到POST和GET请求,然后在上传的图片中加入一句话木马,最后请求这个文件让服务器用引擎解析代码。




我们将原来图片的二进制数据换成一句话木马,并把扩展名改成.php用于让服务器解析代码,鉴于靶场要求读取特定文件,木马如下:

1
<?php echo file_get_contents('/home/carlos/secret'); ?>



这个过程中可以注意到我们的Content-Type始终是image/jpeg,虽然和扩展名冲突,但是依然被成功上传,说明该场景下只检查了文件类型而没有检查名称。

文件内容验证漏洞

更安全的服务器会尝试验证文件内容是否符合预期,比如对于图片上传,服务器可能会尝试验证图像的内在属性,比如尺寸。如果上传一个PHP脚本,因为没有尺寸属性,服务器会推断不是图片而拒绝上传。还有提到的验证文件头,比如JPEG文件以FF D8 FF字节开头。

这种内容验证也不是万无一失的,可以用如Exiftool的工具进行元数据注入制造多语言文件(polyglot)进行绕过。其中polyglot是指一个文件同时符合多种不同文件格式规范,在不同解析器下能被合法执行或读取。

举个简单的例子。

同样的上传头像场景,当我们把文件内容改成脚本后回显文件不是一个有效的图片,这就需要我们绕过内容验证。

用到的工具是Exiftool,一种处理文件元数据的工具。

1
exiftool -Comment="<?php echo 'START' . file_get_contents('/home/carlos/secret') . 'END' ;?>" <INPUT_IMAGE>.jpg -o polyglot.php

exiftool指调用的工具。-Comment=“…………”是注入点。<INPUT_IMAGE>.jpg是目标文件,连括号一起改成对应文件的名称。-o polyglot.phpo表示输出(output),整体表示为输出为polyglot.php文件。

这里选Comment字段的原因是,这个字段本身就是设计用来给用户填写任意文本说明的,容量大,不容易破坏图片本身的二进制结构。



这一回上传成功且通过我们留下的START标识在茫茫多二进制数据中找到了回显,脚本生效。

防止用户在可访问目录中执行文件

如果危险文件已经被上传了,那么第二道防线就是阻止服务器运行危险脚本。所以通常服务器只会运行明确配置为执行的文件,对于不可执行的可能会输出纯文本,如果是这样的话,就有可能泄露源码。

这种配置因目录而异,存放用户上传的文件的目录的配置显然会比其他目录更加严格,所以有时候可以想办法跳出这个目录,这样服务器就有可能执行上传的脚本。这就利用到了路径遍历攻击。



我们直接上传.php文件,成功,但是GET请求中的回显只有明文,说明.php文件在这一级目录当中被配置为不可执行,那么想办法跳出这一级目录到上一级,用到../

加上了../发现Res中的名称没有变化,可能是被剥离掉了。编码一次试试。


编码后成功返回了一级目录,拿到回显。

黑名单缺陷

防止用户上传危险文件的一个方法是设置黑名单,将潜在的危险文件拓展名列入黑名单,这种方法本质上有缺陷,人无法穷尽所有可能的危险文件,所以为了安全起见通常会用白名单代替。

但是如果真的是黑名单,那么常见的攻击方法有两种。

覆盖配置文件

如前文所说,文件是否执行取决于服务器配置,这一步在源文件进行。许多服务器允许开发者在单个目录创建特殊的配置文件,便于调整全局设置。比如Apache服务器的.htaccess文件。

我们可以通过上传配置文件修改设置,将任意的扩展名映射到可以执行的MIME类型上。

举个简单的例子。

php文件不可以上传,但是从回显中我们得知这是Apache服务器,所以可以尝试上传配置文件.htaccess

我们把一个没有实际含义的.123a映射成.php,这样我们上传的内容就会用PHP引擎解析。


成功。

混淆扩展名

这一块相当琐碎,仅作整理。

  • 大小写混淆,如.pHp
  • 多个扩展名,如exploit.php.jpg
  • 尾随字符,如.php.
  • 编码,如%2Ephp
  • 空字节隔断,如exploit.php%00.jpg
  • 递归剥离,如.ph.phpp

这只是一小部分。