RCE系统交互条件与受限环境下的利用
简单来说,RCE便是目标程序被远程执行了未经授权的指令。
var name = requset.get("name") `
var cmd = "echo 'Hello " + name + "'"
RUN_CMD(cmd)`
外部输⼊,进⾏简单的拼接,最终放⼊⼀个执⾏外部命令的函数中去执⾏
但命令执⾏的函数,都⼀定存在这个rce吗? 如果不⼀定存在RCE,它们之间的差别是什么
我们平时说的RCE,比如thinkPHP的rce漏洞,即算代码注入漏洞,也算RCE漏洞,因为RCE漏洞危害更大,优先级更高,所以直接叫RCE漏洞了,如果你想利用thinkphp的RCE漏洞去执行phpinfo,也是可以的,本质上就是文件包含漏洞(系统)造成代码注入,最后产生RCE的一个过程。
实际安全领域的讨论中,"RCE" 通常被广泛用来指代远程代码执行漏洞。这种漏洞的危害较大,因为攻击者可以通过远程手段在目标系统上执行任意代码,从而完全控制受影响的系统。
在一些框架或系统的漏洞报告中,特定的漏洞可能被归类为 "RCE",即使它们的根本原因是其他类型的漏洞,比如文件包含漏洞或代码注入漏洞。这种归类的目的在于强调漏洞的严重性和潜在危害,因为远程代码执行意味着攻击者可以在远程服务器上执行他们自己的代码。
如果一个漏洞只能造成代码注入,但没办法RCE系统命令执行,这个时候我们才称之为代码注入漏洞。
但⾸先强调,我们在这里讨论的是应用程序本身的代码执行漏洞,而不是由于其他漏洞或第三方引起的外部代码执行。
RCE与命令注入的区别取决于执行的目标与权限。
分类区别
代码执行与命令执行区别
远程代码执行,⽤户的输⼊被当做代码的⼀部分执⾏;
远程命令执行,数据里面执行外部命令的函数没有被过滤。
⼀般出现远程命令执行漏洞,是因为应⽤系统从设计上需要给⽤户提供指定的远程命令操作的接⼝。⽐ 如我们常⻅的路由器、防⽕墙、⼊侵检测(硬件设备、⼯业交付)等设备的web管理界⾯上。 仅当Web应⽤程序代码包含操作系统调⽤(外壳程序、shell)并且调⽤中使⽤了⽤户输⼊时, 才可能进⾏OS命令注⼊攻击。它们不是特定于语⾔的,命令注⼊漏洞可能会出现在所有让你 调⽤系统外壳命令的语⾔中:C,Java,PHP,Perl,Ruby,Python等。
RCE与命令注入区别
RCE主要是在操作系统上面执行代码,比如whoami (因为RCE涉及到对操作系统底层功能的控制,比如调试与分析)。命令注入主要是在应用程序上面执行相关代码,比如phpinfo (因为通过命令执行的函数可获取到相关信息方便程序与系统交互,但对用户提交的数据过滤不严格)。RCE与命令注入的区别取决于执行的目标与权限。
系统命令执行漏洞,当然是对操作系统进行控制和输出的,而代码注入漏洞,是对应用程序的相关代码进行注入,从而达到控制应用程序的目的。
但有时候他们是交叉的 远程命令执行包含命令注入,命令注入也能RCE,需要调用应用程序的相关代码。
$user_input = $_GET['input'];`
system('whoami ' . $user_input);`
如果用户能够控制 $_GET['input'] 的值 在系统上面执行任意指令 便算是通过命令注入来造成RCE,如果不是在系统上面执行 在应用程序上面执行 就只能算 命令注入
可利用函数
远程代码执行
PHP中
eval() //把字符串作为PHP代码执行
assert() //检查一个断言是否为 PALSE,可用来执行代码
preg_replace() //执行一个正则表达式的搜索和替换
call user func() //把第一个参数作为回调函数调用
call_user_func_array() //调用回调函数,并把一个数组参数作为回调函数的参数
array_map()/7为数组的每个元素应用回调困数
Sa(sb) //动态困数
Python中
eval()
:功能:执行一个字符串表达式,并返回表达式的值。
风险:如果执行的字符串来自不可信的源,则可能执行恶意代码。
exec()
:功能:执行存储在字符串或代码对象中的Python语句。
风险:与
eval()
类似,如果执行的代码来源不安全,可能导致安全漏洞。
pickle.loads()
/pickle.load()
:pickle.loads()
/pickle.load()
:功能:反序列化pickle对象。
风险:如果反序列化的数据来自不可信的源,攻击者可能利用pickle的特性执行任意代码。
getattr()
:功能:获取对象的属性值。
风险:如果属性名和对象是动态提供的,并且来源不安全,可能被用来执行不安全的方法。
subprocess.Popen()
/ 相关函数:功能:执行外部命令和程序。
风险:如果外部命令的输入部分来自用户输入或不安全的源,可能被用于执行恶意命令。
os.system()
/os.popen()
:功能:执行系统命令。
风险:同
subprocess.Popen()
,如果命令字符串不安全,可能导致代码注入
Java的安全模型和语言设计通常不允许直接执行存储在字符串中的代码。然而,Java中的一个著名安全风险源自于反序列化操作,这在恶意数据被反序列化时可能导致远程代码执行(RCE)。
假设有一个Java应用程序,它接受并反序列化来自外部的对象数据,例如,从网络接收的对象流。这个程序可能使用了Apache Commons Collections库。
攻击者构建恶意对象:
攻击者创建了一个特制的序列化对象,这个对象在反序列化时会触发执行代码的逻辑。
利用的是Apache Commons Collections中的
InvokerTransformer
类,这个类可以调用任意方法。攻击者将其配置为执行恶意代码,比如运行一个外部命令。
序列化并发送对象:
这个恶意对象被序列化为字节流,并发送到目标Java应用程序。
目标应用反序列化:
Java应用程序接收到这个对象,并开始反序列化过程。
在反序列化过程中,
InvokerTransformer
被触发,执行了预设的恶意代码。
漏洞分析
这个漏洞的关键在于InvokerTransformer
类的transform
方法可以用来执行任意代码,而在反序列化过程中,对象的方法会自动执行。因此,攻击者可以构造一个当被反序列化时会自动执行恶意操作的对象。
防御措施
限制反序列化:在Java应用中,应限制或完全避免反序列化来自不可信源的数据。
使用安全库:使用更新的安全库,例如更新的Apache Commons Collections版本,这些版本已修复了此类漏洞。
使用对象白名单:在反序列化时,应用程序应该实现白名单机制,只允许预定义的安全类被反序列化。
远程命令执行
PHP中
exec
- 执行一个外部程序passthru
- 执行外部程序并且显示原始输出proc_open
- 执行一个命令,并且打开用来输入/输出的文件指针shell_exec
- 通过shell 执行命令并将完整的输出以字符串的方式返回system
- 执行外部程序,并且显示输出
Python中
os.system()
- 执行系统指令os.popen()
- popen()方法用于从一个命令打开一个管道subprocess.call()
- 执行由参数提供的命令
Java中
Runtime.getRuntime().exec()
ProcessBuilder()
RCE与系统的交互
用户 系统调用内核态
(进程是资源分配的基本单位与程序独立运行的载体)
要深入理解RCE,我们需要探讨操作系统如何处理来自用户程序的请求,特别是涉及权限和安全性的方面。
用户态与内核态
操作系统有两种运行模式:用户态和内核态。用户态限制了程序对关键系统资源的访问,以防止用户程序直接与硬件交互,可能导致系统不稳定或不安全。内核态则允许操作系统内核访问和控制硬件。
系统调用的作用
系统调用是用户态与内核态之间的桥梁。当用户程序需要执行如文件操作、网络通信等需要更高权限的任务时,它会通过系统调用请求操作系统内核执行这些任务。系统调用是一种受控的机制,它确保了即使在执行这些高权限操作时,系统的安全性和稳定性也得到保护。
RCE与系统调用
RCE攻击通常涉及操纵系统调用来执行恶意代码。例如,在Linux系统中,攻击者可能会利用不安全的程序逻辑,通过注入恶意命令来控制exec()
系统调用,从而在受影响的系统上执行任意代码。
RCE漏洞的复杂性与跨语言特性
RCE漏洞的复杂性在于它们可以跨越不同的编程语言和应用架构。例如,一个Web应用的RCE漏洞可能起始于一个简单的PHP脚本注入,但最终可能导致在底层服务器的操作系统上执行恶意命令。
RCE的跨平台特性
RCE不局限于任何特定的编程语言或平台。它们可以出现在任何处理外部输入的程序中,从服务器端的Web应用程序(如PHP、Java、Python)到客户端的桌面应用程序(如C++或Java应用程序)。
syscall系统调用
操作系统将运行模式分为了 用户态和内核态 他们中间通过系统调用syscall来进行交互,用户态可以通过syscall来对内核态发起调用系统特权指令的请求,而请求的结果内核态可以通过syscall返回给用户态,最大程度保障了操作系统的安全稳定(用户空间通过向内核空间发出Syscall,产生[软中断]、而让程序陷入内核态,执行相应的操作)。
系统调用 和 用户自定义函数一样,不同的是,系统调用运行在内核态,而用户自定义函数运行在用户态 由于某些指令(如设置时钟 关闭 打开 和I/O操作)只能运行在内核态,所以操作系统必须提供一种能够进入内核态的方式,系统调用便是这样的机制,他提供了用户态和内核态交互的接口。
比如在C 语言中:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main() {
// 打开文件
int file_descriptor = open("example.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (file_descriptor == -1) {
perror("Error opening file");
return 1;
}
// 写入文件
const char *text = "Hello, System Call!";
ssize_t bytes_written = write(file_descriptor, text, strlen(text));
if (bytes_written == -1) {
perror("Error writing to file");
close(file_descriptor);
return 1;
}
// 关闭文件
close(file_descriptor);
return 0;
}
在这个例子中,open
、write
和close
等函数都是系统调用。open
用于打开文件,write
用于写入文件,close
用于关闭文件。这些函数提供了对底层文件系统的访问,涉及到了内核态的操作。
在运行时,程序将使用syscall
指令触发从用户态到内核态的切换,执行相应的系统调用。这里的例子演示了如何创建一个文件并写入内容,是系统调用在文件操作中的简单应用。
系统调用 fork与execve
进程调用 fork(创建新的进程) execve (可以灵活的调用与管理进程)。
系统调用fork是创建一个新的进程,与父进程共享资源但双方的进程任务互不影响,判断他们可以看返回值和uid。父进程返回子进程的ID 子进程返回0 ,而如果子进程被意外终结 可以等待父进程进行收回,如果不成功,则是僵死进程,但如果是父进程被终结,那么他的孤儿进程会被系统ID为1的进行收回。
比如:当用户输入的是bash -c whoami
时,流程是这样的:
用户输入的是bash -c whoami 而内核态执行的是bash(pid:52350) --> sys\_fork --> bash(pid:52796) --> sys\_execve --> /bin/whoami(pid:52796)
而详细过程是这样的:
bash -c: 这部分命令告诉系统使用bash来执行一段特定的代码。
whoami: 这部分命令是
bash -c
要执行的具体代码,它将返回当前执行这段代码的用户的用户名。
下面是更详细化的执行过程:
用户输入:
bash -c whoami
Shell解析:Shell(用户界面)解析用户输入,发现
bash -c
是一条命令,并理解后面的whoami
是bash -c
要执行的代码。创建新进程(bash):Shell 使用系统调用
fork
创建一个新的进程(子进程),让子进程执行bash
。新进程(bash)的PID(Process ID)为52796。
执行新进程(bash):子进程(bash)开始执行,解释
bash -c whoami
。创建新进程(whoami):在
bash -c
的执行过程中,bash
发现后面要执行whoami
,于是使用系统调用fork
再创建一个新的进程(子进程),让这个新进程执行whoami
。新进程(whoami)的PID为52796。
执行新进程(whoami):子进程(whoami)开始执行,它是
bash -c
的一个子过程,执行的是whoami
命令。系统调用(execve):子进程(whoami)调用
execve
系统调用来执行/bin/whoami
。加载程序:
execve
系统调用加载/bin/whoami
程序到子进程(whoami)的内存空间。执行程序:子进程(whoami)开始执行
/bin/whoami
程序。返回结果:
/bin/whoami
程序执行完成后,将结果返回给子进程。退出子进程(whoami):子进程(whoami)执行完毕后退出。
返回结果给父进程(bash):子进程(whoami)的执行结果返回给了父进程(bash)。
退出父进程(bash):父进程(bash)等待子进程(whoami)执行完毕后,也退出。
这个过程涉及了两次进程的创建和两次程序的执行,使用了fork
和execve
等系统调用。
最终,用户输入的bash -c whoami
在内核态得到执行,返回了当前用户的用户名
liunx的进程设计到了很多东西 文件的创建 命令的调用,而进程又涉及到了权限的问题,权限分为用户态和内核态,所以大多数进程的调用与创建会和系统调用联合一起进行使用,从而提高系统的灵活稳定性。
# RCE漏洞的原理与成功执行的条件
## Shell 与 RCE 漏洞
Shell作为用户与操作系统交互的接口,在RCE漏洞的产生中扮演了关键角色。当应用程序通过shell执行外部命令时,如果这些命令包括未经过滤或转义的用户输入,就可能导致RCE漏洞。这种情况下,恶意构造的输入被shell解释执行,从而允许攻击者运行任意代码。
### Fork-Execve过程与RCE
在Unix和类Unix系统中,进程的创建和命令的执行通常通过
fork()
和execve()
系统调用实现。当应用程序以拥有shell权限的用户身份运行,并使用这些系统调用执行外部命令时,恶意输入的拼接可能触发RCE漏洞。相比之下,直接使用execve()
执行命令通常更安全,因为它不会创建新的shell进程,从而减少了恶意输入被解释执行的可能性。### PHP的特殊情况
在PHP中,许多执行外部命令的函数实际上是通过调用
sh -c
来执行命令的。这增加了RCE的风险,因为它为恶意输入提供了一个直接的执行路径。## 受限情况下的RCE利用
在受限的环境中,例如沙箱环境、受限的服务器配置或权限受限的账户,RCE的实现更具挑战性。在这些环境中,攻击者可能无法直接执行任意命令,但仍可以通过利用现有进程或应用程序的特定功能和参数来实现RCE。
### 示例:利用Curl进行RCE
在一个只允许执行
curl
命令的沙箱环境中,攻击者可能利用curl
的功能和shell的命令替换来绕过限制。例如,通过构造如下命令:bashCopy code curl https://example.com/file.txt -o >(cat)
在这个示例中,
>(cat)
是一种命令替换的语法,它允许将cat
命令的输出结果作为文件路径传递给-o
参数。这种方法可以被用来绕过写入文件的限制,直接在标准输出上显示下载的内容,从而在受限环境中实现间接的RCE。(cat) 的命令替换: >(cat) 是一种命令替换的语法。在这个上下文中,它的作用是将 cat 命令的输出结果作为文件路径传递给 -o 参数。
构造的目的是在沙箱环境中绕过将内容写入文件的限制,而是直接输出到标准输出,使得你可以在沙箱环境中查看下载的内容,而无需直接写入文件。
所以,当我们不能执⾏任意进程的时候(我们的输⼊只是某个特定进程的输⼊的时候),突破口取决的使⽤的进程程序本身的参数是不是能利用。