信息系统的建设过程中,经常会涉及文件上传的业务功能需求。如果在系统研发过程中未对上传的文件进行合理限制,或限制功能存在绕过漏洞,则系统投产后存在“任意文件上传漏洞”。
恶意攻击者可以通过上传恶意文件并在服务器端执行,造成严重危害。如下图某WebShell所示,攻击者可浏览并查看服务器文件,并对服务器拥有管理权限。
历年企业护网行动中,红队(攻击队)经常利用系统的“文件上传”功能,上传木马工具,最终实现对攻击目标的控制,攻陷目标。因此,对于“任意文件上传漏洞”的防护,平台提供方需要高度重视。
文件上传漏洞的出现,是由于代码没有严格限制用户上传文件后缀以及文件类型,导致攻击者向某个可通过Web访问的目录上传任意脚本文件,并能够将这些文件传递给解释器,在远程服务器上执行任意脚本。
存在文件上传功能的地方都有可能存在文件上传漏洞。比如用户头像、视频、照片分享、用户意见反馈等。如果移动端APP端也存在类似的操作,同理也存在文件上传漏洞风险。
一个完整的文件漏洞利用过程,需要三个步骤:
(1)攻击者通过文件上传功能,成功上传恶意文件至后端服务器
用户上传文件时,系统前后端均未进行文件格式检查,造成用户可以上传非功能设定的文件。如jsp、asp、php等可执行文件。
(2)攻击者获得上传恶意文件所在路径,并能成功访问
成功上传可执行文件后,系统服务端返回上传文件访问路径。用户通过浏览器访问该链接,即可查看自己上传的文件。
高级别攻击者,可以在服务端不返回文件目录的情况下,主动去构造上传文件访问路径+文件名。或者结合其他漏洞访问服务器文件目录,获取文件在服务器上的绝对路径。
(3)上传的恶意文件,可执行
上传恶意的可执行文件后(jsp、php、asp等),上传目录允许该文件作执行操作。访问该文件拉起可执行木马文件,获得当前主机命令执行权限或者文件管理权限,进而控制整台主机。该步骤就是红蓝攻防中所称的getshell操作。
在可执行恶意文件这一领域中,随着安全软件检测手段的更新,攻击者的攻击手段也在不断进化。从最初的直接上传多功能木马(俗称大马),改成仅包含上传功能的木马(俗称小马)再二次上传大马,到更精简版的一句话木马,甚至近些年流行的内存马。都是在攻守双方在不断博弈期间的升级手段。
木马相关内容,本期只作简要说明,详情将在下一期内容中说明。
“文件上传”本身功能没有危害,危害发生在恶意文件上传后在服务器端执行,常见危害包括:
在界面风格>编辑模板/css文件>添加模板处将aspx一句话木马添加成html文件,1.asp。
此处即为文件上传漏洞的利用方式。新增模板地址即为木马访问路径。
利用该木马管理工具,再次上传大马(具有多种功能的木马),完成对主机、文件、数据库的完全控制。
此时,即完成了一次getshell的攻击过程。后续的内网扩展内容,不在本文讨论范围。
“任意文件上传漏洞”通常按以下测试思路进行检测:
● 检测服务端文件后缀名过滤是否严格
如黑名单模式下过滤了.php、.jsp、.asp等可执行脚本文件,但却未过滤.php2、.php3、.php5等。以及双扩展名绕过。如上传xxx.jsp.jsp文件。 常见可执行文件后缀扩容包括:
PHP: phtml, .php, .php3, .php4, .php5, and .inc
ASP: asp, .aspx
PERL: .pl, .pm, .cgi, .lib
JSP: .jsp, .jspx, .jsw, .jsv, .jspf
Python:.py, .py3, .pyw, .pyx、
Coldfusion: .cfm, .cfml, .cfc, .dbm
● 检测文件上传限制是否仅在浏览器端JS(前端)限制
可通过抓包修改上传文件后缀名进行测试。
● 在检测过程中使用字符截断技术
如“\0”、“?”、“%00”等,绕过文件后缀名过滤逻辑,如aaa.jsp%00.jpg,在只可上传jpg文件的位置上传jsp文件,在可以控制文件路径的情况下,使用超长的文件名也有可能会导致文件路径截断,绕过服务端限制。
● 检测Web中间件是否存在文件解析漏洞(如IIS解析漏洞、Apache 文件解析问题)。
当存在漏洞时,可能导致原本不可被执行的图片,.htaccess等文件被视作asp,jsp等文件进行解析,从而可上传包含恶意代码的图片文件。
● 检测第三方组件是否包含文件上传漏洞,典型案例有CKEditor。
● 检测服务器是否支持HTTP PUT方法,能否在无文件上传功能的场景下上传文件。
● 检查HTTPHeader中的Content-Type,修改为text/html进行绕过。
只要能通过上述方式,上传可执行脚本文件,并正常访问文件,即证明该功能存在“任意文件上传漏洞”。
任意文件上传漏洞的防护思路。从研发设计角度,可以从其攻击流程三步骤入手。“允许上传恶意文件——访问上传文件——文件被执行”。只需要打断其攻击链,均可实现对文件上传漏洞攻击的防护。
1、后端白名单检查文件扩展名
在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单方式,不属于白名单范围的文件类型,不允许上传。黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。不建议通过前端实现校验,攻击者可通过抓包改包方式绕过前端验证。
2、后端限制文件上传大小
辅助手段。明确上传的文件场景,可预先限定文件大小。防止上传大马等非正常文件。
3、文件名随机命名
文件上传至服务器后,对文件随机命名。如UUID、GUID,不允许用户自定义。可增加攻击者访问上传文件难度。像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。
4、隐藏上传文件目录
在文件上传后,服务端不返回文件访问路径。使攻击者无法找到上传文件,切掉其攻击路径。
5、上传文件保存目录不可执行
要求上传文件的保存目录无可执行权限,不可解析jsp、php、asp、py等脚本语言。只要web容器无法解析该目录下面的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响。
6、文件内容检测
通过威胁检测等工具对文件进行安全检查,可有效防范图片马和文件二次渲染。
代码层面,任意文件上传漏洞防护思路,研发同学可参考如下“任意文件上传漏洞”安全编码示例(Java):
import java.io.*;
import java.util.UUID;
public class FileUploadHandler {
private static final String UPLOAD_DIR = "/path/to/upload/directory"; // 上传文件保存的目录
private static final String[] ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "gif"}; // 允许上传的文件扩展名白名单
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 最大文件大小限制为10MB
public boolean isValidFileExtension(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
for (String allowedExtension : ALLOWED_EXTENSIONS) {
if (extension.equals(allowedExtension)) {
return true;
}
}
return false;
}
public boolean isValidFileSize(long fileSize) {
return fileSize <= MAX_FILE_SIZE; //限制上传文件大小
}
public String saveUploadedFile(InputStream inputStream, String originalFileName) throws IOException {
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
String randomFileName = UUID.randomUUID().toString() + fileExtension;
String filePath = UPLOAD_DIR + File.separator + randomFileName;
try (OutputStream outputStream = new FileOutputStream(filePath)) {
int bytesRead;
byte[] buffer = new byte[8192];
while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
return randomFileName;
}
public static void main(String[] args) {
// 示例用法
FileUploadHandler uploadHandler = new FileUploadHandler();
String uploadedFileName = "example.jpg"; // 假设上传的文件名为example.jpg
long uploadedFileSize = 5000000; // 假设上传的文件大小为5MB
if (uploadHandler.isValidFileExtension(uploadedFileName) && uploadHandler.isValidFileSize(uploadedFileSize)) {
try {
// 假设inputStream是上传文件的输入流
InputStream inputStream = new FileInputStream("/path/to/uploaded/file/example.jpg");
String savedFileName = uploadHandler.saveUploadedFile(inputStream, uploadedFileName);
System.out.println("File saved with random name: " + savedFileName);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("Invalid file. File extension or size is not allowed.");
}
}
}
从SDL的角度来看,“任意文件上传漏洞”可通过以下措施进行防范或缓解:
综上,文件上传漏洞指攻击者利用程序缺陷,绕过系统对文件的验证与处理策略,将恶意程序上传到服务器并获得执行服务器端命令的能力。本文简要讲述了任意文件上传漏洞原理、危害、测试方法及防御措施。
该漏洞可与其他漏洞结合利用,尤其是与WebShell工具结合,可造成更大危害,如直接完成对主机权限控制。企业在研发、测试阶段,需重点关注。
米桃安全漏洞讲堂系列第2期:XSS跨站脚本攻击漏洞-CSDN博客
米桃安全漏洞讲堂系列第1期:SQL注入漏洞-CSDN博客