昨天提到基於效能與資安,ASP.NET 上線時應關閉偵錯模式,有讀者問到:

若選擇 Release 編譯並停用偵錯模式(web.config <compilation debug="false" />),還能 Log 到錯誤程式所在行數?

首先補充一個基本觀念:要在出錯時顯示程式所在位置,偵錯時設定中斷點、逐行偵錯或檢視變數內容,.NET 程式編譯時除 .exe/.dll 外還需同時產出 .pdb (或用 Protable PDB 合併進 .exe/.dll),以提供編譯後程式與原始碼的對映資訊。
延伸閱讀:.NET 知識高裝檢 - .pdb 檔、編譯最佳化與偵錯

當採用 Relase 編譯,與 Debug 的差別包含沒有 #define DEBUG (但有 #define TRACE),偵錯資訊設為 Pdb-Only (與 Debug 使用的 Full 相比,Full 會加入 DebuggableAttribute,影響 JIT 編譯最佳化,故在速度與檔案大小上有些差異),一樣會產生 .pdb 檔案。

因此,Release 編譯可以由 Exception.StackTrace 取得呼叫堆疊、原始檔路徑及程式行數。

這裡用以下實驗驗證。建立一個 ASP.NET 專案(.NET Framework),準備一個共用函式故意拋出例外,自己 try catch 將錯誤資訊(.ToString() 會包含 Message 及 StackTrace)寫入 NLog 再 throw 例外到上層:

using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AspNetDebugModeTest.Models
{
    public class Util
    {
        static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        public static void ThrowException()
        {
            try
            {
                var rnd = Guid.NewGuid().ToString().Substring(0, 8);
#if DEBUG
                var buildConfig = "DEBUG";
#else
                var buildConfig = "RELEASE";
#endif
                var debugMode = HttpContext.Current.IsDebuggingEnabled ? "Enabled" : "Disabled";
                throw new ApplicationException(
                    $"ERROR - {rnd} / Build: {buildConfig} / Debug Mode: {debugMode}");
            }
            catch (Exception e)
            {
                
                logger.Error(e.ToString());
                throw;
            }
        }
    }
}

寫個簡單 MVC Controller,/Home/Index 顯示 HTML 連結呼叫 /Home/Error,在其中呼叫 Util.ThrowException() 觸發錯誤、寫 Log 再拋出錯誤:

using AspNetDebugModeTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace AspNetDebugModeTest.Controllers
{
    public class HomeController : Controller
    {
        
        public ActionResult Index()
        {
            var debugMode = HttpContext.IsDebuggingEnabled ? "Enabled" : "Disabled";    
            return Content($@"
<!DOCTYPE html>
<html>
<body>
    <div>
        <a href='./Home/Error' target='res'>MVC Error</a>
    </div>
    <iframe id=res name=res style='width:720px; height: 720px'></iframe>
</body>
</html>", "text/html");
        }

        public ActionResult Error()
        {
            Util.ThrowException();
            return Content("OK");
        }
    }
}

使用發佈功能設定 Debug 或 Release 編譯,輸出到 bin\app.publish 後掛到 IIS 測試,設為 compilation debug="false"、customErrors mode="Off"。

編譯成 Debug:

編譯成 Release:

二者顯示的錯誤訊息相同,NLog 蒐集到的結果亦相同:(這裡的 try catch 寫在 Util.cs 內部,故 StackTrace 只會包含 Util 段,如要像網頁錯誤訊息包含 MVC 段, 需在 ActionResult Error() 使用 try catch 輸出到 NLog)

2023-08-03 20:05:00.0434|AspNetDebugModeTest.Models.Util|ERROR|System.ApplicationException: ERROR - 28031b45 / Build: DEBUG / Debug Mode: Disabled
   於 AspNetDebugModeTest.Models.Util.ThrowException() 於 X:\Gitea\AspNetDebugModeTest\AspNetDebugModeTest\Models\Util.cs: 行 23 
2023-08-03 20:10:37.9994|AspNetDebugModeTest.Models.Util|ERROR|System.ApplicationException: ERROR - d42539d1 / Build: RELEASE / Debug Mode: Disabled
   於 AspNetDebugModeTest.Models.Util.ThrowException() 於 X:\Gitea\AspNetDebugModeTest\AspNetDebugModeTest\Models\Util.cs: 行 23 

【同場加映】若刪除 .pdb 檔,錯誤訊息便不會顯示程式碼片段及原始碼路徑(沒有 Util.cs 路徑),由此驗證相關資訊要由 .pdb 檔提供。

以上測試突顯了 customeErrors mode="Off" 的安全問題,即使關閉偵錯模式(debug="false"),仍會對外透露程式碼片段、程式碼檔路徑、StackTrace 等細節資訊。故設定 customErrors mode="On" 隱藏錯誤細節,由 Log 取得資訊是較好的做法,IIS 內建的客製 HTTP 500 錯誤畫面如下,實際應用通常會換成與網站風格一致,設計較精美的網頁。(甚至可試試傳說中的「系統忙碌中,請稍侯再試」煙霧彈大絕 XD)

【結論】

  1. 使用 Debug/Release 編譯都能取得出錯程式位置(檔案、行數),不管有無啟用偵錯模式
  2. 直接危害資安的行為是關閉自訂錯誤頁面(customError mode="Off")讓完整錯誤資訊對使用者外露,偵錯模式(<compilation debug="false" />)啟用與否影響其實不大

This article use simple example to test if Release build or disabling debug mode providing less debugging detail.


Comments

# by Lawrence

黑大請教一下,我在使用msbild發佈程式碼到指定資料夾時,發現黃頁顯示的原始程式檔路徑是建置時路徑,這情況是正常的嗎?還是說我發佈時有需要做什麼調整

# by Jeffrey

to Lawrence,PDB 原始檔路徑就是以建置時的檔案路徑為準沒錯,我曾在官方程式庫錯誤訊息中看到微軟 RD 放置原始檔的路徑。

Post a comment