經得起原始碼資安弱點掃描的程式設計習慣培養(二)_8.Insecure Deserialization :不安全的反序列化漏洞ASP.NET處理方式

近期在專案偕同上被Checkmarx掃碼掃到高風險的程式片段
偵測到Deserialization of Untrusted Data (OWASP  2017第8點Insecure Deserialization)
因此要做程式碼安全品質的修正








情境:
Call web api 回傳回來(讀取外部的json測試檔案)的json  字串要反序列化轉為DataTable
for GridView Bind呈現



CheckMarx掃碼報告給的說明
============================================================
Deserialization of Untrusted Data
風險:
對不受信任的資料進行反序列化,可能允許攻擊者提供惡意的物件給反序列化程式碼使用。如果危險物件被不安全地反序列化,則可能透過反序列化的過程中,呼叫可能可用的類別(Class)或方法(method)來執行程式碼或操作系統命令。
此外,反序列化可能會繞過邏輯驗證。由於反序列化通常使用自己的方法,從序列化資料中構造新物件,因此它可以繞過建構函數或setter中強制執行的檢查,這將允許攻擊者反序列化未經驗證不正確或完全惡意的物件。這可能會導致例外狀況,進而影響程式邏輯。



發生原因:
序列化和反序列化物件,對於遠端Web Service流程是不可或缺的,其中物件通過Web
Service(例如透過網路)在程式碼實作之間傳遞。在反序列化期間,從Web Service上提供的序列化物件建構新物件;但是,如果被反序列化的物件不受信任,則可能包含意外或有潛在危險的物件。


如何避免:
盡可能不要在遠端Web Service之間傳遞序列化物件。相反地,請考慮在Web Service之間傳遞原始值( value primitives),並使用這些值填入新建構的物件。如果需要,使用白名單方法傳遞物件。
務必確保傳遞的物件是已知的、可信的和可預期的。除非物件已經過驗證且屬於受信任的已知類型,並且不包含不受信任的物件,否則不要從任何來源動態建構物件。

============================================================

攻擊者在欲被反解譯(de-serialize)回物件的byte stream內容
中輸入自訂的字串以達到攻擊目標
Remote code execution
Denial of Service(DoS)
Access-control-related attacks


聽處理過有經驗的同事分享後得知
原因在於直接對string 也就是Stream 直接ReadToEnd()的結果直接做反序列化
會被掃瞄到這類風險被要求改正





Before:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
        string testJsonFilePath = HttpContext.Current.Server.MapPath("~/json/rcmdAssg.json");
        using (StreamReader streamReader = new StreamReader(testJsonFilePath))
        {
            jsonResponse = streamReader.ReadToEnd();
        }
        IC_RcmdAssg_Model iC_RcmdAssg_Model = Newtonsoft.Json.JsonConvert.DeserializeObject<IC_RcmdAssg_Model>(jsonResponse);

        DataTable dtRcmd = new DataTable();        
        dtRcmd.Columns.Add("isChked", Type.GetType("System.Boolean"));
        dtRcmd.Columns.Add("oriAssgName", Type.GetType("System.String"));
        dtRcmd.Columns.Add("oriAssgCountry", Type.GetType("System.String"));
        dtRcmd.Columns.Add("rcmdName", Type.GetType("System.String"));
        dtRcmd.Columns.Add("rcmdCountry", Type.GetType("System.String"));
        DataRow newRow;

        foreach (Assignee_List assgRcmd in iC_RcmdAssg_Model.assignee_list)
        {
            newRow = dtRcmd.NewRow();
            newRow["isChked"] = true;
            newRow["oriAssgName"] = assgRcmd.oriAssgName;
            newRow["oriAssgCountry"] = assgRcmd.oriAssgName;
            newRow["rcmdName"] = assgRcmd.oriAssgName;
            newRow["rcmdCountry"] = assgRcmd.oriAssgName;
            dtRcmd.Rows.Add(newRow);
        }

        gvRcmdAssgData.DataSource = dtRcmd;
        gvRcmdAssgData.DataBind();


After:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	    string testJsonFilePath = HttpContext.Current.Server.MapPath("~/json/rcmdAssg.json");
            using (StreamReader streamReader = new StreamReader(testJsonFilePath))
            {
                //jsonResponse = streamReader.ReadToEnd();
                JsonSerializer jsonSerializer = new JsonSerializer();
                IC_RcmdAssg_Model iC_RcmdAssg_Model = (IC_RcmdAssg_Model)jsonSerializer.Deserialize(streamReader, typeof(IC_RcmdAssg_Model));
                DataTable dtRcmd = new DataTable();
                dtRcmd.Columns.Add("isChked", Type.GetType("System.Boolean"));
                dtRcmd.Columns.Add("oriAssgName", Type.GetType("System.String"));
                dtRcmd.Columns.Add("oriAssgCountry", Type.GetType("System.String"));
                dtRcmd.Columns.Add("rcmdName", Type.GetType("System.String"));
                dtRcmd.Columns.Add("rcmdCountry", Type.GetType("System.String"));
                DataRow newRow;

                foreach (Assignee_List assgRcmd in iC_RcmdAssg_Model.assignee_list)
                {
                    newRow = dtRcmd.NewRow();
                    newRow["isChked"] = false;
                    newRow["oriAssgName"] = assgRcmd.oriAssgName;
                    newRow["oriAssgCountry"] = assgRcmd.oriAssgCountry;
                    newRow["rcmdName"] = assgRcmd.rcmdName;
                    newRow["rcmdCountry"] = assgRcmd.rcmdCountry;
                    dtRcmd.Rows.Add(newRow);
                }

                gvRcmdAssgData.DataSource = dtRcmd;
                gvRcmdAssgData.DataBind();
            }


通常這種反序列化情境
1.盡量避免用generic types 像是object 或是 dynamic這種,應提早就限制住反序列的物件型態。
2.使用Json.NET時候 請勿使用「無」以外的 TypeNameHandling


Ref:
Attacking .NET deserialization - Alvaro Muñoz
https://www.youtube.com/watch?v=eDfGpu3iE4Q&ab_channel=Scrtinsomnihack
STOP Insecure Deserialization with C#
https://medium.com/@tiagodaraujo/stop-insecure-deserialization-with-c-6a488c95cf2f
TypeNameHandling Enumeration
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm
CA2326:請勿使用「無」以外的 TypeNameHandling 值
https://docs.microsoft.com/zh-tw/dotnet/fundamentals/code-analysis/quality-rules/ca2326

CA2329: Do not deserialize with JsonSerializer using an insecure configuration
https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2329

留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header