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.

379 lines
18 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 调用中直接使用了未经验证的数据,并利用该数据进行信息查询:
```java
pstmt.setString(1, request.getParameter("acct"));
ResultSet results = pstmt.executeQuery();
```
- 在浏览器地址栏中修改 acct 参数,即可对 SQL 语句进行操纵,而在未经验证的情况下,该攻击者可以访问到其他账户的信息。
```txt
https://example.com/app/accountInfo?acct=notmyacct
```
- 2-
- 一个攻击者可以很轻松地修改 URL 地址,尝试去访问他的目标链接,比如这里攻击者试图通过 URL 地址修改直接访问 admin 页面:
```txt
https://example.com/app/getappInfo
https://example.com/app/admin_getappInfo
```
- 如果攻击者成功访问了第二个链接,那么说明系统在权限设计和访问控制上就是存在问题
- 3-
- 由于实现过程中未对用户访问参数设置边界,导致了很多越权问题的发生:
```txt
https://example.com/order/?order_id=2021102617429999
```
- 攻击者可以尝试修改上述 API 接口中的 order_id 参数,使其在程序接口上的输入合法,但是对于用户而言却是越权行为
- 4-
- HTTP PUT 方法最早目的用于文件管理操作,可以对网站服务器中的文件实现更改删除的更新操作,该方法往往可以导致各种文件上传漏洞,造成严重的网站攻击事件:
```shell
put /root/Desktop/shell.php
```
- 上述代码在支持 PUT 方法的环境中,上传 Webshell 进行提权;在实际运用中,若必须启用该方法,则需要对该方法涉及文件资源做好严格的访问权限控制。
- 5-
- Web 应用将身份认证结果直接存储在 Cookie 中,并未施加额外的保护措施:
```shell
Cookie: role=user --> Cookie: role=admin
```
- 通过在 Web 前端拦截 Cookie并进行 Cookie 内容修改即可提权
- 6-
- 有些开发者为了方便,直接在 Access-Control-Allow-Origin 中反射请求的 Origin 值:
```shell
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 版本的一个默认组件:
```txt
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 以及端口号进行检查:
```injectablephp
...
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 也是需要的**。
- 那么到现在为止,我们的攻击示意图
```shell
./squirrel-nmap [Target] [IP] [TCP_PORT] [Cookie]
- Target suqiremail的URL地址 如http://target.com/sqm/
- IP 待扫描的IP地址
- TCP_PORT 尝试探测的TCP端口号
- Cookie 经过认证的Cookie
```
- 通过简单的实现,我们来看看实际的战斗威力如何:
```shell
./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 为例,许多自动化的请求处理机制都是能够帮助我们实现这种需求的技术
- **默认拒绝**
- 这是非常简单但是有效的策略,所谓默认拒绝是指,只要一个请求没有被指明是被允许的,那么它就是被拒绝的。
- **不要硬编码角色**
- 很多应用框架默认使用用户角色来进行访问控制,以下的代码形态是很常见的:
```java
if (user.hasRole("admin") || user.hasRole("Manager")) {
deleteAccount();
}
```
- 但是你要对这种 Role-Based 编码模式格外留意,因为它可能会带来以下几种风险:
- 由于这种编码自身的特性非常脆弱,很容易**出现检查错误或者检查缺失等情况**
- 由于这种编码模型**对于多租户产品非常不友好**,很容易出现用户角色一致但是权限不一致的情况;
- Role-Based 编码模型**无法适配包括以数据为核心的以及横向访问控制**
- 当项目代码量攀升并且伴随着很多访问权限控制的情况出现时,**访问控制策略的审计和验证是非常困难的**。
- 因此这里我更推荐你使用这种编码方式:
```java
if (user.hasAccess("DELETE_ACCOUNT")) {
deleteAccount();
}
```
- 以属性或者功能为核心的访问控制编码模型,从特性上来讲更易于构建功能丰富的访问控制系统。
- **记录所有的访问控制类事件**
- 所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。
### 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 "<ul>\n";
while(<$fh>) {
print "<li>$_</li>\n";
}
print "</ul>\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
<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 目录
```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 只包含最小必要信息,避免过于详细的信息展示,防止攻击者因此获取系统相关信息。
- 在架构设计阶段:
- 确保所有客户端发生的安全检查,都在服务端完成第二次检查,这样做的目的是防止攻击者在客户端进行安全检查绕过;
- 使用成熟的库或者框架来使开发者更容易规避这种特定类型的风险。
- 在防御建设阶段:
- 使用可以防御这种类型攻击的应用层防火墙,在某些特定情况下(比如应用系统漏洞无法修复)非常有效;
- 使用最小权限运行开发完毕的应用系统,如果可能,创建独立的受限账户用于应用系统运行;
- 使用沙箱环境运行开发完毕的应用系统,做好进程和系统之间的边界隔离。