242 lines
17 KiB
Markdown
242 lines
17 KiB
Markdown
# 19 | 失效的输入检测(上):攻击者有哪些绕过方案?
|
||
|
||
你好,我是王昊天。今天我们来学习失效的输入检测,看看攻击者有哪些绕过方案。
|
||
|
||
在现实生活中,我们在乘坐一些交通工具时,需要经过安检,以防止有人携带危险物品,避免一些危害公众安全的行为。但是这种安全检查也不是万能的,比如进地铁站的时候不会检查我们衣服口袋里的物品。
|
||
|
||
对于一个交互的系统来说,同样也需要对输入进行安全检查,也同样很难做到万无一失。
|
||
|
||
交互系统的输入,可能来自用户的输入或者其他系统的传递。在系统获得预期内的输入信息之后,就会将它们当作参数,运行相应的命令来实现自己想要的功能。那么问题来了,如果忽略了对输入的验证或者验证得不够充分,在攻击者的恶意操作下,系统就会接收到预期之外的数据,进而随着命令的运行就让攻击者实现了自己想要的目标,进而产生难以想象的后果。
|
||
|
||
这,其实就是失效的输入检测。
|
||
|
||
**根据检测技术的不同,失效的输入检测可以分为6种,它们分别是:不安全的输入检查、中间件的输入输出、不安全的映射、编码及转义、编码及混淆、WAF及绕过。**
|
||
|
||
在接下来的2讲内容中,我会带你学习这6种常见的失效输入检测,是如何产生的,以及应该如何应对。
|
||
|
||
## 不安全的输入检查
|
||
|
||
不安全的输入检查产生的原因,其实很好理解,就是当一个产品需要接收数据的输入时,却没有正确地对这些输入进行验证。
|
||
|
||
为了抵御这个问题,解决方案也比较简单,开发者只需要对一些输入数据进行安全性验证就可以。但其中的难点在于,如果安全性验证不够充分,攻击者就可以将输入构造成安全人员意料之外的形式,导致系统接收到意料之外的恶意输入。
|
||
|
||
这是非常危险的,因为攻击者甚至可以借此实现任意命令的执行。
|
||
|
||
我们来看一个关于消费行为的例子:
|
||
|
||
```c++
|
||
public static final double price = 20.00;
|
||
# 用户可以自由指定购买商品的数量
|
||
int quantity = currentUser.getAttribute(“quantity”);
|
||
# 计算总价
|
||
double total = price * quantity;
|
||
chargeUser(total);
|
||
|
||
```
|
||
|
||
在这段代码中,商品单价的值用户是无法修改的,但没有对购买数量的值进行限制。这时候,如果攻击者提供一个负值,那么他就不用进行消费,反而还能获得相应的收入。
|
||
|
||
接下来,我们开始学习失效的输入检测的第二种情况:中间件的输入输出。
|
||
|
||
## 中间件的输入输出
|
||
|
||
通常情况下,一个系统会由多个组件构成。上游组件在接收到外部输入后,会将它传给中间件来构建部分命令、数据结构或记录,然后就将它们发送给下游组件。
|
||
|
||
需要注意的是,**中间件并不能正确地处理这些输入中的特殊元素。这种失效的输入检测问题,就是中间件的输入输出问题。**
|
||
|
||
我们直接看个例子。
|
||
|
||
```c++
|
||
int main(int argc, char** argv) {
|
||
char cmd[CMD_MAX] = "/usr/bin/cat";
|
||
strcat(cmd, argv[1]);
|
||
system(cmd);
|
||
}
|
||
|
||
```
|
||
|
||
如果这个程序是以`root`权限运行的,那么对`system()`的调用也会以`root`权限执行。如果用户传入的参数是标准的文件名,那么调用会按预期工作。
|
||
|
||
但是,如果攻击者传递了一个恶意输入,比如一个`; rm -rf /`形式的字符串,那么对`system()`的调用也会因为缺少参数而无法执行`cat`命令,从而运行恶意命令,递归删除根分区的内容。
|
||
|
||
这个示例就是中间件没有对用户传入的恶意输入进行处理导致的。首先,在输入检查就存在问题,导致该输入没有被拦截。其次,中间件没有对这部分信息进行过滤处理,直接将接收到的恶意参数当成了命令来执行,造成了严重的后果。
|
||
|
||
到这里,我们已经学习了两种最直接的失效的输入检测风险类型,接下来我们再来学习一种更加隐蔽的风险种类,也就是不安全的映射。
|
||
|
||
## 不安全的映射
|
||
|
||
不安全的映射发生的场景是:当应用程序需要使用带有映射的外部输入来选择要执行的代码时,却没有充分验证这些外部输入是否合法,这时候攻击者就可以将恶意文件上传到应用会执行的位置。
|
||
|
||
这对于应用来说是毁灭性的漏洞,非常危险。我们再通过一个例子,来理解下这种漏洞是怎么产生的吧。
|
||
|
||
下面这个例子,显示了一个不使用映射的命令调度程序,它的代码书写方式看起来并不十分优雅:
|
||
|
||
```java
|
||
String ctl = request.getParameter(“ctl”);
|
||
Worker ao = null;
|
||
|
||
// 判断是否ctl参数中是Add字符串
|
||
if (ctl.equals("Add"))
|
||
{
|
||
ao = new AddCommand();
|
||
}
|
||
// 判断是否ctl参数中是Modify字符串
|
||
else if (ctl.equals("Modify"))
|
||
{
|
||
ao = new ModifyCommand();
|
||
}
|
||
else {
|
||
throw new UnknownActionError();
|
||
}
|
||
ao.doAction(request);
|
||
|
||
```
|
||
|
||
我们品味一番,可以发现上述代码写得属实不够优雅,而优秀的开发人员可能会使用映射的方式来进行代码重构,如下所示:
|
||
|
||
```java
|
||
String ctl = request.getParameter("ctl");
|
||
Class cmdClass = Class.forName(ctl + "Command");
|
||
Worker ao = (Worker)cmdClass.newInstance();
|
||
ao.doAction(request);
|
||
|
||
```
|
||
|
||
重构后的这段代码,确实提供了许多优势:代码更加简洁了;if/else块也消失了;在不修改命令调度程序的情况下,也可以添加新的命令类型。
|
||
|
||
但是,重构后的代码有个漏洞。攻击者可以先利用Worker接口创建一个类,然后使用它们。这里创建的类是没有限制的,它是由攻击者控制的参数ctl所决定。攻击者可以利用创建的这个类去执行恶意命令。
|
||
|
||
## 编码及转义
|
||
|
||
软件为了与另一个组件通信,会准备要发送的消息。它的结构需要符合通信协议的要求,如果数据的编码或转义过程中发生丢失或者执行错误,就可能会导致消息的结构发生变化。
|
||
|
||
不正确的编码或转义,可能允许攻击者将发送的正常命令更改为恶意命令。大多数软件都会遵循双方规定的协议进行通信。通信消息可以为带有控制信息的原始数据。
|
||
|
||
这么说有些抽象,我们看一个具体的示例:
|
||
|
||
```plain
|
||
“GET/index.html HTTP/1.1”是一个结构化消息,其中包含一个命令(“GET”)和一个参数(“/index.html”)和有关正在使用的协议版本(“HTTP/1.1”)。
|
||
|
||
```
|
||
|
||
如果应用程序使用攻击者提供的输入来构建结构化消息,而没有正确编码或转义,那么攻击者就可以在这条消息中插入特殊字符,导致数据被解释为控制信息。因此,接收输出的组件,就将会执行错误的操作。
|
||
|
||
我们再通过一个示例,来看看涉及编码及转义的攻击方式是如何发生的。
|
||
|
||
现在有这么一个聊天应用程序,它的前端Web应用程序与后端服务器之间要进行通信。因为后端是不执行身份验证或授权的遗留代码,所以我们必须在前端必须实现这个功能。聊天协议规定只支持两个命令SAY和BAN,而且BAN命令只有管理员才可以使用。每个参数必须由一个空格分隔,原始输入经过URL编码,消息协议允许在一行中执行多个以`|`分隔的命令。
|
||
|
||
我们先看后端的代码:
|
||
|
||
```php
|
||
$inputString = readLineFromFileHandle($serverFH);
|
||
# generate an array of strings separated by the "|" character.
|
||
@commands = split(/\|/, $inputString);
|
||
|
||
foreach $cmd (@commands) {
|
||
# separate the operator from its arguments based on a single whitespace
|
||
($operator, $args) = split(/ /, $cmd, 2);
|
||
|
||
$args = UrlDecode($args);
|
||
if ($operator eq "BAN") {
|
||
ExecuteBan($args);
|
||
}
|
||
else if ($operator eq "SAY") {
|
||
ExecuteSay($args);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
前端Web应用程序接收命令后,对其进行编码,然后发送到权限查看服务器执行授权的检查,然后再将命令发送给后端。
|
||
|
||
```php
|
||
$inputString = GetUntrustedArgument("command");
|
||
($cmd, $argstr) = split(/\s+/, $inputString, 2);
|
||
|
||
/# removes extra whitespace and also changes CRLF's to spaces/
|
||
$argstr =~ s/\s+/ /gs;
|
||
|
||
$argstr = UrlEncode($argstr);
|
||
if (($cmd eq "BAN") && (! IsAdministrator($username))) {
|
||
die "Error: you are not the admin.\n";
|
||
}
|
||
|
||
/# communicate with file server using a file handle/
|
||
$fh = GetServerFileHandle("myserver");
|
||
|
||
print $fh "$cmd $argstr\n";
|
||
|
||
```
|
||
|
||
我们可以发现一个很明显的问题,虽然协议和后端都允许在一个请求中发送多个命令,但前端只打算发送一个命令。可是`UrlEncode`函数可能会留下`|`字符。
|
||
|
||
也就是说,如果攻击者提供`SAY hello world|BAN user12`,前端会看到这是一个SAY命令,`$argstr` 就为`hello world | BAN user12`。由于命令是SAY,对BAN命令的检查会失败。前端会向后端发送URL编码的命令:
|
||
|
||
```plain
|
||
SAY hello%20world|BAN%20user12
|
||
|
||
```
|
||
|
||
后端就会把这个解析为如下两条命令来运行:
|
||
|
||
```plain
|
||
SAY hello world
|
||
BAN user12
|
||
|
||
```
|
||
|
||
但是请注意,如果前端使用正确的编码将`|` 编码为`%7C` ,那么后端将只处理一个命令。
|
||
|
||
这就是一个典型的编码错误导致的输入验证失效的例子。
|
||
|
||
## 编码及混淆
|
||
|
||
除了编码及转义,**攻击者还可以通过编码混淆攻击来逃避输入的检查,为攻击区注入有害负载**。这种攻击方式,就叫做编码及混淆。
|
||
|
||
客户端和服务器会使用各种不同的编码在系统之间传递数据,而当它们想要使用数据时就需要首先对其进行解码。
|
||
|
||
在构建攻击时,我们需要考虑有害负载的注入位置。如果可以根据关联环境推断出输入是如何被解码的,那么我们就可以知道,要用什么方式对有害负载进行编码。
|
||
|
||
在URL中,有一系列具有特殊含义的保留字符。例如,`&`用作分隔符,它可以分隔查询字符串中的参数。基于URL的输入可能包含这些字符,比如用户搜索`Fish & Chips`之类的内容会发生什么呢?
|
||
|
||
浏览器会自动对任何可能导致解析器歧义的字符进行URL编码。这意味着,用%字符和它们的二位十六进制代码替换它们,成为这样`[…]/?search=Fish+%26+Chips`,来确保`&` 不会被误认为是分隔符。
|
||
|
||
任何基于URL的输入在分配给相关变量之前,都会在服务器端自动进行URL解码。这意味着,就大多数服务器而言,查询参数中的`%22`、`%3D`和`%3E`等序列分别与`“`、`<`和`>`字符同义。也就是说,我们可以通过URL注入URL编码的数据,它通常仍会被后端应用程序正确解释。
|
||
|
||
有时,我们可能会发现,WAF等在检查你的输入时,无法正确地对你的输入进行 URL解码。在这种情况下,我们只需对列入黑名单的任何字符或单词进行编码,就可以将有害负载绕过检测,发送给后端应用程序,实现攻击行为。
|
||
|
||
在XSS注入中,我们经常会需要输入<script>进行攻击,但是往往输入检测会将它拦截,使得我们无法成功攻击,这时我们就可以对它进行编码混淆,将它改为:
|
||
|
||
```
|
||
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()(([]+[])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+[]]+(![]+[])[!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[+[]]+([]+[])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[!+[]+!+[]])
|
||
|
||
```
|
||
|
||
利用这个方法,我们可以绕过很多的输入检测。
|
||
|
||
## 总结
|
||
|
||
好了,今天的主要内容就到这里,我们一起小结下。
|
||
|
||
今天这节课,我们一起学习了不安全的输入检查、中间件的输入输出、不安全的映射、编码及转义、编码及混淆,这5种失效的输入检测是如何产生的。
|
||
|
||
不安全的输入检查,主要就是应用没有正确地过滤用户的输入,使得攻击者使用精心设计的恶意输入,成功实现对系统的攻击。
|
||
|
||
中间件的输入输出问题,是因为应用的中间组件在接收到其他组件的输入数据时,没有正确地对这个输入进行过滤所导致的问题。它的危害性极大,攻击者可以凭此实现任意命令的执行。
|
||
|
||
不安全的映射,发生在应用程序需要执行外部文件,而这个外部文件可以由攻击者上传的情况下。这主要是因为,应用程序没有对这个外部文件进行足够的限制所导致的。
|
||
|
||
对于编码及转义、编码及混淆这两个问题,它们都是编码相关的问题,区别在于:编码及转义问题,主要是因为系统没有对特殊字符做转义处理,使得攻击者可以借助这些特殊字符实现攻击行为;而编码及混淆问题,主要是系统只对一些危险的字符串进行了限制,却没有对这些字符对应的混淆编码进行拦截,导致攻击者可以凭借将恶意输入进行混淆,从而实现恶意输入的上传。
|
||
|
||
我把这些重点信息也提炼到一张思维导图中,你可以保存下来,方便复习:
|
||
|
||
![](https://static001.geekbang.org/resource/image/28/23/281e5d9c00c26d59a9d79f93119dd523.jpg?wh=2250x1250)
|
||
|
||
在下一讲中,我会和你一起学习失效的输入检测中最复杂的一部分WAF检测,以及如何让自己的应用避免失效的输入检测问题的发生。
|
||
|
||
## 思考
|
||
|
||
学完这一讲,请你思考下,失效的输入检测问题的核心到底是什么呢?你能想到什么好办法解决这一类问题吗?
|
||
|
||
欢迎在评论区留下你的思考。如果你觉得今天的内容对你有所帮助的话,欢迎你把课程分享给其他同事或朋友,我们共同学习进步!
|
||
|