天天看點

Java中的RASP實作

RSAP

  RASP是Gartner公司提出的一個概念,稱:程式不應該依賴于外部元件進行運作時保護,而應該自身擁有運作時環境保護機制;

  RASP就是運作時應用自我保護(Runtime application self-protection)的縮寫,正如RASP字面意思一樣,這是運作在運作時的一種防護技能;也就是說RASP能夠在程式運作期間實施自我保護,監控與過濾有害資訊,還能夠擁結合程式的目前上下文實施精确、實時的防護;

Java中的RASP

  不嚴格來說Java是半編譯、半解釋型語言,我們也都知道Java中也有運作時(Runtime)那Java的運作時在哪呢?

  不急,我們先看看Java從編譯到運作的流程圖;

Java中的RASP實作

  上圖的流程為:Java編譯程式如Javac編譯.java源碼檔案,生成Java位元組碼檔案.class,接着.class檔案進入JVM中解釋執行; 從中我們可以看到Java的最後執行階段是在JVM中,也就可以說Runtime運作時是JVM的重要組成部分;除此之外我們還發現Java中實作RASP的幾個關鍵點:

    1、 我們的防護程式必須能夠分析、修改java的.class檔案;

    2、 必須在JVM解釋執行.class檔案時進行注入(Java Runtime);

  通過上面的分析我們知道了要實作Java的RASP所要具備的能力,然後我們發現在Java中有Javassist、與ASM可以實作對Java位元組碼的修改;有了修改.class位元組碼檔案的技能,還需要能夠在Java運作期間注入我們的防護程式,通過上面我們發現Java運作時是發生在JVM中,通過查找相關資料與JVM參數發現在JVM參數中有-javaagent參數配置Java代理可以在

運作時注入我們的防護程式;

Java RASP實作

  在上面的分析中我們發現隻要在JVM的-javaagent參數 中配置我們的保護程式,就能夠輕松實作Java的RASP;

  Java代理程式入口類需要有名為premain的靜态方法 ,還需要在jar的META-INF/MAINIFEST.MF檔案中包含 Premain-Class配置,下面是RASP保護程式的入口類;

  JavaRASPApp:

/**
 * @author linx
 * @date 2017-06-25
*/
public class JavaRASPApp {

  public static void premain(String agentArgs, Instrumentation instru) throws ClassNotFoundException, UnmodifiableClassException {
    System.out.println("premain");
    instru.addTransformer(new ClassTransformer());
  }

}
           

  ClassTransformer類實作了Java的代理程式機制提供的ClassFileTransformer接口 ,能夠在運作時(Runtime)對類的位元組碼進行替換與修改;

  ClassTransformer也很簡單,隻有一個實作方法:transform,此方法中可以擷取得到ClassLoader、className、classfileBuffer等,分别為類加載器、類名、位元組碼 ;

  此時我們可以在transform方法中做文章,實作我們的防護程式;

/**
 * @author linxin
 * @version v1.0
 *          Copyright (c) 2017 by linx
 * @date 2017/6/23.
*/
 public class ClassTransformer implements ClassFileTransformer {
 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){

    byte[] transformeredByteCode = classfileBuffer;
    try {

        if (className.equals("co/solinx/demo/Test")) {
            System.out.println(String.format("transform start %s",className));
            ClassReader reader = new ClassReader(classfileBuffer);
            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            ClassVisitor classVisitor = (ClassVisitor) createVisitorIns("co.solinx.demo.visitor.TestVisitor", writer, className);
            reader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
            transformeredByteCode = writer.toByteArray(); 
        }

    } catch (Exception e) {
        e.printStackTrace();
    }catch (Throwable t){
        t.printStackTrace();
    }
    return transformeredByteCode;
}
 public Object createVisitorIns(final String name, ClassVisitor cv, String className)
        throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
    Constructor<?> ctor = Class.forName(name).getDeclaredConstructor(new Class[]{ClassVisitor.class, String.class});
    ctor.setAccessible(true);
    return  ctor.newInstance(new Object[]{cv, className});
 }
}
           

  可以看到我們在transform方法中co/solinx/demo/Test類進行攔截,并通過ASM修改位元組碼注入我們的保護邏輯,下面代碼是TestVisitorAdapter類中的onMethodEnter方法實作了通過ASM調用攔截器,抛出異常的位元組碼;

@Override
protected void onMethodEnter() { 

    mv.visitTypeInsn(NEW,"co/solinx/demo/filter/SqlFilter");
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKESPECIAL,"co/solinx/demo/filter/SqlFilter","<init>","()V",false);
    mv.visitVarInsn(ASTORE,2);
    mv.visitVarInsn(ALOAD,2);
    mv.visitVarInsn(ALOAD,1);
    mv.visitMethodInsn(INVOKEVIRTUAL,"co/solinx/demo/filter/SqlFilter", "filter","(Ljava/lang/Object;)Z",false);

    Label label = new Label();
    /**
     * IFEQ filter傳回值也就是棧頂int型數值等于true時跳轉,抛出異常
     */
    mv.visitJumpInsn(IFEQ, label);  
    mv.visitTypeInsn(NEW, "java/sql/SQLException");
    mv.visitInsn(DUP);
    mv.visitLdcInsn("invalid sql because of security check");
    mv.visitMethodInsn(INVOKESPECIAL, "java/sql/SQLException", "<init>", "(Ljava/lang/String;)V", false);
    mv.visitInsn(ATHROW);
    mv.visitLabel(label);
    /**
     * 必須要調該方法,手動設定Stack Map Table,否則會有 java.lang.VerifyError: Expecting a stackmap frame at branch target 26異常
     * 在jdk1.7中可以使用JVM參數-UseSplitVerifier,關掉class驗證,但jdk1.8中該參數已經去掉,是以要在1.8中運作必須調用該方法;
     */
    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
 } 
           

  SqlFilter攔截類:

public class SqlFilter {
public boolean filter(Object sql){
    boolean ret=false;
    System.out.println(String.format("sql filter : %s ",sql));
    if(sql.toString().contains("1=1")){
        ret=true;
    }
    return ret;
}
}
           

  TestVisitorAdapter類中的onMethodEnter方法中通過調用filter攔截器,傳回true就是被攔截了,抛出異常,否則放行;至此一個簡單的JAVA RASP demo就完成了;通過後面的方式即可使用我們的RASP程式:java -javaagent:respjar-1.0-SNAPSHOT.jar app.jar;

  通過RASP可以通過無嵌入、無需修改代碼的實作安全保護,在RASP中可以攔截SQL、會話、有害請求、OGNL等等資訊;

  Demo源碼:https://github.com/linxin26/javarespdemo

文章首發位址:Solinx

http://www.solinx.co/archives/950