今天遇上這幾年來數一數二有趣的詭異茶包,所幸憑藉老司機的經驗與直覺順利過關斬將,找出真相。

同事報案:某網站行為異常,起初頁面正常,執行到某幾個動作會卡住,久久沒反應。初步偵察,該環境僅有 IE 可用,且有 SSL 憑證未被信任的問題,即使一開始 IE 有忽略 SSL 憑證無效繼續的選項,但之後還是常陷入不尋常的無回應狀態。

決定放棄難以使喚的 IE,自己寫 PowerShell + C# 跑 WebClient.DownloadString() 拿第一手資訊,不要 IE 加進來攪局。但此時跳出兩隻小雜魚怪物攔路,分別是 SSL 憑證無效跟 TLS 1.0/1.1 停用。不打緊,這我有經驗:

加上 ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; 及 ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; 之後,已能成功用 WebClient.DownloadString() 取得 ASP.NET 網站回應。觀察到一個現象,網站同時有 WebForm 及 MVC,.aspx 是好的,MVC 卻會傳回 HTTP 503,模擬如下:

如上圖所示,WebForm.aspx 是好的,兩個 MVC 動作則傳回 503,接著馬上試 WebForm.aspx 卻是好的。這裡有兩點很吊詭:

  1. 依我的理解,HTTP 503 多因 IIS AppPool 崩潰或停止造成,潰崩原因可能是伺服器太忙或網頁程式出現嚴重錯誤,被 IIS 也有保護機制判定連續出錯關閉。但是,如果真是 AppPool 崩壞,怎麼會有同一 AppPool 裡 WebForm.aspx 是好的,只有 MVC 吐回 503 的詭異結果。
  2. AppPool 崩潰時,Windows 事件檢視器裡多半要留下「伺服應用程式集區 'XXX' 的處理序與 World Wide Web Publishing 服務通訊時發生嚴重錯誤。」之類的訊息,但問題主機的事件檢視器很乾淨,沒有半則相關錯誤,這跟過往的經驗大不相同。

由於該功能很長一段時間沒用了,再想了幾種可能,主要集中在 MVC 與 WebForm 的差異上:.NET Framework 版本不對?某個 Windows Update 把 MVC 搞壞?但這些猜測很難解釋上述兩項疑點。

靈光一現,我有個大膽的想法...

會不會,這個 HTTP 503 回應並非源於 AppPool 崩壞,而是 MVC 程式自己捏造的?

請同事用 503 關鍵字搜尋程式碼卻無所獲,有點失望,心想猜錯了;隔了幾分鐘,不死心再請同事找 Unavailable 關鍵字,啊哈! BINGO!

找到 MVC 程式中有一段存取權限檢查,在檢核失敗時傳回 System.Net.HttpStatusCode.ServiceUnavailable!

附上我前面模擬 HTTP 503 的 MVC 程式當成示範:

using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Filters;

namespace DemoWeb.Controllers
{
    public class MvcActionsController : Controller
    {
        [MyAuthFilter]
        public ActionResult Index()
        {
            return Content("OK");
        }
        [MyAuthFilter]
        public ActionResult Query()
        {
            return Content("OK");
        }
    }

    public class MyAuthFilterAttribute : ActionFilterAttribute, IAuthenticationFilter
    {
        bool CheckAuthToken(HttpRequestBase request) => false;
        public void OnAuthentication(AuthenticationContext filterContext)
        {
            if (!CheckAuthToken(filterContext.HttpContext.Request))
            {
                filterContext.Result = new HttpStatusCodeResult(
                    System.Net.HttpStatusCode.ServiceUnavailable);
            }
        }

        public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
        {
            
        }
    }
}

存取被拒回傳 HTTP 503 這招的欺敵效果十足,說起來也具有防駭功用,只是當偵錯時遇上,就變成坑了,哈。

Experience of solving a "WebForm OK, but MVC return HTTP 503" issue. It turns out that the 503 is throwed by a piece of MVC code.


Comments

Be the first to post a comment

Post a comment