ASP.NET 中的 Session 固定漏洞
在本文中,我们将学习如何避免 ASP.NET 中的 Session 固定漏洞。
引言
ASP.NET Session 通过在用户浏览器中创建名为 ASP.NET_SessionId
的 cookie 来跟踪用户。每次请求都会检查此 cookie 的值,以确保提供的数据是特定于该用户的。在许多应用程序中,Session 变量用于跟踪登录的用户,即,如果该用户存在 Session 变量,则表示用户已登录,否则表示未登录。
背景 - 漏洞
每当任何数据保存到 Session 中时,都会在用户的浏览器中创建 ASP.NET_SessionId
cookie。即使用户已注销(意味着通过调用 Session.Abandon()
或 Session.RemoveAll()
或 Session.Clear()
方法删除了 Session 数据),此 ASP.NET_SessionId
cookie 及其值也不会从用户浏览器中删除。攻击者可以通过提供利用跨站脚本漏洞设置此预定义 cookie 的链接来利用此合法 cookie 值来劫持用户会话。当用户单击此链接并登录时,用户将拥有攻击者已知的相同 ASP.NET_SessionId
cookie 值,并且他/她也将能够浏览用户帐户并访问与该用户相关的所有信息。这种攻击称为 Session 固定漏洞。
您可以在我的博客中找到数百个此类技巧和窍门: .NET 如何技巧和窍门。
让我们创建一个演示应用程序,显示即使用户已注销并且所有 Session 数据都已删除,ASP.NET_SessionId
cookie 仍然存在。
<fieldset>
<legend>Login</legend>
<p>Username : <asp:TextBox ID="txtU" runat="server" /> </p>
<p>Password : <asp:TextBox ID="txtP" runat="server" /> </p>
<p><asp:Button ID="btnSubmit" runat="server"
Text="Login" OnClick="LoginMe" />
<asp:Label ID="lblMessage" runat="server" EnableViewState="false" />
<asp:Button ID="btnLogout" runat="server"
Text="Logout" OnClick="LogoutMe" Visible="false" />
</p>
</fieldset>
在上面的代码片段中,我们有两个 TextBox
控件,两个 Button
控件和一个 Label
控件。
protected void Page_Load(object sender, EventArgs e)
{
if (Session["LoggedIn"] != null)
{
lblMessage.Text = "Congratulations !, you are logged in.";
lblMessage.ForeColor = System.Drawing.Color.Green;
btnLogout.Visible = true;
}
else
{
lblMessage.Text = "You are not logged in.";
lblMessage.ForeColor = System.Drawing.Color.Red;
}
}
protected void LoginMe(object sender, EventArgs e)
{
// Check for Username and password (hard coded for this demo)
if (txtU.Text.Trim().Equals("u") && txtP.Text.Trim().Equals("p"))
{
Session["LoggedIn"] = txtU.Text.Trim();
}
else
{
lblMessage.Text = "Wrong username or password";
}
}
protected void LogoutMe(object sender, EventArgs e)
{
Session.Clear();
Session.Abandon();
Session.RemoveAll();
}
单击 Login 按钮后,在验证 TextBox
的值后,LoginMe
方法会触发并创建 Session[“LoggedIn”]
。
单击 Logout 按钮后,我们调用 Session.Clear()
、Session.Abandon()
和 Session.RemoveAll()
方法,以确保会话变量已被删除。
输出
用户登录时的 ASP.NET_SessionId cookie
请注意下面的图片,当用户登录时,已创建 ASP.NET_SessionId
cookie。
单击 Login 后,返回并刷新页面。
现在,当我们单击 Logout 按钮时,即使 Session 已被放弃/删除,ASP.NET_SessionId
cookie 仍然存在。
即使用户已注销,ASP.NET_SessionId cookie 仍然存在
单击 Login 后,返回并刷新页面。
如何修复此漏洞
简单的修复
为避免 Session 固定漏洞攻击,我们可以在 Logout 方法中显式删除 ASP.NET_SessionId
cookie。
万无一失的修复
为了彻底防范此攻击,我们可以创建另一个 cookie(例如 AuthCookie
),其中包含一个唯一值,并且相同的值也可以存储在 Session 中。在每次页面加载时,我们可以将此 cookie 值与 Session 值进行匹配;如果两者都匹配,则允许用户进入应用程序,否则重定向到登录页面。
在 Logout 函数中,请确保您也删除了此新 Cookie “AuthCookie
”。要删除此 cookie,只需将其过期日期/时间设置为比当前日期/时间早几个月。
因此,我修改后的此页面的代码隐藏如下所示:
protected void Page_Load(object sender, EventArgs e)
{
//NOTE: Keep this Session and Auth Cookie check
//condition in your Master Page Page_Load event
if (Session["LoggedIn"] != null && Session["AuthToken"] != null
&& Request.Cookies["AuthToken"] != null)
{
if (!Session["AuthToken"].ToString().Equals(
Request.Cookies["AuthToken"].Value))
{
// redirect to the login page in real application
lblMessage.Text = "You are not logged in.";
}
else
{
lblMessage.Text = "Congratulations !, you are logged in.";
lblMessage.ForeColor = System.Drawing.Color.Green;
btnLogout.Visible = true;
}
}
else
{
lblMessage.Text = "You are not logged in.";
lblMessage.ForeColor = System.Drawing.Color.Red;
}
}
protected void LoginMe(object sender, EventArgs e)
{
// Check for Username and password (hard coded for this demo)
if (txtU.Text.Trim().Equals("u") &&
txtP.Text.Trim().Equals("p"))
{
Session["LoggedIn"] = txtU.Text.Trim();
// createa a new GUID and save into the session
string guid = Guid.NewGuid().ToString();
Session["AuthToken"] = guid;
// now create a new cookie with this guid value
Response.Cookies.Add(new HttpCookie("AuthToken", guid));
}
else
{
lblMessage.Text = "Wrong username or password";
}
}
protected void LogoutMe(object sender, EventArgs e)
{
Session.Clear();
Session.Abandon();
Session.RemoveAll();
if (Request.Cookies["ASP.NET_SessionId"] != null)
{
Response.Cookies["ASP.NET_SessionId"].Value = string.Empty;
Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-20);
}
if (Request.Cookies["AuthToken"] != null)
{
Response.Cookies["AuthToken"].Value = string.Empty;
Response.Cookies["AuthToken"].Expires = DateTime.Now.AddMonths(-20);
}
}
LoginMe 方法
首先,让我们关注单击 Login 按钮时触发的 LoginMe
方法。在此方法中,在设置常规 Session 变量后,我们创建一个 GUID(一个唯一且几乎不可能猜测的值),并将其保存为名为 AuthToken
的新 Session 变量。然后,相同的 GUID 被保存到名为 AuthToken
的 cookie 中。
LogoutMe 方法
在 LogoutMe
方法中,我们首先显式使 ASP.NET_SessionId
cookie 过期,以确保在用户单击 Logout 按钮时从浏览器中删除此 cookie,然后,我们也使 AuthToken
cookie 过期。
Page_Load 事件(在实际应用程序中,请将此逻辑保留在 Master Page 的 Page_Load 方法中)
在 Page_Load
事件中,我们检查常规的 LoggedIn
Session 变量,同时,我们还检查名为 AuthToken
的新 Session 变量以及名为 AuthToken
的新 Cookie。如果所有三个都不为 null,那么我们再次匹配新的 Session 变量 AuthToken
和新的 Cookie AuthToken
的值。如果两者**不**相同,则我们显示一条失败消息(在实际应用程序中,将用户重定向到登录页面)。
此逻辑可确保即使 ASP.NET_SessionId
cookie 的值被攻击者知晓,他也无法登录应用程序,因为我们正在检查由我们创建的新 Session 值和新 Cookie(其 GUID 值也是由我们创建的)。攻击者可以知道 Cookie 的值,但他/她无法知道存储在 Web 服务器级别的 Session 值,并且由于此 AuthToken
值在用户每次登录时都会更改,因此旧值将不起作用,攻击者甚至无法猜测此值。除非新的 Session(AuthToken
)值和新的 Cookie(AuthToken
)相同,否则没有人能够登录应用程序。
输出
ASP.NET_SessionId 配合 AuthToken cookie
希望您觉得这篇文章很有趣,感谢您的阅读。订阅我的 RSS feed 以定期阅读更多文章。
最初发布于 这里。