天天看點

action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...

action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...

引子

通過《OpenRASP Java源碼分析與總結(一)——啟動與檢測》的分析,我們對OpenRASP Java部分的整體啟動和檢測流程有了大緻的了解。本文将對OpenRASP所支援的js插件、安全基線等幾類檢測方式中,選取幾個比較有代表性的檢測進行詳細分析。

準備

1

  • agent Java代碼包結構
/+ |-boot // rasp.jar代碼所在目錄 | +-engine+ // rasp-engine.jar代碼所在目錄         +src+             +main+                  +java+                       +com.baidu.openrasp+                                          |-config          // 配置代碼                                          |-hook+           // hoot代碼,主要分析這個目錄下的代碼                                          |     |-file      // 檔案hook                                          |     |-server    // 伺服器hook                                          |     |-sql       // sql hook                                          |     +-ssrf      // ssrf hook                                          |                                          |-plugin+                 // 插件代碼                                          |       |-antlr           // sql文法分析代碼                                          |       |-checker+        // checker代碼                                          |       |        |-js     // js checker代碼                                          |       |        |-local                                          |       |        +-policy // 安全基線checker代碼                                          |       +-js.engine       // js引擎代碼                                          |                                          |-tool                                          +-transformer
           
  • 《OpenRASP Java源碼分析與總結(一)——啟動與檢測》中介紹過檢測的基本流程,入口為hook,hook再委托給checker。是以,下文的分析也将按照此流程進行:先分析入口hook,再分析對應的checker。
  • 分析過程中涉及一些正常Web伺服器、JDBC規範、SQL文法規則,本文不做詳細分析,請自行學習了解。
  • 分析過程中涉及到的配置和代碼,均為精簡過,去掉了和分析無關的内容,是以和真正源碼并非一一對應,但對了解整個過程已經足夠了。

源碼分析

2

Web請求檢測

2.1

Tomcat是Java生态圈裡最常見和常用的Web容器,它實作了Servlet規範(版本不同實作的規範版本也不同),所有的Web請求均由Tomcat接收解析後,調用使用者程式進行處理。是以對Web請求檢測可以從Tomcat相關的Hook進行入手,ApplicationFilterHook就是其中一個:

代碼2-1

@HookAnnotationpublic class ApplicationFilterHook extends ServerRequestHook {    // 1    @Override    public boolean isClassMatched(String className) {        return className.endsWith("apache/catalina/core/ApplicationFilterChain");    }    @Override    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {        // 2        String src = getInvokeStaticSrc(ServerRequestHook.class, "checkRequest",                "$0,$1,$2", Object.class, Object.class, Object.class);        // 3                insertBefore(ctClass, "doFilter", null, src);    }}public abstract class ServerRequestHook extends AbstractClassHook {    // 4    public static void checkRequest(Object filter, Object request, Object response) {        HookHandler.checkRequest(filter, request, response);    }}
           
  1. 判斷入參類是否需要被hoook。這裡檢查的是Tomcat對Servlet規範中FilterChain的實作類ApplicationFilterChain。通過Tomcat部署的Web應用的所有HTTP請求均會經過ApplicationFilterChain,是以以此類作為hook入口非常适合;
  2. 擷取靜态方法HookHandler.checkRequest()的源碼;
  3. 将2中的代碼插入到執行個體方法ApplicationFilterChain.doFilter()的開始處。ApplicationFilterChain.doFilter()為HTTP請求的入口方法,在這裡可以對所有的HTTP請求進行檢測;
  4. 調用HookHandler.checkRequest()進行請求檢測。

代碼2-2

public class HookHandler {    public static void checkRequest(Object servlet, Object request, Object response) {        // 1        HttpServletRequest requestContainer = new HttpServletRequest(request);        // 2        HttpServletResponse responseContainer = new HttpServletResponse(response);        // 3        responseContainer.setHeader(OPEN_RASP_HEADER_KEY, OPEN_RASP_HEADER_VALUE);        // 4        responseContainer.setHeader(REQUEST_ID_HEADER_KEY, requestContainer.getRequestId());        // 5        requestCache.set(requestContainer);        // 6        responseCache.set(responseContainer);        // 7        doCheck(CheckParameter.Type.REQUEST, JSContext.getUndefinedValue());    }}
           
  1. 包裝原有的request對象為自定義的request;
  2. 包裝原有的response對象為自定義的response;
  3. 增加請求頭X-Protected-By,值為OpenRASP;
  4. 增加請求頭X-Request-ID,值為一個UUID;
  5. 儲存自定義的request到目前線程上下文中;
  6. 儲存自定義的response到目前線程上下文中;
  7. 檢測請求。

根據7中的第一個入參,查找CheckParameter.Type:

public class CheckParameter {    public enum Type {        ...,        REQUEST("request", new JsChecker()),        ...;}
           

可以得知,CheckParameter.Type.REQUEST對應的Checker實作為JsChecker。是以,最終檢測邏輯即委托給了JsChecker。在《OpenRASP Java源碼分析與總結(一)——啟動與檢測》中我們曾分析過,JsChecker的檢測邏輯實際又委托給了plugins目錄下的js實作。

OpenRASP官方僅提供了幾個請求檢測的Demo插件作為示範使用,如果要投入到實際生産環境,還需要做進一步的開發。下面,我們分别看下官方提供的兩個Demo插件,了解下請求檢測的大緻邏輯,分别是plugins/addons/001-xss-demo.js和plugins/addons/002-detect-scanner.js:

代碼2-3

var plugin = new RASP('offical')// 1plugin.register('request', function(params, context) {    // 2    function detectXSS(params, context) {        // 3        var xssRegex   = /||javascript:(?!(?:history\.(?:go|back)|void\(0\)))/i        var parameters = context.parameter;        var message    = '';        // 4        Object.keys(parameters).some(function (name) {            parameters[name].some(function (value) {                if (xssRegex.test(value)) {                    message = 'XSS 攻擊: ' + value;                    return true;                }            });        });        return message    }    // 5    var message = detectXSS(params, context)    if (message.length) {        return {action: 'block', message: message, confidence: 90}    }    return clean    })
           
  1. 注冊一個request檢測函數;
  2. 定義一個XSS檢測函數;
  3. 定義XSS檢測的正規表達式;
  4. 循環請求裡的每個參數,判斷是否比對XSS正規表達式;
  5. 調用XSS檢測函數,如果命中,則傳回命中資訊。

代碼2-4

var plugin = new RASP('offical')plugin.register('request', function(params, context) {  var foundScanner = false  // 1  var scannerUA    = [..., "bsqlbf", "sqlmap", "nessus", "arachni", "metis", ...]  var headers      = context.header  // 2  var ua = headers['user-agent']  if (ua) {    // 3    for (var i = 0; i < scannerUA.length; i++) {      if (ua.indexOf(scannerUA[i].toLowerCase()) != -1) {        foundScanner = true        break      }    }  }   // 4  if (foundScanner) {    return {action: 'block', message: '已知的掃描器探測行為,UA 特征為: ' + scannerUA[i], confidence: 90}  }  return clean})
           
  1. 定義掃描器可能産生的特定浏覽器User-Agent(以下簡稱為UA)請求頭;
  2. 從請求中擷取UA請求頭;
  3. 循環1中定義的掃描器UA,判斷是否和2中的UA進行比較;
  4. 如果命中掃描器UA,則傳回命中資訊。

通過以上Demo的分析,可以得知,如果要自行編寫Web請求檢測的插件,需要注冊一個request檢測函數,通過檢測函數的第二個入參context(其實就是OpenRASP自定義的HttpServletRequest對象)擷取請求頭、請求參數等資訊,對請求資訊進行檢測即可。通過自行編寫的請求檢測插件,可以完成諸如XSS、掃描器、異常請求頭等檢測。

基于文法分析的SQL檢測

2.2

Java通過JDBC來通路資料庫,而JDBC隻是一套規範和接口,不同的資料庫廠商會根據JDBC規範實作自己的通路邏輯。是以,RASP SQL檢測其實就是在不同資料庫的JDBC實作(主要是Statement、PrepareStatement接口的實作)中注入對SQL的檢測邏輯。

下面我們就來分析下OpenRASP是如何在Statement中完成SQL的檢測(PrepareStatement同理),分析的入口類為SQLStatementHook:

代碼2-5

@HookAnnotationpublic class SQLStatementHook extends AbstractSqlHook {  // 1    public static LRUCache sqlCache = new LRUCache();    @Override    public boolean isClassMatched(String className) {      // 2        if ("com/mysql/jdbc/StatementImpl".equals(className)                || "com/mysql/cj/jdbc/StatementImpl".equals(className)) {            this.type = "mysql";            this.exceptions = new String[]{"java/sql/SQLException"};            return true;        }        ...        return false;    }    @Override    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {      // 3    String checkSqlSrc = getInvokeStaticSrc(SQLStatementHook.class, "checkSQL",                "\"" + type + "\"" + ",$0,$1", String.class, Object.class, String.class);        // 4                insertBefore(ctClass, "execute", checkSqlSrc,                new String[]{"(Ljava/lang/String;)Z", "(Ljava/lang/String;I)Z",                        "(Ljava/lang/String;[I)Z", "(Ljava/lang/String;[Ljava/lang/String;)Z"});        ...    }    public static void checkSQL(String server, Object statement, String stmt) {      // 5        if (!sqlCache.isContainsKey(stmt)) {            JSContext cx = JSContextFactory.enterAndInitContext();            Scriptable params = cx.newObject(cx.getScope());            params.put("server", params, server);            params.put("query", params, stmt);            HookHandler.doCheck(CheckParameter.Type.SQL, params);        }    }}
           
  1. 定義一個SQL緩存,用于存放已經檢測過可放行的SQL;
  2. 檢查入參的類是否需要被hook。這裡檢查的是各個資料庫的JDBC驅動對Statement接口的具體實作類,比如MySQL的com.mysql.jdbc.StatementImpl;
  3. 擷取靜态方法SQLStatementHook.checkSQL()的源碼;
  4. 将4中的代碼插入到執行個體方法StatementImpl.execute()的開始處。StatementImpl.execute()總共有4個同名方法,是以insertBefore()的第四個入參提供了對應的4個不同的方法簽名;
  5. 判斷是否命中SQL緩存,命中則說明該SQL可放行,未命中則調用HookHandler.doCheck()。除了execute()之外,executeUpdate()、executeQuery()和addBatch()也被注入了SQLStatementHook.checkSQL()。

通過上述代碼,實作了類似如下的邏輯(以MySQL的StatementImpl.execute(String sql)為例):

public StatementImpl implements Statement {    public boolean execute(String sql) {        // 插入的SQLStatementHook.checkSQL(this, sql),展開後的代碼        if (!sqlCache.isContainsKey(sql)) {            JSContext cx = JSContextFactory.enterAndInitContext();            Scriptable params = cx.newObject(cx.getScope());            params.put("server", params, server);            params.put("query", params, sql);            HookHandler.doCheck(CheckParameter.Type.SQL, params);        }        // 原有邏輯        ...    }}
           

根據5中的第一個入參,查找CheckParameter.Type:

public class CheckParameter {    public enum Type {        ...,        SQL("sql", new SqlStatementChecker()),        ...;}
           

可以得知,CheckParameter.Type.REQUEST對應的Checker實作為SqlStatementChecker。是以,最終檢測邏輯即委托給了SqlStatementChecker,下面進行分析:

代碼2-6

public class SqlStatementChecker extends ConfigurableChecker {    public List checkSql(CheckParameter checkParameter, Map parameterMap, JsonObject config) {        List result = new LinkedList();        String query = (String) checkParameter.getParam("query");        String message = null;        // 1        String[] tokens = TokenGenerator.detailTokenize(query, new TokenizeErrorListener());        // 2        for (Map.Entry entry : parameterMap.entrySet()) {            String value = entry.getValue()[0];            // 3            int para_index = query.indexOf(value);            if (para_index < 0) {                continue;            }            // 4            int start = tokens.length, end = tokens.length, distance = 2;            ...            // 5            if (end - start > distance) {                message = "SQLi - SQL query structure altered by user input, request parameter name: " + entry.getKey();            }        }        if (message != null) {            result.add(AttackInfo.createLocalAttackInfo(checkParameter, action,                    message, "sqli_userinput", 90));        } else {            // 6            for (String token : tokens) {                if (token.equals("select")) {                    int nullCount = 0;                    // 7                    for (int j = i + 1; j < tokens.length && j < i + 6; j++) {                        if (tokens[j].equals(",") || tokens[j].equals("null") || StringUtils.isNumeric(tokens[j])) {                            nullCount++;                        } else {                            break;                        }                    }                    // 8                    if (nullCount >= 5) {                        message = "SQLi - Detected UNION-NULL phrase in sql query";                        break;                    }                }                if (token.equals(";") && i != tokens.length - 1) {                    // 9                    message = "SQLi - Detected stacked queries";                    break;                } else if (token.startsWith("0x")) {                    // 10                    message = "SQLi - Detected hexadecimal values in sql query";                    break;                } else if (token.startsWith("/*!")) {                    // 11                    message = "SQLi - Detected MySQL version comment in sql query";                    break;                } else if (i < tokens.length - 2 && tokens[i].equals("into")                        && (tokens[i + 1].equals("outfile") || tokens[i + 1].equals("dumpfile"))) {                    // 12                      message = "SQLi - Detected INTO OUTFILE phrase in sql query";                    break;                } else if (i < tokens.length - 1 && tokens[i].equals("from"))) {                    // 13                    String[] parts = tokens[i + 1].replace("`", "").split("\\.");                    if (parts.length == 2) {                        String db = parts[0].trim();                        String table = parts[1].trim();                        if (db.equals("information_schema") && table.equals("tables")) {                            message = "SQLi - Detected access to MySQL information_schema.tables table";                            break;                        }                    }                }                if (message != null) {                    result.add(AttackInfo.createLocalAttackInfo(checkParameter, action,                            message, "sqli_policy", 100));                }                            }        }        return result;    }}
           

1.對SQL進行文法分析(嚴格來說,這裡隻完成了詞法分析),擷取分析後的token(詞)。例如對SQL:select c2 from t1 where c1 = 'a',進行文法分析後,可以得到如下的token表:

action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...

2.循環使用者的輸入,檢測每個輸入項;

3.擷取使用者輸入項在SQL中的索引位置,如果未找到,則進入下一輪循環;

4.定義并計算使用者輸入項在token表中的start(開始索引)和end(結束索引),以及定義可能發生SQL注入時,使用者輸入項所占的最少token項,這裡為2。例如SQL:select c2 from t1 where c1 = '${input}',${input}為使用者輸入項:

  • 正常的使用者輸入,${input} = a:
action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...
  • 存在SQL注入的使用者輸入,${input} = ' or '1' = '1:
action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...
  • 上面的例子中,正常的使用者輸入項隻會占7這一索引的token,而當使用者輸入項發生SQL注入的時候,占用了7~11總共4個索引的token。是以,可以通過計算使用者輸入項所占的token項(11-7=4)來判定使用者的輸入是否可能産生SQL注入。

5.判斷使用者輸入項所占的token是否大于2,大于2則可能為攻擊。為什麼沒有直接使用大于1來判定是否産生SQL注入?個人覺得可能的原因為,為了産生SQL注入,首先要對SQL中兩個字元引号(即where c1 = '${input}'中的兩個字元單引号')進行閉合(' or '1' = '1中的第一個和最後一個'分别用于閉合where c1 = '${input}'中的兩個引号),閉合後自然産生了兩個token。由此可知,為了産生SQL注入,使用者輸入項所占的token項必須大于2;

6.循環每個token,檢測token是否符合安全政策;

7.計算連續出現null、,或者數字的token。這裡的檢測是為了防止可能為UNION查詢攻擊,例如:select c1, c2, c3, c4 from t1 where c1 = 'a' union select null, null, null, c5 from t2 where c6 = 'b';

8.判斷連續出現的null、,或者數字的token是否大于5,大于5則可能為攻擊。選用5應該是出于經驗,null, null, null、1, 2, 3,這些token均為5,太小,容易産生誤判,因為偶爾也會有特殊場景下需要使用null占一個或兩個查詢字段。個人覺得可以再加入union這個token作為判斷依據會更精确;

9.判斷非最後一個token是否為;,如果是,則可能為攻擊。這裡的檢測是為了防止可能為堆疊查詢,例如select c1 from t1 where c1 = 'a'; select c2 from t2;

10.判斷token是否為十六進制符号,如果是,則可能為攻擊;

11.判斷token是否包含/*!,如果包含,則可能為攻擊。這裡的檢測是為了防止可能為内聯注釋攻擊;

12.判斷目前和下一個token是否為into outfile或者into dumpfile,如果是,則可能為攻擊。這裡的檢測是為了防止可能為檔案導出攻擊;

13.判斷目前和下一個token是否為from information_schema.tables,如果是,則可能為攻擊。

注:上述分析中涉及到的攻擊方式不做詳細解釋,請自行百度。

由上述分析可以得知,OpenRASP的SQL檢測是基于對SQL的文法分析。相比傳統的,特别是WAF,基于關鍵字、正規表達式比對的方式,基于文法分析由于從語言層面去分析和了解SQL,可以做到更加的精準,減少誤殺。當然,基于文法分析帶來的問題是,更大的性能損耗和記憶體占用(需要建構一棵完整的文法樹)。

安全基線檢查

2.3

代碼2-7

@HookAnnotationpublic class TomcatStartupHook extends ServerStartupHook {    @Override    public boolean isClassMatched(String className) {        // 1        return "org/apache/catalina/startup/Catalina".equals(className);    }    @Override    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {        // 2        String src = getInvokeStaticSrc(TomcatStartupHook.class, "checkTomcatStartup", "");        insertBefore(ctClass, "start", null, src);    }    public static void checkTomcatStartup() {        // 3        HookHandler.doCheckWithoutRequest(CheckParameter.Type.POLICY_TOMCAT_START, CheckParameter.EMPTY_MAP);    }}
           
  1. 判斷入參類是否需要被hook。這裡檢查的是Tomcat啟動的核心類Catalina;
  2. 擷取靜态方法TomcatStartupHook.checkTomcatStartup()的源碼,插入到執行個體方法Catalina.start()開始處;
  3. 調用靜态方法HookHandler.doCheckWithoutRequest()進行檢測。

根據3中的第一個入參,查找CheckParameter.Type:

public class CheckParameter {    public enum Type {        ...,        POLICY_TOMCAT_START("tomcatStart", new TomcatSecurityChecker()),        ...;}
           

可以得知,CheckParameter.Type.POLICY_TOMCAT_START對應的Checker實作為TomcatSecurityChecker。是以,最終檢測邏輯即委托給了TomcatSecurityChecker,下面進行分析:

代碼2-8

public abstract class ServerPolicyChecker extends PolicyChecker {    @Override    public List checkParam(CheckParameter checkParameter) {        List infos = new LinkedList();        // 1        checkStartUser(infos);        checkServer(checkParameter, infos);        return infos;    }    private void checkStartUser(List infos) {        String osName = System.getProperty("os.name").toLowerCase();        if (osName.startsWith("linux") || osName.startsWith("mac")) {            // 2            if ("root".equals(System.getProperty("user.name"))) {                infos.add(new SecurityPolicyInfo(SecurityPolicyInfo.Type.START_USER, "Java security baseline - should not start application server with root account", true));            }        } else if (osName.startsWith("windows")) {            // 3            Class ntSystemClass = Class.forName("com.sun.security.auth.module.NTSystem");            Object ntSystemObject = ntSystemClass.newInstance();            String[] userGroups = (String[]) ntSystemClass.getMethod("getGroupIDs").invoke(ntSystemObject);            for (String group : userGroups) {                // 4                if (group.equals("S-1-5-32-544")) {                    infos.add(new SecurityPolicyInfo(SecurityPolicyInfo.Type.START_USER, "Java security baseline - should not start application server with Administrator/system account", true));                }            }        }    }        public abstract void checkServer(CheckParameter checkParameter, List infos);}
           
  1. Server安全基線檢查的基類,包含使用者、Server自檢查,其中Server自檢查留給子類實作;
  2. 如果系統為Linux或者Mac,如果目前使用者為root,則記錄觸發安全基線;
  3. 加載com.sun.security.auth.module.NTSystem類,通過該類可以擷取Windows NT系統的安全資訊;
  4. 通過NTSystem.getGroupIDs()擷取目前使用者組資訊,如果為S-1-5-32-544(Windows NT系統上最高權限Administrators使用者組),則觸發安全基線。

代碼2-9

public class TomcatSecurityChecker extends ServerPolicyChecker {    @Override    public void checkServer(CheckParameter checkParameter, List infos) {        // 1        String tomcatBaseDir = System.getProperty("catalina.base");        checkHttpOnlyIsOpen(tomcatBaseDir, infos);        checkManagerPassword(tomcatBaseDir, infos);        checkDirectoryListing(tomcatBaseDir, infos);        checkDefaultApp(tomcatBaseDir, infos);    }    private void checkHttpOnlyIsOpen(String tomcatBaseDir, List infos) {        // 2        File contextFile = new File(tomcatBaseDir + File.separator + "conf/context.xml");        Element contextElement = getXmlFileRootElement(contextFile);        // 3        String httpOnly = contextElement.getAttribute("useHttpOnly");        boolean isHttpOnly = true;        if (httpOnly != null && httpOnly.equals("false")) {          isHttpOnly = false;        }        // 4        if (!isHttpOnly) {          infos.add(new SecurityPolicyInfo(Type.COOKIE_HTTP_ONLY,                    "Tomcat security baseline - httpOnly should be enabled in " + contextFile.getAbsolutePath(), true));        }    }}
           
  1. 擷取Tomcat安裝根目錄,我們先隻分析checkHttpOnlyIsOpen();
  2. 擷取根目錄下的conf/context.xml檔案,并解析XML;
  3. 擷取根元素的userHttpOnly屬性的值;
  4. 如果userHttpOnly為false,則觸發安全基線。

除了HttpOnly的檢查,TomcatSecurityChecker.checkServer()裡還包括管理者密碼、目錄清單等安全配置檢查。

從上述兩段代碼可以得知,OpenRASP Java的安全基線檢查和普通的Java程式并沒有任何差別。當然,也正因為和普通的Java程式沒有差別,是以OpenRASP也隻能完成運作目前應用程式的使用者所能完成的檢查,比如一些需要特殊使用者權限才能完成的檢查。

其他檢查

2.4

代碼2-10

public class SQLResultSetHook extends AbstractSqlHook {    @Override    public boolean isClassMatched(String className) {        // 1        if ("com/mysql/jdbc/ResultSetImpl".equals(className)                || "com/mysql/cj/jdbc/result/ResultSetImpl".equals(className)) {            this.type = "MySQL";            return true;        }        ...        return false;    }    @Override    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {        // 2        String src = getInvokeStaticSrc(SQLResultSetHook.class, "checkSqlQueryResult",                "\"" + type + "\"" + ",$0", String.class, Object.class);        insertBefore(ctClass, "next", "()Z", src);    }    public static void checkSqlQueryResult(String server, Object sqlResultSet) {        ResultSet resultSet = (ResultSet) sqlResultSet;        // 3        int queryCount = resultSet.getRow();        HashMap params = new HashMap(4);        params.put("query_count", queryCount);        params.put("server", server);        // 4        HookHandler.doCheck(CheckParameter.Type.SQL_SLOW_QUERY, params);    }}
           
  1. 判斷入參類是否需要被hook。這裡檢查的是MySQL JDBC驅動ResultSet接口的實作類ResultSetImpl;
  2. 擷取靜态方法SQLResultSetHook.checkSqlQueryResult()的源碼,插入到執行個體方法ResultSetImpl.next()開始處,即擷取資料庫查詢結果的方法開始處;
  3. 擷取查詢結果傳回的總條數;
  4. 調用靜态方法HookHandler.doCheck()進行檢測。

根據3中的第一個入參,查找CheckParameter.Type:

public class CheckParameter {    public enum Type {        ...,        SQL_SLOW_QUERY("sqlSlowQuery", new SqlResultChecker(false)),        ...;}
           

可以得知,CheckParameter.Type.SQL_SLOW_QUERY對應的Checker實作為SqlResultChecker。是以,最終檢測邏輯即委托給了SqlResultChecker,下面進行分析:

代碼2-11

public class SqlResultChecker extends AttackChecker {    @Override    public List checkParam(CheckParameter checkParameter) {        LinkedList result = new LinkedList();        // 1        Integer queryCount = (Integer) checkParameter.getParam("query_count");        // 2        int slowQueryMinCount = Config.getConfig().getSqlSlowQueryMinCount();        // 3        if (queryCount == slowQueryMinCount) {            result.add(AttackInfo.createLocalAttackInfo(checkParameter, EventInfo.CHECK_ACTION_INFO,                    "慢查詢: 使用SELECT語句讀取了大于等于" + slowQueryMinCount + "條資料", "slow query"));        }        return result;    }}
           
  1. 擷取查詢結果傳回的總條數;
  2. 從配置檔案(RELEASE版本的conf/rasp.properties)中的SQL慢查詢最小條數,配置項為sql.slowquery.min_rows,預設配置值為500;
  3. 判斷實際查詢傳回的總條數是否大于等于配置值(源碼中為==,BUG),大于等于則記錄安全檢查結果。

總結

3

經過上述的分析,我們了解到,OpenRASP:

  1. 通過往特定Web容器實作中注入檢測邏輯,可以完成對所有Web請求的檢測;
  2. 通過往特定JDBC驅動實作中注入檢測邏輯,可以完成對所有SQL的檢測。其中,SQL的檢測基于文法分析;
  3. 通過往特定類中注入檢測邏輯,可以完成安全基線的檢查,以及一些非安全相關的檢查。

因為RASP天然具備擷取應用運作過程中上下文,是以RASP具備了解應用上下文的能力。在了解應用上下文的前提下,可以擷取到精準的應用資料進行檢測,檢測的結果可以直接回報給應用,應用依據回報結果進行決策。

action請求_同盾技術 | OpenRASP Java源碼分析與總結(二)Web請求、基于文法分析的SQL和安全基線檢測...