你好,我是王昊天。今天我们来学习失效的输入检测,看看攻击者有哪些绕过方案。
在现实生活中,我们在乘坐一些交通工具时,需要经过安检,以防止有人携带危险物品,避免一些危害公众安全的行为。但是这种安全检查也不是万能的,比如进地铁站的时候不会检查我们衣服口袋里的物品。
对于一个交互的系统来说,同样也需要对输入进行安全检查,也同样很难做到万无一失。
交互系统的输入,可能来自用户的输入或者其他系统的传递。在系统获得预期内的输入信息之后,就会将它们当作参数,运行相应的命令来实现自己想要的功能。那么问题来了,如果忽略了对输入的验证或者验证得不够充分,在攻击者的恶意操作下,系统就会接收到预期之外的数据,进而随着命令的运行就让攻击者实现了自己想要的目标,进而产生难以想象的后果。
这,其实就是失效的输入检测。
根据检测技术的不同,失效的输入检测可以分为6种,它们分别是:不安全的输入检查、中间件的输入输出、不安全的映射、编码及转义、编码及混淆、WAF及绕过。
在接下来的2讲内容中,我会带你学习这6种常见的失效输入检测,是如何产生的,以及应该如何应对。
不安全的输入检查产生的原因,其实很好理解,就是当一个产品需要接收数据的输入时,却没有正确地对这些输入进行验证。
为了抵御这个问题,解决方案也比较简单,开发者只需要对一些输入数据进行安全性验证就可以。但其中的难点在于,如果安全性验证不够充分,攻击者就可以将输入构造成安全人员意料之外的形式,导致系统接收到意料之外的恶意输入。
这是非常危险的,因为攻击者甚至可以借此实现任意命令的执行。
我们来看一个关于消费行为的例子:
public static final double price = 20.00;
# 用户可以自由指定购买商品的数量
int quantity = currentUser.getAttribute(“quantity”);
# 计算总价
double total = price * quantity;
chargeUser(total);
在这段代码中,商品单价的值用户是无法修改的,但没有对购买数量的值进行限制。这时候,如果攻击者提供一个负值,那么他就不用进行消费,反而还能获得相应的收入。
接下来,我们开始学习失效的输入检测的第二种情况:中间件的输入输出。
通常情况下,一个系统会由多个组件构成。上游组件在接收到外部输入后,会将它传给中间件来构建部分命令、数据结构或记录,然后就将它们发送给下游组件。
需要注意的是,中间件并不能正确地处理这些输入中的特殊元素。这种失效的输入检测问题,就是中间件的输入输出问题。
我们直接看个例子。
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
命令,从而运行恶意命令,递归删除根分区的内容。
这个示例就是中间件没有对用户传入的恶意输入进行处理导致的。首先,在输入检查就存在问题,导致该输入没有被拦截。其次,中间件没有对这部分信息进行过滤处理,直接将接收到的恶意参数当成了命令来执行,造成了严重的后果。
到这里,我们已经学习了两种最直接的失效的输入检测风险类型,接下来我们再来学习一种更加隐蔽的风险种类,也就是不安全的映射。
不安全的映射发生的场景是:当应用程序需要使用带有映射的外部输入来选择要执行的代码时,却没有充分验证这些外部输入是否合法,这时候攻击者就可以将恶意文件上传到应用会执行的位置。
这对于应用来说是毁灭性的漏洞,非常危险。我们再通过一个例子,来理解下这种漏洞是怎么产生的吧。
下面这个例子,显示了一个不使用映射的命令调度程序,它的代码书写方式看起来并不十分优雅:
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);
我们品味一番,可以发现上述代码写得属实不够优雅,而优秀的开发人员可能会使用映射的方式来进行代码重构,如下所示:
String ctl = request.getParameter("ctl");
Class cmdClass = Class.forName(ctl + "Command");
Worker ao = (Worker)cmdClass.newInstance();
ao.doAction(request);
重构后的这段代码,确实提供了许多优势:代码更加简洁了;if/else块也消失了;在不修改命令调度程序的情况下,也可以添加新的命令类型。
但是,重构后的代码有个漏洞。攻击者可以先利用Worker接口创建一个类,然后使用它们。这里创建的类是没有限制的,它是由攻击者控制的参数ctl所决定。攻击者可以利用创建的这个类去执行恶意命令。
软件为了与另一个组件通信,会准备要发送的消息。它的结构需要符合通信协议的要求,如果数据的编码或转义过程中发生丢失或者执行错误,就可能会导致消息的结构发生变化。
不正确的编码或转义,可能允许攻击者将发送的正常命令更改为恶意命令。大多数软件都会遵循双方规定的协议进行通信。通信消息可以为带有控制信息的原始数据。
这么说有些抽象,我们看一个具体的示例:
“GET/index.html HTTP/1.1”是一个结构化消息,其中包含一个命令(“GET”)和一个参数(“/index.html”)和有关正在使用的协议版本(“HTTP/1.1”)。
如果应用程序使用攻击者提供的输入来构建结构化消息,而没有正确编码或转义,那么攻击者就可以在这条消息中插入特殊字符,导致数据被解释为控制信息。因此,接收输出的组件,就将会执行错误的操作。
我们再通过一个示例,来看看涉及编码及转义的攻击方式是如何发生的。
现在有这么一个聊天应用程序,它的前端Web应用程序与后端服务器之间要进行通信。因为后端是不执行身份验证或授权的遗留代码,所以我们必须在前端必须实现这个功能。聊天协议规定只支持两个命令SAY和BAN,而且BAN命令只有管理员才可以使用。每个参数必须由一个空格分隔,原始输入经过URL编码,消息协议允许在一行中执行多个以|
分隔的命令。
我们先看后端的代码:
$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应用程序接收命令后,对其进行编码,然后发送到权限查看服务器执行授权的检查,然后再将命令发送给后端。
$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编码的命令:
SAY hello%20world|BAN%20user12
后端就会把这个解析为如下两条命令来运行:
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种失效的输入检测是如何产生的。
不安全的输入检查,主要就是应用没有正确地过滤用户的输入,使得攻击者使用精心设计的恶意输入,成功实现对系统的攻击。
中间件的输入输出问题,是因为应用的中间组件在接收到其他组件的输入数据时,没有正确地对这个输入进行过滤所导致的问题。它的危害性极大,攻击者可以凭此实现任意命令的执行。
不安全的映射,发生在应用程序需要执行外部文件,而这个外部文件可以由攻击者上传的情况下。这主要是因为,应用程序没有对这个外部文件进行足够的限制所导致的。
对于编码及转义、编码及混淆这两个问题,它们都是编码相关的问题,区别在于:编码及转义问题,主要是因为系统没有对特殊字符做转义处理,使得攻击者可以借助这些特殊字符实现攻击行为;而编码及混淆问题,主要是系统只对一些危险的字符串进行了限制,却没有对这些字符对应的混淆编码进行拦截,导致攻击者可以凭借将恶意输入进行混淆,从而实现恶意输入的上传。
我把这些重点信息也提炼到一张思维导图中,你可以保存下来,方便复习:
在下一讲中,我会和你一起学习失效的输入检测中最复杂的一部分WAF检测,以及如何让自己的应用避免失效的输入检测问题的发生。
学完这一讲,请你思考下,失效的输入检测问题的核心到底是什么呢?你能想到什么好办法解决这一类问题吗?
欢迎在评论区留下你的思考。如果你觉得今天的内容对你有所帮助的话,欢迎你把课程分享给其他同事或朋友,我们共同学习进步!
评论