2025年6月24日 星期二

[研究]ASP.NET, WebForm, 跨站請求偽造 XSRF/CSRF(Cross-Site Request Forgery)防護

[研究]ASP.NET, WebForm, 跨站請求偽造 XSRF/CSRF(Cross-Site Request Forgery)防護

2025-06-24

環境:Visual Studio 2022 + ASP.NET + WebForm + Web Application + C# + SQL Server 2019 + SQL Server Management Studio (SSMS) 19

********************************************************************************

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
    Inherits="WebApplication1.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
    </form>
</body>
</html>


Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class Default : System.Web.UI.Page
    {

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            Response.Write("XSRF (CSRF) 測試範例!");
        }
    }
}

用 OWASP Zed Attack Proxy (ZAP) 2.16.1 (2025-03-26 釋出) 測試

********************************************************************************

修改後 Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    public partial class Default : System.Web.UI.Page
    {
        private string AntiXsrfTokenKey = "__AntiXsrfToken";
        private string AntiXsrfTokenValue;

        protected void Page_Init(object sender, EventArgs e)
        {
            // 取得或產生 Anti-XSRF Token
            AntiXsrfTokenValue = (string)ViewState[AntiXsrfTokenKey];
            if (string.IsNullOrEmpty(AntiXsrfTokenValue))
            {
                AntiXsrfTokenValue = Guid.NewGuid().ToString("N");
                ViewState[AntiXsrfTokenKey] = AntiXsrfTokenValue;
            }

            // 綁定驗證事件
            Page.PreLoad += Page_PreLoad;
        }

        protected void Page_PreLoad(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                // 首次載入,將 Token 存入 ViewState
                ViewState[AntiXsrfTokenKey] = AntiXsrfTokenValue;
            }
            else
            {
                // PostBack 時比對 Token
                if ((string)ViewState[AntiXsrfTokenKey] != AntiXsrfTokenValue)
                {
                    throw new InvalidOperationException("Anti-XSRF 檢查失敗。");
                }
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            Response.Write("XSRF (CSRF) 測試範例!");
        }
    }
}

再次測試



********************************************************************************

ZAP 為何仍出現 XSRF 警告?

因為 ZAP 看到:

<form method="post" action="./" id="form1">

代表:

  • 表單缺少 隱藏欄位 Token (Anti-CSRF Token)。
  • 僅靠 ViewStateUserKey,ZAP 檢查不到,因為它藏在 __VIEWSTATE,無明顯 Token 存在。

所以:

  • ✅ ViewStateUserKey 減少風險,但不是主動、可見的 CSRF 防護。
  • ❌ 缺少獨立、隱藏欄位 Token,ZAP 視為未防護。

********************************************************************************

再再次修改

Default.aspx


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
    Inherits="WebApplication1.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:HiddenField ID="AntiCsrfToken" runat="server" />
        <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
    </form>
</body>
</html>

 Default.aspx.cs

using System;

namespace WebApplication1
{
    public partial class Default : System.Web.UI.Page
    {
        private const string AntiCsrfKey = "AntiCsrfTokenKey";

        protected void Page_Init(object sender, EventArgs e)
        {
            // ViewStateUserKey 強化 ViewState 防偽造
            if (Context.User.Identity.IsAuthenticated)
            {
                ViewStateUserKey = Session.SessionID;
            }
            else
            {
                ViewStateUserKey = Guid.NewGuid().ToString();
            }

            // 產生 Anti-CSRF Token
            if (Session[AntiCsrfKey] == null)
            {
                Session[AntiCsrfKey] = Guid.NewGuid().ToString("N");
            }

            AntiCsrfToken.Value = Session[AntiCsrfKey].ToString();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
            {
                // 驗證 Anti-CSRF Token
                if (AntiCsrfToken.Value != (string)Session[AntiCsrfKey])
                {
                    throw new InvalidOperationException("Anti-CSRF Token 驗證失敗,請重新整理頁面。");
                }
            }
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            Response.Write("XSRF (CSRF) 測試範例!");
        }
    }
}

再再次測試,防住 XSRF / CSRF 了。

********************************************************************************

(完)

相關

沒有留言:

張貼留言