淺談被動式 IAST 產品與技術實現

語言: CN / TW / HK

作者:[email protected]墨雲科技VLab Team

原文連結: https://mp.weixin.qq.com/s/6olAInQLPDaDAO3Up1rQvQ

筆者曾參與RASP研究與研發得到一些相關經驗,近兩年觀察到IAST發展勢頭明顯,但目前國內外對於IAST具體實現的細節相關文章較少,且筆者看到的開源IAST僅有洞態,故想通過筆者視角,對IAST的原理及技術實現進行探究及分享。

本文僅代表筆者個人觀點,歡迎大家進行技術交流及學習。

什麼是IAST

IAST是AST其中的一個類別,AST是Application Security Testing的簡稱,譯為應用安全測試,在其之下衍生出來以下幾種型別:

  1. SAST(Static Application Security Testing)靜態應用安全測試
  2. DAST(Dynamic Application Security Testing)動態應用安全測試
  3. MAST(Mobile Application Security Testing)移動應用安全測試
  4. IAST (Interactive Application Security Testing)互動式應用安全測試

對於IAST的定義我並沒有在Gartner找到其相關的術語表,但是在Gartner推薦的服務商中找到了一些關於IAST的定義,核心內容如下:

IAST使用執行時代理方法在測試階段分析與監控應用程式的行為。這種型別的測試也不測試整個應用程式或程式碼,而只測試執行功能的部分。

有趣的是,多數人認為IAST是Gartner2012年提出來的術語,但我在Gartner的術語表內並沒有找到IAST的相關定義(可能由於Gartner之前改版,導致這個術語丟失),於是我在Gartner推薦的服務商中找到了IAST相關的標籤和簡單的介紹。

關於IAST的細分,可參考以下文章 https://www.freebuf.com/sectool/290671.html 這篇文章對IAST的分類有比較清晰的描述。本文以下內容主要圍繞被動式IAST進行分析介紹。

國內外IAST產品

筆者對國內外的IAST相關的產品公司進行了一些整理,內容如下(該資料不代表所有的IAST廠商,僅為筆者搜尋到的部分廠商):

被動式IAST要想實現,那麼其實和RASP差別不大,區別主要集中在埋點檢測,從而達到對呼叫鏈的精準跟蹤,在這一細小部分,我個人的理解是,對所有有可能導致source獲取到的引數進行改變的方法進行埋點,包括但不限於類似以下幾種情況(下面僅是虛擬碼,並不代表真實邏輯中的程式碼,僅便於大家理解):

new String(....)
"aa".replace(...)
StringBuilder sb = new StringBuilder();
Base64.decode(...)

此鏈路需根據實際業務情況進行完善,例如實現某個加解密的類等,又或者是加入對souce進行安全過濾處理的方法,然後將所有經過預埋點的堆疊資訊進行拼接,在這個過程中,可以去判斷這條鏈路經過了安全過濾處理方法,那麼或許可以粗暴的不上報這條呼叫鏈資訊,認為這是一個安全的請求(當然這種情況還是要謹慎,畢竟研發中難免會犯一些錯誤,所以在情況允許的環境下,還是全部上報,交給人工進行復驗、排除是更為妥當的解決方式),然後將資料上報到服務端,到此完成一個IAST的技術理念邏輯。

那麼其實是不是可以使用一些APM的開源技術,對它進行改造,從而實現IAST的部分功能。如果想深度控制IAST的流程,更好的方式就是自己實現一套IAST埋點、檢測邏輯。

實現IAST所需要的一些技術

如果想要從零實現一個被動式的IAST,我們至少需要掌握關於位元組碼操作的技術,例如ASM、Javassist等,若不想從零或底層的方式去實現,可以試試使用AspectJ技術,或結合使用開源APM框架進行改造,讓其成為一個簡單的被動IAST。

本次所涉及的Demo原始碼已經公開,Github專案為: iiiusky/java_iast_example。

實驗環境搭建

這次IAST相關的環境其實和之前的RASP環境基本差不多。大家可以參照之前的淺談RASP技術攻防之實戰[環境配置篇]文章內容去搭建一個本地的實驗環境,唯一變的,可能就是包名了。

demo整體邏輯

這次實驗的整體邏輯如果相比真正的IAST,肯定會有很多缺少的細節部分完善,所以僅僅適合用來學習瞭解被動IAST實現的大致流程,整體邏輯圖如下:

從上圖可以看到,其實在這次demo實現的過程中,邏輯也並不是很複雜,大致文字版說明如下:

http->enterHttp->enterSource->leaveSource

enterPropagator->leavePropagator(…………此過程重複n次…………)

enterSink->leaveSink(可省略)->leaveHttp

以上大致完成了整個汙點跟蹤鏈路流程,在初始化HTTP的時候,將新建一個 LinkedList 型別的物件,用來儲存執行緒鏈路呼叫的資料。

package cn.org.javaweb.iast.visitor;

import org.objectweb.asm.MethodVisitor;

/**
 * @author iiusky - 03sec.com
 */
public interface Handler {

    MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, String name, String desc, String signature, String[] exceptions);
}

實現Http埋點

為了方便對不同型別的點進行適配,抽象了一個 Handler 出來,然後在根據不同的型別實現具體的 ClassVisitorHandler 內容, Handler.java 具體程式碼如下:

在Java EE中通過劫持 javax.servlet.Servletservice 方法和 javax.servlet.Filter 類的 doFilter 方法不但可以獲取到原始的 HttpServletRequestHttpServletResponse 物件,還可以控制Servlet和Filter的程式執行邏輯。

可以將所有引數描述符為 (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V 的方法進行插入埋點,並快取request、response物件。

實現的程式碼如下(示例程式碼為了便於理解未考慮異常處理):

package cn.org.javaweb.iast.visitor.handler;

import cn.org.javaweb.iast.visitor.Handler;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.reflect.Modifier;


/**
 * @author iiusky - 03sec.com
 */
public class HttpClassVisitorHandler implements Handler {

    private static final String METHOD_DESC = "(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V";

    public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access,
                                             String name, String desc, String signature, String[] exceptions) {
        if ("service".equals(name) && METHOD_DESC.equals(desc)) {
            final boolean isStatic = Modifier.isStatic(access);
            final Type    argsType = Type.getType(Object[].class);

            System.out.println(
                    "HTTP Process 類名是: " + className + ",方法名是: " + name + "方法的描述符是:" + desc + ",簽名是:"
                            + signature + ",exceptions:" + exceptions);
            return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                @Override
                protected void onMethodEnter() {
                    loadArgArray();
                    int argsIndex = newLocal(argsType);
                    storeLocal(argsIndex, argsType);
                    loadLocal(argsIndex);

                    if (isStatic) {
                        push((Type) null);
                    } else {
                        loadThis();
                    }
                    loadLocal(argsIndex);
                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Http", "enterHttp",
                            "([Ljava/lang/Object;)V", false);

                }

                @Override
                protected void onMethodExit(int i) {
                    super.onMethodExit(i);
                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Http", "leaveHttp", "()V",
                            false);
                }
            };
        }
        return mv;
    }
}

上面的程式碼將對所有實現 javax.servlet.Servlet#service 的方法進行了埋點處理(介面、抽象類除外),真正編譯到jvm中的類如下:

可以看到,在對進入方法的時候呼叫了IAST中的方法 cn.org.javaweb.iast.core.Http#enterHttp ,在離開方法的時候,呼叫了 cn.org.javaweb.iast.core.Http#leaveHttp 其中 enterHttp 具體程式碼如下:

public static void enterHttp(Object[] objects) {
    if (!haveEnterHttp()) {
      IASTServletRequest request = new IASTServletRequest(objects[0]);
      IASTServletResponse response = new IASTServletResponse(objects[1]);

      RequestContext.setHttpRequestContextThreadLocal(request, response, null);
    }
  }

從上文中可以看到,傳入的 HttpServletRequestHttpServletResponse 物件存到了當前執行緒的上下文中,方便後續對資料的調取使用。

leaveHttp 具體程式碼如下:

public static void leaveHttp() {
    IASTServletRequest request = RequestContext.getHttpRequestContextThreadLocal()
        .getServletRequest();
    System.out.printf("URL            : %s \n", request.getRequestURL().toString());
    System.out.printf("URI            : %s \n", request.getRequestURI().toString());
    System.out.printf("QueryString    : %s \n", request.getQueryString().toString());
    System.out.printf("HTTP Method    : %s \n", request.getMethod());
    RequestContext.getHttpRequestContextThreadLocal().getCallChain().forEach(item -> {
      if (item.getChainType().contains("leave")) {
        String returnData = null;
        if (item.getReturnObject().getClass().equals(byte[].class)) {
          returnData = new String((byte[]) item.getReturnObject());
        } else if (item.getReturnObject().getClass().equals(char[].class)) {
          returnData = new String((char[]) item.getReturnObject());
        } else {
          returnData = item.getReturnObject().toString();
        }

        System.out
            .printf("Type: %s CALL Method Name: %s CALL Method Return: %s \n", item.getChainType(),
                item.getJavaClassName() + item.getJavaMethodName(), returnData);
      } else {
        System.out
            .printf("Type: %s CALL Method Name: %s CALL Method Args: %s \n", item.getChainType(),
                item.getJavaClassName() + item.getJavaMethodName(),
                Arrays.asList(item.getArgumentArray()));
      }
    });
  }

從當前執行緒中獲取到在呼叫 enterHttp 時候存的資料,對其中的資料進行視覺化的輸出列印。

實現Source埋點

在Java EE中通過可以劫持獲取輸入源的所有方法,比如常用的 getParametergetHeader 等類似的方法,在這裡將對呼叫的方法、以及返回的引數進行跟蹤,這裡為真正汙點跟蹤的起點。可以簡單的理解為就是http各個get方法即為來源,但這一結論不保證完全適配所有情況。對於Source相關的點處理的程式碼如下(示例程式碼為了便於理解未考慮異常處理):

package cn.org.javaweb.iast.visitor.handler;

import cn.org.javaweb.iast.visitor.Handler;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.reflect.Modifier;


/**
 * @author iiusky - 03sec.com
 */
public class SourceClassVisitorHandler implements Handler {

    private static final String METHOD_DESC = "(Ljava/lang/String;)Ljava/lang/String;";

    public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access, final String name,
                                             final String desc, String signature, String[] exceptions) {
        if (METHOD_DESC.equals(desc) && "getParameter".equals(name)) {
            final boolean isStatic = Modifier.isStatic(access);

            System.out.println("Source Process 類名是: " + className + ",方法名是: " + name + "方法的描述符是:" + desc + ",簽名是:" + signature + ",exceptions:" + exceptions);
            return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                @Override
                protected void onMethodEnter() {
                    loadArgArray();
                    int argsIndex = newLocal(Type.getType(Object[].class));
                    storeLocal(argsIndex, Type.getType(Object[].class));
                    loadLocal(argsIndex);
                    push(className);
                    push(name);
                    push(desc);
                    push(isStatic);

                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Source", "enterSource", "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false);
                    super.onMethodEnter();
                }

                @Override
                protected void onMethodExit(int opcode) {
                    Type returnType = Type.getReturnType(desc);
                    if (returnType == null || Type.VOID_TYPE.equals(returnType)) {
                        push((Type) null);
                    } else {
                        mv.visitInsn(Opcodes.DUP);
                    }
                    push(className);
                    push(name);
                    push(desc);
                    push(isStatic);
                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Source", "leaveSource", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false);
                    super.onMethodExit(opcode);
                }
            };
        }
        return mv;
    }
}

以上程式碼的邏輯,只是簡單的對於 getParameter 進行了埋點處理,讓其呼叫IAST的處理邏輯,編譯到JVM的Class內容如下:

可以看到,在進入方法後呼叫了 cn.org.javaweb.iast.core.Source#enterSource ,具體內容如下:

public static void enterSource(Object[] argumentArray,
                                   String javaClassName,
                                   String javaMethodName,
                                   String javaMethodDesc,
                                   boolean isStatic) {
        if (haveEnterHttp()) {
            CallChain callChain = new CallChain();
            callChain.setChainType("enterSource");
            callChain.setArgumentArray(argumentArray);
            callChain.setJavaClassName(javaClassName);
            callChain.setJavaMethodName(javaMethodName);
            callChain.setJavaMethodDesc(javaMethodDesc);
            callChain.setStatic(isStatic);
            RequestContext.getHttpRequestContextThreadLocal().addCallChain(callChain);
        }
    }

對引數、類名、方法名、描述符等資訊新增到了callChain中. 在方法結束前獲取了返回值,並且呼叫了 cn.org.javaweb.iast.core.Source#leaveSource 方法,將返回值傳入了進去,那麼在處理的時候,就將其結果放到了 callChain.returnObject

實現Propagator埋點

傳播點的選擇是非常關鍵的,傳播點規則覆蓋的越廣得到的傳播鏈路就會更清晰。比如簡單粗暴的對 StringByte 等類進行埋點,因為中間呼叫這些類的太多了,所以可能導致一個就是結果堆疊太長,不好對呼叫鏈進行分析,但是對於傳播點的選擇,可以更精細化一些去做選擇,比如 Base64decodeencode 也可以作為傳播點進行埋點,以及執行命令的 java.lang.Runtime#exec 也是可以作為傳播點的,因為最終執行命令是最底層在不同系統封裝的呼叫執行命令JNI方法的類,如 java.lang.UNIXProcess 等,所以將 java.lang.Runtime#exec 作為傳播點也是一個選擇。為了方便演示汙點傳播的效果,對 Base64decode 以及 encodejava.lang.Runtime 進行了埋點處理,具體實現程式碼如下(示例程式碼為了便於理解未考慮異常處理):

package cn.org.javaweb.iast.visitor.handler;

import cn.org.javaweb.iast.visitor.Handler;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.reflect.Modifier;


/**
 * @author iiusky - 03sec.com
 */
public class PropagatorClassVisitorHandler implements Handler {

    private static final String METHOD_DESC = "(Ljava/lang/String;)[B";

    private static final String CLASS_NAME = "java.lang.Runtime";

    @Override
    public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access,
                                             final String name, final String desc, String signature, String[] exceptions) {
        if ((name.contains("decode") && METHOD_DESC.equals(desc)) || CLASS_NAME.equals(className)) {
            final boolean isStatic = Modifier.isStatic(access);
            final Type    argsType = Type.getType(Object[].class);

            if (((access & Opcodes.ACC_NATIVE) == Opcodes.ACC_NATIVE) || className
                    .contains("cn.org.javaweb.iast")) {
                System.out.println(
                        "Propagator Process Skip  類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc);
            } else {
                System.out
                        .println("Propagator Process 類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc);
                return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                    @Override
                    protected void onMethodEnter() {
                        loadArgArray();
                        int argsIndex = newLocal(argsType);
                        storeLocal(argsIndex, argsType);
                        loadLocal(argsIndex);
                        push(className);
                        push(name);
                        push(desc);
                        push(isStatic);

                        mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Propagator",
                                "enterPropagator",
                                "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V",
                                false);
                        super.onMethodEnter();
                    }

                    @Override
                    protected void onMethodExit(int opcode) {
                        Type returnType = Type.getReturnType(desc);
                        if (returnType == null || Type.VOID_TYPE.equals(returnType)) {
                            push((Type) null);

                        } else {
                            mv.visitInsn(Opcodes.DUP);
                        }
                        push(className);
                        push(name);
                        push(desc);
                        push(isStatic);
                        mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Propagator",
                                "leavePropagator",
                                "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V",
                                false);
                        super.onMethodExit(opcode);
                    }
                };
            }
        }
        return mv;
    }
}

真正執行在JVM中的類如下:

java.util.Base64$Decoder#decode

java.lang.Runtime

可以看到其實也是在方法進入後和方法離開前插入了IAST的程式碼邏輯,以便直觀的觀察到入參值以及返回值發生的變化。

實現Sink埋點

對於Sink點的選擇,其實和找RASP最終危險方法的思路一致,只限找到危險操作真正觸發的方法進行埋點即可,比如 java.lang.UNIXProcess#forkAndExec 方法,這種給 java.lang.UNIXProcess#forkAndExec 下點的方式太底層,如果不想這麼底層,也可以僅對 java.lang.ProcessBuilder#start 方法或者 java.lang.ProcessImpl#start 進行埋點處理。本次實驗選擇了對 java.lang.ProcessBuilder#start 進行埋點處理,具體實現程式碼如下(示例程式碼為了便於理解未考慮異常處理):

package cn.org.javaweb.iast.visitor.handler;

import cn.org.javaweb.iast.visitor.Handler;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.reflect.Modifier;


/**
 * @author iiusky - 03sec.com
 */
public class SinkClassVisitorHandler implements Handler {

    private static final String METHOD_DESC = "()Ljava/lang/Process;";

    @Override
    public MethodVisitor ClassVisitorHandler(MethodVisitor mv, final String className, int access,
                                             final String name, final String desc, String signature, String[] exceptions) {
        if (("start".equals(name) && METHOD_DESC.equals(desc))) {
            final boolean isStatic = Modifier.isStatic(access);
            final Type    argsType = Type.getType(Object[].class);

            System.out.println("Sink Process 類名:" + className + ",方法名: " + name + "方法的描述符是:" + desc);
            return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                @Override
                protected void onMethodEnter() {
                    loadArgArray();
                    int argsIndex = newLocal(argsType);
                    storeLocal(argsIndex, argsType);
                    loadThis();
                    loadLocal(argsIndex);
                    push(className);
                    push(name);
                    push(desc);
                    push(isStatic);

                    mv.visitMethodInsn(INVOKESTATIC, "cn/org/javaweb/iast/core/Sink", "enterSink",
                            "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V",
                            false);
                    super.onMethodEnter();
                }
            };
        }
        return mv;
    }
}

在這次實驗中,選擇了對所有方法名為 start 且方法描述為 ()Ljava/lang/Process; 的類進行埋點,其實也就是對 java.lang.ProcessBuilder#start 進行埋點處理。最終執行在JVM中的class如下:

在方法進去後呼叫了IAST的 cn.org.javaweb.iast.core.Sink#enterSink 方法,以此來確定一個呼叫鏈是否已經到達危險函式執行點。對於Sink,除了整體處理邏輯與 Propagator 以及 Source 相似,多了一個 setStackTraceElement 的操作,目的是將在觸發 Sink 點的堆疊將其儲存下來,方便後面使用分析。具體程式碼如下:

public static void enterSink(Object[] argumentArray,
                                 String javaClassName,
                                 String javaMethodName,
                                 String javaMethodDesc,
                                 boolean isStatic) {
        if (haveEnterHttp()) {
            CallChain callChain = new CallChain();
            callChain.setChainType("enterSink");
            callChain.setArgumentArray(argumentArray);
            callChain.setJavaClassName(javaClassName);
            callChain.setJavaMethodName(javaMethodName);
            callChain.setJavaMethodDesc(javaMethodDesc);
            callChain.setStatic(isStatic);
            callChain.setStackTraceElement(Thread.currentThread().getStackTrace());
            RequestContext.getHttpRequestContextThreadLocal().addCallChain(callChain);
        }
    }

結果驗證

全部實現完成後,寫一個jsp來執行命令試試看,程式碼如下:

該JSP接收一個引數,然後對該引數進行base64解碼後傳入Runtime.exec中來執行命令,最後輸出執行結果。

<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Base64" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<pre>
<%
    String sb = request.getParameter("cmd");
    byte[] decode = Base64.getDecoder().decode(sb);
    Process process = Runtime.getRuntime().exec(new String(decode));
    InputStream in = process.getInputStream();
    int a = 0;
    byte[] b = new byte[1024];

    while ((a = in.read(b)) != -1) {
        out.println(new String(b, 0, a));
    }

    in.close();
%>
</pre>

接著編譯agent,將其加入到tomcat的啟動命令中,部署jsp頁面,訪問結果。

可見,首先觸發了 getParameter 方法中的Source埋點,傳入的引數為 cmd ,獲取到的結果為 CHdK ,接著連續觸發了5次Propagator點。

第一次觸發的Propagator點位於 Base64 類中 decode 方法,傳入的引數是 CHdK ,返回值為 pwd (原始返回為[]byte,為了方便展示,對其轉為了字串),這時候已經可以初步看到了引數的獲取到base64解碼,也就是原始source點已經發生了變化。

第二次觸發的埋點資訊為獲取一個 Runtime 物件,呼叫的是 java.lang.Runtime#getRuntime ,傳入的引數為空,返回的結果為一個Runtime的物件資訊,其實就是例項化了一個 java.lang.Runtime 物件,這次可以觀察到一個小細節,就是這個返回物件發生了變化,但是並沒有傳入任何引數。

第三次觸發的埋點資訊為呼叫 java.lang.Runtime#exec 方法(接收引數型別為: String ),傳入的值是 pwn ,在這次呼叫中可以看到,第一次Propagator點的返回值作為了入參傳入了這次呼叫,但是緊接著並觸發沒有想象中的 leavePropagator 方法,而是呼叫了另一個 exec 方法。

第四次觸發的埋點資訊為呼叫 java.lang.Runtime#exec 方法(接收引數型別為: String、String[]、File ),其中第一個引數的值為 pwn ,而其它引數為 null (本文不討論如何確定第幾個引數是汙染點的問題,這個可以通過加規則去逐步完善)。在這次呼叫中可以看到,第三次中傳遞過來的 pwn 沒有發生變化,然而也沒有觸發 leavePropagator 方法,由此可以推測出來這個方法內部繼續呼叫了在規則裡面預先匹配到的方法。

第五次觸發的埋點資訊為呼叫 java.lang.Runtime#exec 方法(接收引數型別為: String[]、String[]、File ),傳入的值是 [[Ljava.lang.String;@58ed07d8, null, null] ,這時候就看到了在傳入的值由 pwn 變為了一個 String 陣列型別的物件,返回到第四次觸發的埋點看,其實就可以看到 var 6其實是最開始是由 var1 ,也就是入參值 pwn 轉換得到的。然後可以看到在當前呼叫的方法裡面,又呼叫了規則中的Sink點( java.lang.ProcessBuilder#start )方法。

以上就是大概從Srouce點( getParameter ),經過中間的Propagator點( java.util.Base64$Decoder#decode、java.lang.Runtime#getRuntime、java.lang.Runtime#exec )到最終Sink點( java.lang.ProcessBuilder#start )的整體大概流程了。

總結

在本次實驗中,將 java.lang.Runtime 作為了傳播點,其實在整體流程訪問結束後,這個傳播點才會有返回值返回回來,他是在傳播的過程中呼叫到了Sink點。

那麼對於這種情況,是否應該摒棄將 java.lang.Runtime 作為傳播點呢?這其實應該就是仁者見仁智者見智了,對於整體IAST的流程,其實和RASP流程差不多,但是對於傳播點的選擇,目前大家更多的是基於規則(正則or繼承類)判斷去覆蓋其中的傳播鏈,或者更簡單粗暴的對 StringByte 進行埋點,但是需要處理的細節也就更多了,以及對於在整條鏈路中的無用呼叫也需要處理。是否有一種一勞永逸的辦法可以完整的拿到整條汙點傳播鏈路,從而拋棄基於規則的對傳播點進行人為覆蓋,這個可能就需要進行更加深入的研究了。

在這次實現的demo中,並沒有結合真正業務去實現,以及IAST的其它功能點去展開研究,比如流量重放、SCA、汙點在方法中的引數位置等功能。如果僅僅是想融入DevSecOps中,可以基於開源的APM專案實現一個簡易的IAST,根據具體的一些公司開發規範,去定製一些規則點,來減少因為某些問題導致的誤報情況。

參考連結