昨天說到 WebForm 與 MVC 共用 Form 驗證身分,關鍵在於共用 Machine Key。 Machine Key 是 ASP.NET 重要的安全基礎,被拿來處理 ViewState、Form 身分驗證/Membership Cookie、Out-Of-Process Session、Membership 密碼的加解密。

ASP.NET 預設的 Machine Key 由系統自動產生,有 Windows 安全機制把關,要取得需費點手腳。 當為了共用 Machine Key 將其寫進 web.config,保管的重責大任就落到管理人員身上,一旦正式台 web.config 不慎外洩,decryptionKey 與 validationKey 就會隨之曝光,會帶來可怕後果, 故文章最後提醒一定要將 machineKey 的 decryptionKey 及 validationKey 為為重要機密,嚴加保護。 這篇文章就來展示 Machine Key 外洩會有什麼嚴重後果,順便談談如何因應。

沿用前篇文章的範例,我在 FormAuthWebForm 新增一個 ShowUser.ashx, 用來顯示目前登入狀況,未登入時顯示"Not Authenticated",登入後則回傳"User=登入者名稱":

<%@ WebHandler Language="C#" Class="ShowUser" %>

using System;
using System.Web;

public class ShowUser : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/plain";
        if (context.Request.IsAuthenticated)
            context.Response.Write("User=" + context.User.Identity.Name);
        else
            context.Response.Write("Not Authenticated");
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

}

由於 ShowUser.ashx 在匿名存取時也要能執行,故 web.config 需加入一段 <location path="ShowUser.ashx"> 配合 <allow users="*" /> 開放匿名存取。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5.2" />
    <authentication mode="Forms">
      <forms loginUrl="Login.aspx" />
    </authentication>
    <machineKey decryption="AES" decryptionKey="D11D...省略..." validation="SHA1" validationKey="304E...省略..." />
    <pages controlRenderingCompatibilityVersion="4.0" />
    <authorization>
      <deny users="?"/>
    </authorization>
  </system.web>
  <location path="ShowUser.ashx">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
</configuration>

由以下展示可看到 ShowUser.ashx 在登入前後顯示的差異:

假設我偷到了 FormAuthWebForm/web.config,從中取得 decryptionKey 與 validationKey,那麼我就可以寫一個簡單工具,為任何登入者名稱捏造 .ASPXAUTH Cookie,跳過登入關卡直接模擬任意使用者。

在以下的展示中,由於擁有 decryptionKey 與 validationKey,Hack.aspx?u=userId 可產生任何一段帶有 userId 登入成功 .ASPXAUTH Cookie。使用 curl 指令取回 ShowUser.ashx 執行結果,可以看到未帶 Cookie 時顯示 Not Authenticated,加上偽造的 Cookie 後,ShowUser.ashx 無從識別真偽,我們想當誰就當誰。

展示影片

看過 MachineKey 外洩的驚悚後果,聊完矛,現在來談談盾。

要保護 Form Authentication 不被破解,我想到兩種解法:

方法一 加密 system.web/machineKey

aspnet_regiis -pef / -pdf 可加密解密指定 web.config 檔的特定 XML 節點,開始前需先建立金鑰容器,並授與 IIS AppPool 執行帳號存取權限:

aspnet_regiis -pc "FormAuthLabRsaKey" -exp
aspnet_regiis -pa "FormAuthLabRsaKey" "IIS APPPOOL\Lab"

金鑰容器建好後,要先在 web.config 加入一段 configProtectedData/providers 新增 "FormAuthLabRsaKey" 金鑰(加入位置請參考稍後 web.config 範例)。接著呼叫 aspnet_regiis 指定用 FormAuthLabRsaKey 金鑰加密 system.web/machineKey:

aspnet_regiis -pef system.web/machineKey X:\Lab\FormAuthLab\FormAuthWebForm -prov FormAuthLabRsaKey

執行後,<machineKey> 將被置換成一段加密內容,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <configProtectedData>
    <providers>
      <add keyContainerName="FormAuthLabRsaKey" useMachineContainer="true"
           name="FormAuthLabRsaKey" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
  </configProtectedData>
  <system.web>
    <!-- 省略 -->
    <machineKey configProtectionProvider="FormAuthLabRsaKey">
      <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
        xmlns="http://www.w3.org/2001/04/xmlenc#">
        <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
              <KeyName>Rsa Key</KeyName>
            </KeyInfo>
            <CipherData>
              <CipherValue>I6xQ38/I2UvFi...ZCRbNXtUJxw=</CipherValue>
            </CipherData>
          </EncryptedKey>
        </KeyInfo>
        <CipherData>
          <CipherValue>QY51cr3T4Q...z/eyjCIM5eNMnnk6qJfftI90=</CipherValue>
        </CipherData>
      </EncryptedData>
    </machineKey>
    <!-- 省略 -->
</configuration>

但需注意,若 Web Farm 的多台機器要共用 web.config,則需要將 FormAuthLabRsaKey 匯出成金鑰容器 XML 拿到其他主機匯入,相關細節可參考 web.config 連線字串加密潛盾機一文。

system.web/machineKey 加密後,駭客即便取得 web.config 也拿不到 machineKey,還必須拿到 FormAuthLabRsaKey 才有解,難度提高許多。(當然,如果豬頭到把 web.config 跟 FormAuthLabRsaKey 金鑰容器 XML放在一起被偷,就神仙難救了)

【延伸閱讀】

方法二 加上第二重認證

除了加密 machineKey,另一個思考方向是在 Cookie 之外加入第二重認證檢查機制。

最簡單做法改為自己產生 .ASPXAUTH Cookie,並在 UserData 埋入可辨別真偽的信物:參考

  FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
    username,
    DateTime.Now,
    DateTime.Now.AddMinutes(30),
    isPersistent,
    tokenForCheck, //隨機產生且可檢驗真偽的字串
    FormsAuthentication.FormsCookiePath);

  // Encrypt the ticket.
  string encTicket = FormsAuthentication.Encrypt(ticket);

  // Create the cookie.
  Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

  // Redirect back to original URL.
  Response.Redirect(FormsAuthentication.GetRedirectUrl(username, isPersistent));

檢查登入身分時,可讀取 Cookie 還原回 FormsAuthenticatioTicket 讀取 UserData 檢查真偽,除非駭客握第二重檢查的邏輯知道怎麼產生可通過檢查的信物,單單取得 decryptionKey 及 validationKey 並無法完成假冒身分。

HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
string tokenForCheck = ticket.UserData;

結語

在這篇文章中,驗證了 system.web/machineKey decryptionKey 及 validationKey 外洩的嚴重後果 - 惡意人士可跳過登入關卡模擬任何身分,讓 ASP.NET Form 驗證機制徹底瓦解。 由此應不難體會嚴防 mechaineKey 外洩的重要性(把它當成管理者密碼保管就對了) 文章也介紹兩種做法,可有效防止 machineKey 外流或降低被竊取時的危害。 大家在使用自訂 Mechine Key 時,請多加留意。

Demostration of hacking ASP.NET form auth cookie with knowing machine key. This article also provides machine key protection tips and how to add additional validation factor to make form authentictaion more secure.


Comments

# by lex.xu

> 當為了共用 Machine Key 將其寫進 web.congif 拼錯了唷 XD

# by Jeffrey

to lex.xu, 感謝提醒,已更正。

Post a comment


74 + 15 =