前言
簡單整理一下cookie的跨站攻擊,這個其實現在不常見,因為很多公司都明確宣告不再用cookie儲存重要資訊,不過對於老站點還是有的。
正文
攻擊原理:
這種攻擊要達到3個條件:
-
使用者訪問了我們的站點。
-
使用者通過cookie儲存和傳遞身份資訊
-
使用者訪問了壞站點
1和3根本控制不了,那麼控制的就只有2,就是不使用cookie。
但是有些站點改動太大,那麼是否還有其他方式呢?
防禦方式:
-
不使用cookie儲存和傳輸身份認證
-
使用antiforgerytoken,anti-forgery 防偽。
-
避免使用Get作為業務操作的請求方式
那麼從上面看可以在2上做文章,因為3只是說讓低階黑客被阻擋。
同樣antiforgerytoken 方式也有兩種:
- validateAntiForgeryToken
- AntoValidateAntiforgeryToken
那麼就來演示一下:
下面問我們的站點,提供的cookie身份認證方式:
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"]));
services.AddSingleton(securityKey);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuerSigningKey = true,
ValidAudience = "localhost",
ValidIssuer = "localhost",
IssuerSigningKey = securityKey
};
});
上面是cookie和jwt的認證方式哈。
那麼下面這個是cookie登入:
[HttpGet]
public async Task<IActionResult> CookieLogin(string userName)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim("Name", userName));
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(identity));
return Content("login");
}
那麼有一個介面是購買介面:
[ApiController]
[Route("[controller]")]
public class OrderController : Controller
{
[HttpPost]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme+","+CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Pay()
{
return Content(User.FindFirst("name").Value+"買買買");
}
}
上面認證方式有cookie和jwt了。
那麼我們先使用cookie進行登入一下,呼叫CookieLogin這個介面。
https://localhost:5001/Account?username=aomaomao
以看到前臺有cookie資訊。
在另外一個站點有這樣一個東西:
<form action="https://localhost:5001/order" method="post">
<label>打折購買:</label>
<label>1塊錢</label>
<input type="submit" value="提交"/>
</form>
那麼點選一下這個提交後:
可以看到這樣就被攻擊了。
那麼可能有人就會說,如果自己看了一眼網站地址應該就不會出現問題吧。
其實一般攻擊站點一般不給你點選的機會。
<script>
document.form[0].submit()
</script>
這一段程式碼在進入網站的時候就自動幫你點選了。
services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
});
services.AddMvc(options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
上面這個程式碼進行Antiforgery驗證,通過header裡面的X-CSRF-TOKEN的值。options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()是自我判斷是否應該驗證的機制。
可以看下AutoValidateAntiforgeryTokenAttribute的原始碼:
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return (IFilterMetadata) serviceProvider.GetRequiredService<AutoValidateAntiforgeryTokenAuthorizationFilter>();
}
那麼繼續看AutoValidateAntiforgeryTokenAuthorizationFilter:
internal class AutoValidateAntiforgeryTokenAuthorizationFilter : ValidateAntiforgeryTokenAuthorizationFilter
{
public AutoValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
ILoggerFactory loggerFactory)
: base(antiforgery, loggerFactory)
{
}
protected override bool ShouldValidate(AuthorizationFilterContext context)
{
if (context == null)
throw new ArgumentNullException(nameof (context));
string method = context.HttpContext.Request.Method;
return !string.Equals("GET", method, StringComparison.OrdinalIgnoreCase) && !string.Equals("HEAD", method, StringComparison.OrdinalIgnoreCase) && (!string.Equals("TRACE", method, StringComparison.OrdinalIgnoreCase) && !string.Equals("OPTIONS", method, StringComparison.OrdinalIgnoreCase));
}
}
看ShouldValidate,裡面說明了get,head,trace,options都不會進行驗證,post delete等其他的才會驗證。
如果你怕這個把握不住的話,那麼你可以設定屬性:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme+","+CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Pay()
{
return Content(User.FindFirst("name").Value+"買買買");
}
直接在方法上加入ValidateAntiForgeryToken即可。
那麼我們還是按照前面的方法進行演示一下。
這裡表示的就是400了,bad request。
先這樣,細節篇會介紹是具體是如何驗證的,在DefaultAntiforgery這個類裡面。
結
下一節重定向攻擊。