0x01 引言
RASP(Runtime application self-protection)運行時應用自我保護罕扎,是一種植入到應用程序內(nèi)部或其運行時環(huán)境的安全技術(shù)。RASP可將自身注入到應用程序中,與應用程序融為一體,實時監(jiān)測记焊、阻斷攻擊,使程序自身擁有自保護的能力终吼。
從實時監(jiān)測烛愧、阻斷攻擊
這兩個詞看好像與waf(Web Application Firewall,web應用防護系統(tǒng))很像希痴,那它們有什么不同之處呢者甲?RASP到底是做什么的?實現(xiàn)原理是什么润梯?一場”修行”又開始了过牙。
0x02 RASP vs WAF
1、簡介
-
WAF
Web應用防火墻是通過執(zhí)行一系列針對HTTP/HTTPS的安全策略來專門為Web應用提供保護的一款產(chǎn)品纺铭。
聊點陽間的東西寇钉,waf就好像“WEB應用小區(qū)”的門衛(wèi),門衛(wèi)手里有個小冊子(俗稱特征庫:存放以往來小區(qū)做壞事的壞蛋特征如壞蛋總是戴頭套)舶赔。當小區(qū)外邊人要進小區(qū)串門的時候扫倡,它就會攔下來問:來者何人?并根據(jù)這個人的特征和手里的小冊子核對,如果沒有“在案”的壞蛋特征撵溃,就一律放行疚鲤。
從壞人的角度看,似乎這樣的門衛(wèi)并非無懈可擊缘挑,壞人可以換裝集歇、整容等等手段去騙過門衛(wèi)。
-
RASP
RASP(Runtime application self-protection)運行時應用自我保護语淘,是一種植入到應用程序內(nèi)部或其運行時環(huán)境的安全技術(shù)诲宇。RASP可將自身注入到應用程序中,與應用程序融為一體惶翻,實時監(jiān)測姑蓝、阻斷攻擊,使程序自身擁有自保護的能力吕粗。
聊點陽間的東西纺荧,相當于在小區(qū)里每家每戶都安排了一位管家,無事不登三寶殿颅筋,壞人進入小區(qū)肯定是要干壞事的宙暇,所以RASP思路就是:你戴不戴頭套我不管,就看你做不做出格(攻擊動作)的事议泵。
就好像客给,越獄的方法千萬種,總要離開牢房肢簿;撩妹的手段永遠在變花樣靶剑,但目標都是...
2、部署&產(chǎn)品特性
-
WAF
外部邊界入口統(tǒng)一部署
支持串聯(lián)池充、旁路桩引、反向代理三種方式部署
容易形成單點故障,影響面大
簡單說就是收夸,如果小區(qū)門口門衛(wèi)今天喝多了坑匠,可能在門口睡著了導致小區(qū)的人不管好的壞的都進不去。
-
RASP
服務器上單獨部署卧惜,嵌入在應用程序內(nèi)部厘灼,應用代碼無感知
與開發(fā)語言強相關(guān),但防護插件可共用
產(chǎn)品特性對比如下:
3咽瓷、性能&檢測能力
-
WAF
規(guī)則越多设凹,匹配時對性能消耗就越大
和硬件配置相關(guān)
對服務器CPU無影響
業(yè)務報文多一次轉(zhuǎn)發(fā),延遲變大
-
RASP
只在關(guān)鍵點檢測茅姜,不是所有請求都匹配所有規(guī)則
對服務器CPU性能有消耗
非防護狀態(tài)延遲增大3-5%闪朱,防護狀態(tài)延遲增大4.6 – 8.9%
檢測能力對比如下:
百度Openrasp 官方檢測能力說明:https://rasp.baidu.com/doc/usage/web.html
4、優(yōu)劣分析
接著聊小區(qū)門衛(wèi)與管家的事兒,列舉幾個關(guān)鍵點:
1)門衛(wèi)手里有個小冊子(俗稱特征庫:存放以往來小區(qū)做壞事的壞蛋特征如壞蛋總是戴頭套)
Waf誤報多奋姿,壞蛋總是戴頭套锄开,但不一定戴頭套的全是壞人。
Waf維護成本高称诗,小冊子需要不斷更新添加新的特征萍悴。
Waf漏報多容易被繞過,小冊子要在發(fā)生偷盜事件后再去更新特征比較被動寓免,而且壞人騙過門衛(wèi)的手段太多了退腥,換衣服、化妝再榄、整容等等。
2)RASP 相當于在小區(qū)里每家每戶都安排了一位管家&你戴不戴頭套我不管享潜,就看你做不做出格(攻擊動作)的事
Rasp檢測更全面精準困鸥,每家每戶都安排一位管家肯定比小區(qū)門口的門衛(wèi)更了解每家每戶的情況。
Rasp漏洞響應更快可預防未知漏洞剑按,不過多新的偷盜方法肯定要有把錢裝到口袋的動作疾就,不管是0day還是Nday都要獲取權(quán)限執(zhí)行命令。
Rasp消耗服務器資源多艺蝴,Waf是一個門衛(wèi)放小區(qū)門口就行猬腰,Rasp可能需要N多個管家安排到每家每戶。
Rasp技術(shù)棧太多時使用不方便猜敢,需要根據(jù)業(yè)務不同開發(fā)語言姑荷、開發(fā)不同語言的探針。
0x03 以O(shè)penRasp為例了解實現(xiàn)原理
經(jīng)過簡單的對比我們已經(jīng)對RASP有了一定了解缩擂,那它是怎么實現(xiàn)的呢鼠冕?下面以百度的OpenRasp 為例看看它的實現(xiàn)原理。
閱讀官方文檔胯盯,發(fā)現(xiàn)啟動方式為:
java -javaagent:/opt/spring-boot/rasp/rasp.jar -jar XXX.jar
java -h
-javaagent:<jar 路徑>[=<選項>]
加載 Java 編程語言代理, 請參閱 java.lang.instrument
發(fā)現(xiàn)是用到了探針技術(shù)懈费,原理見:
了解完Java 探針的原理后我們來簡單看下OpenRasp的實現(xiàn)原理,可以發(fā)現(xiàn)其入口同樣是premain
或agentmain
方法博脑,并且都會在pox.xml中標注:
<!--pom.xml-->
<manifestEntries>
<Premain-Class>com.baidu.openrasp.Agent</Premain-Class>
<Agent-Class>com.baidu.openrasp.Agent</Agent-Class>
<Main-Class>com.baidu.openrasp.Agent</Main-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
// 所以入口就是 com.baidu.openrasp.Agent
/**
* 啟動時加載的agent入口方法
*
* @param agentArg 啟動參數(shù)
* @param inst {@link Instrumentation}
*/
public static void premain(String agentArg, Instrumentation inst) {
init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
}
/**
* attach 機制加載 agent
*
* @param agentArg 啟動參數(shù)
* @param inst {@link Instrumentation}
*/
public static void agentmain(String agentArg, Instrumentation inst) {
init(Module.START_MODE_ATTACH, agentArg, inst);
}
繼續(xù)往下看憎乙,在com.baidu.openrasp.transformer.CustomClassTransformer.java
為Instrumentation注冊了transformer
,從此之后的類加載都會被Transformer攔截。
public CustomClassTransformer(Instrumentation inst) {
this.inst = inst;
inst.addTransformer(this, true); // 注冊Transformer
addAnnotationHook(); // 加載所有hook點
}
接著我們來看攔截后的操作:
/**
* 過濾需要hook的類叉趣,進行字節(jié)碼更改
*
* @see ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (loader != null) {
DependencyFinder.addJarPath(domain);
}
if (loader != null && jspClassLoaderNames.contains(loader.getClass().getName())) {
jspClassLoaderCache.put(className.replace("/", "."), new SoftReference<ClassLoader>(loader));
}
for (final AbstractClassHook hook : hooks) {
if (hook.isClassMatched(className)) {
CtClass ctClass = null;
try {
ClassPool classPool = new ClassPool();
addLoader(classPool, loader);
ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
if (loader == null) {
hook.setLoadedByBootstrapLoader(true);
}
classfileBuffer = hook.transformClass(ctClass);
if (classfileBuffer != null) {
checkNecessaryHookType(hook.getType());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
}
}
serverDetector.detectServer(className, loader, domain);
return classfileBuffer;
}
可以看到在這里hook.isClassMatched(className)
檢測當前攔截的類是否為已經(jīng)注冊的hook的類泞边,如果是的話則利用javassist的方法創(chuàng)建ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
, javassist 與之前文章中提到的ASM相似都是修改字節(jié)碼的工具,想了解的可以的官方搜一下使用方法疗杉。
在這里classfileBuffer = hook.transformClass(ctClass);
獲取處理后的字節(jié)碼繁堡,展開看下代碼:
/**
* 轉(zhuǎn)化目標類
*
* @param ctClass 待轉(zhuǎn)化的類
* @return 轉(zhuǎn)化之后類的字節(jié)碼數(shù)組
*/
public byte[] transformClass(CtClass ctClass) {
try {
hookMethod(ctClass);
return ctClass.toBytecode();
} catch (Throwable e) {
if (Config.getConfig().isDebugEnabled()) {
LOGGER.info("transform class " + ctClass.getName() + " failed", e);
}
}
return null;
}
這里直接調(diào)用具體hook類的hookMethod(ctClass);
方法來執(zhí)行具體的邏輯,完成字節(jié)碼的生成及寫入,如OgnlHook#hookMethod
代碼示例如下:
/**
* (none-javadoc)
*
* @see com.baidu.openrasp.hook.AbstractClassHook#hookMethod(CtClass)
*/
@Override
protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
String src = getInvokeStaticSrc(OgnlHook.class, "checkOgnlExpression",
"$_", Object.class);
insertAfter(ctClass, "topLevelExpression", null, src);
}
/**
* struct框架ognl語句解析hook點
*
* @param object ognl語句
*/
public static void checkOgnlExpression(Object object) {
if (object != null) {
String expression = String.valueOf(object);
if (expression.length() >= Config.getConfig().getOgnlMinLength()) {
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("expression", expression);
HookHandler.doCheck(CheckParameter.Type.OGNL, params);
}
}
}
getInvokeStaticSrc
用于獲取調(diào)用靜態(tài)方法的代碼字符串椭蹄,然后通過insertAfter
在目標類的目標方法的出口插入相應的源代碼闻牡,從而完成一次對字節(jié)碼的修改操作。
今天就先到這兒吧绳矩,要去團建...感興趣的朋友也可以看下這篇文章實際操作一遍罩润。
https://mp.weixin.qq.com/s/LtoDe353uXPA8oT2D9FE8A
參考鏈接:
http://blog.nsfocus.net/rasp-tech/
http://blog.nsfocus.net/openrasp-tech/
https://www.freebuf.com/articles/network/167166.html // 文章比喻的寫法是參考這篇文章去寫的,很有意思建議大家看下原文