视频
made by Mitunlny(M.Y)
参考文章:
RCE漏洞概述
什么是RCE——基本概念
远程代码/命令执行漏洞(Remote Code/Command Execute,简称RCE)是Web安全领域中危害性极高的漏洞类型,主要分为两类:
- 代码执行漏洞:针对后端编程语言(如PHP、Java、Python等)的漏洞
- 命令执行漏洞:针对操作系统层面的漏洞
本篇仅涉及PHP的代码执行漏洞
RCE漏洞成因
当开发人员为了实现功能灵活性,在代码中调用动态执行函数时,若未对用户输入进行严格过滤,攻击者就可以构造恶意输入执行任意代码或系统命令。这种漏洞通常出现在以下场景:
- 使用字符串转代码函数(如eval())
- 调用系统命令函数(如system())
- 动态包含文件功能
- 模板引擎解析
相关函数
命令执行
system()
|
|
exec()
|
|
popen()
|
|
proc_open()
|
|
passthru()
|
|
shell_exec()
|
|
反引号 `
|
|
pcntl_exec()
|
|
代码注入
eval()
|
|
assert()
|
|
preg_replace()
|
|
create_function()
|
|
array_map()
|
|
call_user_func()
|
|
call_user_func_array()
|
|
array_filter()
|
|
uasort()
|
|
例题
- https://www.nssctf.cn/problem/424
- https://www.nssctf.cn/problem/383
- https://www.nssctf.cn/problem/425
- https://www.nssctf.cn/problem/3090
绕过方式
空格
|
|
命令分隔符
|
|
关键字
假如过滤了关键字cat\flag,无法读取不了flag.php,又该如何去做
拼接绕过
|
|
编码绕过
|
|
单引号和双引号绕过
|
|
反斜杠绕过
|
|
通过$PATH绕过
|
|
通配符绕过
- \[…\]表示匹配方括号之中的任意一个字符
- {…}表示匹配大括号里面的所有模式,模式之间使用逗号分隔。
- {…}与\[…\]有一个重要的区别,当匹配的文件不存在,\[…\]会失去模式的功能,变成一个单纯的字符串,而{…}依然可以展开
|
|
例题
- https://www.nssctf.cn/problem/439
- https://www.nssctf.cn/problem/467
- https://www.nssctf.cn/problem/1095
- https://ctf.xidian.edu.cn/training/19?challenge=779
无字母数字RCE
技术原理与背景
无字母数字RCE是指在不使用任何字母(a-z,A-Z)和数字(0-9)的情况下实现代码执行的技巧。这种技术在以下场景特别有用:
- 过滤了所有字母和数字的CTF题目
- 严格的WAF规则过滤了常规payload
- 需要绕过字符限制的实战环境
例如:
|
|
思路:
利用各种非数字字母的字符,经过各种变换(异或,取反,自增),构造出单个的字母字符,然后把单个字符拼接成函数名,进行动态执行。
核心实现方法
异或
php7有一种特性叫做函数的动态调用,即在字符串上加一个括号就可以调用和字符串同名的函数
例如:
|
|
而其中的字符串可以是任意形式,例如拼接:("php"."info") 或者运算的结果
异或运算,本质是得到这样的字符串然后括起来,进行命令操作
这里的异或,指的是php按位异或,在php 中,两个字符异或后,得到的依然是一个字符!
例如:将字符“A”和“?”进行异或:
|
|
得到:~
它是如何计算的呢,过程如下:
首先将 A 和 ? 分别转换为对应的ASCII码,A变为65,?变为63
然后将其转换为对应的二进制数,A变为1000001,?变为111111 接下来就进行运算,异或的运算规则是相同为0,不同为1
|
|
接下来将其二进制转换为对应十进制数,1111110对应的十进制数为126,根据ASCII码表可知126对应的是~,所以这个时候得到的字符就是~ 因此,我们利用这种思路,就可以借助异或构造payload。
取反
取反其实是利用了不可见字符,我们对一个字符进行两次取反,得到的还是其本身。当我们进行一次取反过后,对其进行URL编码,再对其进行取反,此时可以得到可见的字符,它的本质其实还是这个字符本身,然后因为取反用的多是不可见字符,所以这里就达到了一种绕过的目的
取反运算符(~)的作用
在PHP中,~是按位取反运算符,它对一个数的每一位二进制位进行取反操作(0变1,1变0)9。例如:
|
|
取反绕过的核心是:
- 将想要执行的PHP代码进行取反操作
- 将取反后的结果进行URL编码
- 通过eval等函数执行取反后的代码
因为取反后的字符大多是不可见字符,可以绕过基于正则表达式的字符过滤。
具体实现步骤
基本payload构造:
假设我们要执行 phpinfo();,可以按照以下步骤构造payload:
- 对”phpinfo”进行取反:
|
|
- 对取反结果进行URL编码:
|
|
- 最终payload格式:
|
|
注意括号的使用:取反部分要用括号括起来,最后的分号不能少5。
以CTF题目为例,当遇到如下过滤条件时:
|
|
我们可以使用取反绕过:
|
|
可以使用以下PHP代码生成取反payload:
|
|
自增
什么是自增绕过?
自增绕过是一种在PHP代码中,当字母和数字被严格过滤时,通过利用PHP的类型转换和自增运算符(++)特性来构造所需字符的技术。它允许攻击者在无法直接输入字母数字的情况下,逐步”构建”出执行命令所需的字符7。
基本行为:
在PHP中,自增运算符有两种形式:
- 前置自增(
++$a):先增加变量的值,然后返回新值 - 后置自增(
$a++):先返回变量的当前值,然后增加变量的值
例如:
|
|
为什么自增可以用于RCE绕过?
当Web应用过滤了所有字母和数字时,传统的RCE方法(如直接输入 system('ls'))无法使用。但PHP中:
- 空数组
[]可以转换为字符串’Array’ - 字符串可以通过自增逐步构建出需要的字符
- 利用这些特性可以构造出函数名和参数7
核心原理:
- 从空数组开始构建字符串
|
|
- 利用自增构建字符
PHP中字符串的自增遵循字母表顺序:
|
|
- 构建完整函数调用
通过逐步自增,可以从空字符串开始构建出如 assert、system等关键函数名,以及执行命令所需的参数7。
Payload解析
下面是一个典型的自增绕过Payload,用于执行 assert($_POST[_]):
|
|
使用时配合POST参数:_=phpinfo();即可执行任意PHP代码7。
优缺点:
优点:
- 完全不依赖字母和数字字符
- 在严格过滤环境下仍能工作
- 可以构建任意函数名和参数
缺点:
- Payload通常较长且复杂
- 需要PHP环境支持字符串自增行为
- 对PHP版本有一定要求(某些版本行为可能不同)
无参数RCE
无参RCE是一种特殊的代码执行漏洞,攻击者可以在不直接传递参数的情况下,通过构造特定的函数调用链来实现远程代码执行。以下是关于PHP无参RCE的详细介绍:
成因
PHP无参RCE的成因主要与以下几个方面有关:
- 代码逻辑缺陷:某些PHP代码可能会对用户输入进行不安全的处理,例如使用
eval()函数直接执行用户输入的代码,而没有对输入进行严格的过滤 - 正则表达式绕过:某些情况下,开发者可能会使用正则表达式来过滤用户输入,但攻击者可以通过构造特定的函数调用链来绕过这些过滤
- PHP函数特性:PHP中存在一些无参数的函数或可以通过特定方式构造参数的函数,这些函数可以被攻击者利用
利用方式
利用PHP无参RCE的关键在于构造无参数的函数调用链,以实现代码执行。以下是一些常见的利用方式:
-
利用无参数函数:
scandir():获取当前目录下的文件列表localeconv():返回包含本地数字及货币格式信息的数组,可以用于构造特定的字符current()、pos():获取数组的第一个值array_rand()、array_reverse():操作数组以获取特定值
-
构造文件路径:
- 通过
getcwd()、dirname()等函数构造文件路径,结合scandir()获取目标文件
- 通过
-
读取文件内容:
- 利用
highlight_file()、show_source()、readfile()等函数读取文件内容
- 利用
-
命令执行:
- 使用
eval()、assert()等函数执行代码
- 使用
示例
以下是一个典型的无参RCE场景:
|
|
攻击者可以通过构造类似以下的 exp参数来绕过过滤并执行代码:
|
|
通过 current(localeconv())构造小数点,进而调用 scandir()获取当前目录下的文件列表
PHP相关函数
附:PHP官方文档的函数介绍:https://www.php.net/manual/zh/indexes.functions.php
HTTP相关函数
- 功能:获取所有HTTP请求头,返回关联数组
- 利用:从请求头中获取可控数据
|
|
get_defined_vars()
- 功能:返回所有已定义变量的多维数组
- 利用:获取GET/GET/_POST等超全局变量
|
|
会话控制函数
session_id()
- 功能:获取/设置当前会话ID
- 利用:通过设置恶意session ID执行代码
|
|
session_start()
- 功能:启动新会话或重用现有会话
- 利用:配合session_id()使用
本地化函数
localeconv()
- 功能:返回本地化数字格式信息
- 关键利用:返回的数组第一个元素是小数点(.)
|
|
文件系统函数
scandir()
- 功能:列出目录中的文件和目录
- 利用:目录遍历核心函数
|
|
chdir()
- 功能:改变当前目录
- 利用:配合scandir()跳转目录
|
|
getcwd() / grtpwd()
- 功能:获取当前工作目录
- 利用:获取路径信息
|
|
dirname()
- 功能:返回路径中的目录部分
- 利用:目录跳转
|
|
数组操作函数
current() / pos()
- 功能:返回数组当前元素
- 利用:获取数组第一个元素
|
|
array_reverse()
- 功能:返回逆序数组
- 利用:改变数组顺序
|
|
array_flip()
- 功能:交换键值
- 利用:改变数组结构
|
|
array_rand()
- 功能:随机返回数组键名
- 利用:随机选择文件
|
|
next() / prev() / end()
- 功能:移动数组指针
- 利用:遍历数组
|
|
输出函数
print_r()
- 功能:打印易读的变量信息
- 利用:输出数组内容
|
|
show_source() / highlight_file()
- 功能:语法高亮显示文件源码
- 利用:读取文件
|
|
readfile() / file_get_contents()
- 功能:读取文件内容
- 利用:读取flag
|
|
编码转换函数
chr() / ord()
- 功能:ASCII码与字符互转
- 利用:构造特定字符
|
|
hex2bin()
- 功能:十六进制转二进制
- 利用:构造字符串
|
|
urldecode()
- 功能:URL解码
- 利用:解码特殊字符
|
|
数学函数
phpversion()
- 功能:获取PHP版本
- 利用:判断环境
|
|
rand() / time()
- 功能:随机数/时间戳
- 利用:生成临时数据
|
|
localtime()
- 功能:返回本地时间数组
- 利用:获取数字
|
|
经典利用组合
读取当前目录文件
|
|
利用HTTP头执行代码
|
|
构造命令执行
|
|
目录遍历技巧
|
|
Session ID利用
|
|