保命前言

RASP及其对抗我上谷歌查了很多资料, 这方面的文章很少, 都是what is RASP这种科普文章, 很难找到比较深入的文档和资料, 所以有的少许地方是我自己思(y)考(y)得出的一些东西, 可能导致很多地方写的不是很合适, 或者有错误, 希望有师傅看到了可以指出。
RASP概述

RASP (Runtime application self-protection): 运行状态应用自我保护
RASP是一种新型的应用程序保护技术, 它通过将保护程序注入到应用程序中, 与应用程序融为一体来使得应用程序获得自我防护的能力。它会拦截检测所有的系统调用, 当应用程序受到攻击时, 可以实时检测并阻断攻击行为, 应用程序相当于拥有了自我保护能力, 有文章将其生动的称为"疫苗"。
(1) 运行在程序内部
(2) 检测点位于应用程序的输入与输出位置
(3) 输入点: 用户请求、文件输入等
(4) 输出点: 数据库系统、网络系统、文件系统等
(1) 误报率:
WAF的误报率相对较高, WAF一般是根据规则匹配来进行判断, 虽然现在WAF对于攻击的识别越来越精准, 我们在渗透过程中经常会触发WAF的拦截机制, 但是为了保证安全性, 很多正常行为也会在阴差阳错下被WAF报警, 有一种宁杀错不放过的感觉。
(2) 准确率:
WAF部署在web的边界入口, 对于某些攻击还比较依赖于特征库, 而bypass waf是老生常谈的问题, 各种混淆绕过层出不穷, 而RASP是根据行为来分析 (也检测流量和内容)。因为RASP运行在程序内部, 监测点位于输入输出口, 因此RASP能够了解到前后发生了什么, 它可以结合上下文对行文进行分析处理, 这对于误报和准确都有所帮助。
(3) 性能与效率:
WAF处在 边界入口对所有的输入流量进行检测, 相关规则库越大, 效率会越低, 而RASP在关键点进行检测, 检测输入输出和数据逻辑, 不会对所有的请求做检测, 好钢用在刀刃上。但是因为RASP是实时拦截检测, 因此对于CPU有一定负担, 总体来说, 性能方面RASP处于劣势, 目前正在改善。
(4) 语言情况:
WAF和RASP理论上都不受限制, 但RASP不同的语言需要单独开发探针。
(5) 使用场景
WAF针对web应用程序, RASP可以应用在web应用程序和非web应用程序。
Joseph Feinman 非常精确的把防火墙比喻为簇拥在大人物周围的保镖,大人物去哪里都带着保镖,看起来防御力爆棚,但是大人物本人肌肉不发达也没有武功,一旦保镖被突破或者保镖被调虎离山,那么这个大人物就没有任何保护了,就非常危险了。RASP 可以让没有武功的人在很短的时间并且付出的代价不高的情况下拥有很高的自我保护能力 (这不是美队吗???)
总结下来RASP有点像杀软哈哈。
RASP浅析

一 腾讯TRASP (PHP)
TRASP架构示意图:
TRASP主要分为三个模块:
(1) 客户端模块: 负责数据采集和规则匹配
(2) 云端数据分析模块: 前端规则无法完全解决问题, 会上传数据到云端进行二次分析
(3) 安全处置模块: 配置事件安全等级和处置策略 (减少误报)
PHP RASP是一个动态库so文件, 作为PHP解释器的扩展。它与mysql.so的加载方式和运行原理一样。
PHP实例会经过四个过程, RASP的预加载在第一阶段的Module init阶段实现。在初始化阶段预加载RASP模块后, 替换opcode handler以及用自定义函数替换全局函数表位置。
去改变opcode对应的处理函数, 替换后就可以自定义函数调用前后的操作, 还可以跳回原来的opcode执行。
PHP内置了opcode handler替换函数: zend_user_opcode_handlers
先看这一段代码, 我们对ZEND_ECHO这条OP指令hook
zend_set_user_opcode_handler(ZEND_ECHO, hook_echo_handler);
复制
这时当PHP执行ZEND_ECHO (调用echo输出)的时候, 会先调用我们的handler函数。
具体看一下代码细节:
ZEND_API int zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)
{
if (opcode != ZEND_USER_OPCODE) {
if (handler == NULL) {
/* restore the original handler */
zend_user_opcodes[opcode] = opcode;
} else {
zend_user_opcodes[opcode] = ZEND_USER_OPCODE;
}
zend_user_opcode_handlers[opcode] = handler;
return SUCCESS;
}
return FAILURE;
}
复制
PHP内核中, 每个OP操作都有一个固定的handler函数处理, opcode_handler_t表示了对应的handler函数具体是哪一个。
PHP内置了一个zend_user_opcode_handlers表, 这里直接去进行替换。
PHP有一个全局函数表, 包含了所有的内置函数, 因此我们可以在全局函数表中寻找zend_function结构体 (可以通过CG(function_table)获取), 然后替换handler为我们自己的。
typedef void (*php_func)(INTERNAL_FUNCTION_PARAMETERS);
static void php_taint_override_func(const char *name, php_func handler, php_func *stash) /* {{{ */ {
zend_function *func;
if ((func = zend_hash_str_find_ptr(CG(function_table), name, strlen(name))) != NULL) {
// 原始函数指针备份
if (stash) {
*stash = func->internal_function.handler;
}
// 替换为新的函数
func->internal_function.handler = handler;
}
}
复制
两种方式皆可实现Hook函数,有各自的优点。第一种,适合工程化,只需要对几种Opcode类型进行hook,后续Hook的敏感函数可以自行添加;第二种则是需要对敏感函数进行逐一替换,比较繁琐,但优点是替换发生在函数表内,故通过越过Opcode进行函数执行的手法也无法绕过检测,更加可靠。
二 Java RASP
Java从编译到运行流程图 :
最后一部分是.class文件进入jvm中解释执行, 我们的RASP需要能修改.class文件并且在jvm解释执行的时候注入保护程序。
Java中有Javaassist和ASM可以对字节码进行修改, 从而注入程序。
Javaagent: 运行main之前的拦截器, 可以在服务启动时, 动态的修改Java字节码, 对敏感函数hook。代理程序入口需要有名为premain的静态方法。
(1) 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
(2) Premain-Class 指定的那个类必须实现 premain() 方法。
premain方法是运行main函数之前的类, Java虚拟机启动时, 执行main函数之前JVM会先执行 -javaagent 指定的jar包内 Premain-Class 这个类的 premain 方法。
为了不误人子弟, 这里写的很粗糙, 而且也只是为了大致说明原理是一样的这个意思, 这里我们也确实能看出来, 不管是什么语言, RASP的具体实现原理都是一样的, 只是不同语言我们去hook, 去注入保护程序的过程、语法不一样, 大家可以仔细对比一下PHP和Java的实现。
RASP对抗

假设RASP hook了以下函数
system()
exec()
shell_exec()
...
复制
那我们可以通过测试未hook的函数来绕过检测机制实现命令执行
proc_open()
pcntl_exec()
COM组件
...
复制
前面我们对比过RASP和WAF的区别, 我们知道相比于RASP, WAF更加依赖与规则匹配, RASP在hook函数之后对函数的参数会更加上心, 这时我们可以尝试对命令进行混淆。
whoami
复制
混淆后类似如下 (广告插入哈哈)
^"""""""""""c^"""""""""""m^"""""""""""^"""^"""d;;;,;LpSq,006I,5;b;",;",;",;";;",";",;",;,;/C "s^e^t I_am_ecat_Welcome_To_Use_My_Framework=mas2#$6D46wmas2#$6D46hmas2#$6D46omas2#$6D46amas2#$6D46mmas2#$6D46imas2#$6D46 & echo %I_am_ecat_Welcome_To_Use_My_Framework:mas2#$6D46=% | c"""""""""""m""""d"""""""""""
复制
LD_PRELOAD是Linux的一个用于动态库加载的环境变量, 它的动态加载优先级最高, 它允许你定义程序运行前优先加载的动态链接库。
putenv()
(PHP 4, PHP 5, PHP 7, PHP 8)
Sets the value of an environment variable
putenv(string $assignment): bool
Returns true on success or false on failure
examp:
<?php
putenv("UNIQID=$uniqid");
?>
因此, 我们可以写一个恶意动态库链接文件, 这个文件中有我们目标劫持函数的同名函数, 因为优先级问题, 将优先调用我们的重写函数, 把文件上传之后用putenv函数劫持环境变量。
这里需要注意的一些点
(1) 需要能够上传动态链接库(.so文件)
(2) putenv函数未被禁用
(3) 劫持的函数需要启动子进程, 这样才会刷新环境变量
(4) 优先重写无参数的函数, 减少工作量
例如经常举例的mail函数, main()运行时会启动子进程去调用sendmail, sendmail又使用了getegid()函数, 那我们重写getegid()函数, 写入我们的恶意代码然后将其编译为.so文件, 上传之后通过putenv函数去LD_PRELOAD我们的自写动态链接库。
<?php
putenv("LD_PRELOAD=./getegid.so");
mail("", "", "");
?>
复制
此时恶意代码不属于PHP进程范畴, 因此不在RASP检测范围之内。
渗透中经常使用到的技巧, 除了解析为PHP脚本文件, 还可以AddHandler一个格式为cgi-script。
apache环境
mod_cgi可用
AllowOverride为All
.htsccess可写
外部函数接口, 是PHP7.4引入的一个扩展, 它让我们可以调用C语言库。
不在PHP RASP检测范围之内。
之前讲过PHP RASP对函数hook的两种方法, 都是重写函数然后将调用重定向到了我们的自写函数, 但是原函数依然存在, 我们可以利用堆溢出去在内存中找到原函数的函数地址。这个在网上有exp, 可以直接上传然后传参去执行命令。
结束语

接开头所说, 因为相关资料比较少, 我也没有跟其他师傅进行详细探讨, 所以本文在写的浅显的情况下也可能还会存在一些不当, 纰漏, 甚至是错误, 希望有所指教的师傅能提出来帮助改正, 对RASP有兴趣的师傅还可以去研究一下百度在GitHub开源的openRASP项目, 没记错应该是C++写的。