跨站点脚本 (XSS) 攻击是一种严重的安全威胁,恶意脚本会注入其他用户查看的网页中。本文演示了如何在 ASP.NET Core MVC 中构建一个简单的博客应用程序,同时使用内置安全功能和最佳实践来防止 XSS 攻击。
步骤 1.创建 ASP.NET Core MVC 项目
创建新的 ASP.NET Core MVC 项目
dotnet new mvc -n BlogApp
cd BlogApp
添加实体框架核心包
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.Security.Application
步骤 2. 设置 Entity Framework Core
创建数据库上下文和模型
创建数据文件夹并添加ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using XSSAttackInAspNetCoreMVC.Model;
namespace XSSAttackInAspNetCoreMVC.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<BlogPost> BlogPosts { get; set; }
}
}
在 Startup.cs 中配置数据库上下文
using Microsoft.EntityFrameworkCore;
using XSSAttackInAspNetCoreMVC.Data;
namespace XSSAttackInAspNetCoreMVC
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 创建Web应用程序构建器
// 向容器中添加服务
builder.Services.AddRazorPages();
// 添加Razor页面服务
builder.Services.AddControllersWithViews();
// 添加控制器和视图服务
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 添加数据库上下文服务,并配置使用SQL Server连接字符串
var app = builder.Build();
// 构建Web应用程序
// 配置HTTP请求管道
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// 非开发环境使用异常处理程序,重定向到 /Error 页面
// 默认HSTS值为30天。在生产环境中可能需要更改此值,参考https://aka.ms/aspnetcore-hsts
app.UseHsts();
}
app.UseHttpsRedirection();
// 使用HTTPS重定向
app.UseStaticFiles();
// 使用静态文件
app.UseRouting();
// 启用路由功能
app.UseAuthorization();
// 启用授权功能
app.MapRazorPages();
// 映射Razor页面
app.UseEndpoints(routes =>
{
routes.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// 设置默认的控制器路由
});
app.Run();
// 运行应用程序
}
}
}
在 appsettings.json 中添加连接字符串
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BlogAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
创建初始迁移并更新数据库
dotnet ef migrations add InitialCreate
dotnet ef database update
步骤 3.创建控制器和视图
创建 BlogPost 模型
创建 Models 文件夹并添加 BlogPost.cs
using System.ComponentModel.DataAnnotations;
namespace XSSAttackInAspNetCoreMVC.Model
{
public class BlogPost
{
// 博客文章的唯一标识符
public int Id { get; set; }
// 标题属性,必填并限制长度为100个字符
[Required]
[StringLength(100)]
public string? Title { get; set; }
// 内容属性,必填
[Required]
public string? Content { get; set; }
// 创建日期时间
public DateTime Created { get; set; }
}
}
创建 BlogController
创建 Controllers 文件夹并添加 BlogController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Security.Application;
using XSSAttackInAspNetCoreMVC.Data;
using XSSAttackInAspNetCoreMVC.Model;
namespace XSSAttackInAspNetCoreMVC.Controllers
{
public class BlogController : Controller
{
private readonly ApplicationDbContext _context;
// 依赖注入的数据库上下文,用于访问数据库
public BlogController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet]
public IActionResult Index()
{
var posts = _context.BlogPosts.ToList();
// 从数据库中获取所有博客文章
return View(posts);
// 将博客文章列表传递给视图
}
[HttpGet]
public IActionResult Create()
{
return View();
// 返回创建博客文章的视图
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(BlogPost model)
{
if (ModelState.IsValid)
{
// 如果模型状态有效
// 在保存到数据库之前对输入进行清理
model.Title = Sanitizer.GetSafeHtmlFragment(model.Title);
model.Content = Sanitizer.GetSafeHtmlFragment(model.Content);
_context.BlogPosts.Add(model);
// 将新的博客文章添加到数据库上下文
_context.SaveChanges();
// 保存更改到数据库
return RedirectToAction(nameof(Index));
// 重定向到博客文章列表页
}
return View(model);
// 如果模型状态无效,返回创建视图并显示错误信息
}
}
}
创建视图
创建 Views/Blog 文件夹并添加以下视图
Index.cshtml
@model IEnumerable<XSSAttackInAspNetCoreMVC.Model.BlogPost>
<!DOCTYPE html>
<html>
<head>
<title>Blog Posts</title>
</head>
<body>
<h1>Blog Posts</h1>
<a asp-controller="Blog" asp-action="Create">Create New Post</a>
<ul>
@foreach (var post in Model)
{
<li>
<h2>@post.Title</h2>
<p>@Html.Raw(@post.Content)</p>
<p><small>@post.Created</small></p>
</li>
}
</ul>
</body>
</html>
Create.cshtml
@model XSSAttackInAspNetCoreMVC.Model.BlogPost
<!DOCTYPE html>
<html>
<head>
<title>Create Blog Post</title>
</head>
<body>
<h1>Create Blog Post</h1>
<form asp-action="Create" method="post" asp-antiforgery="true">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Content"></label>
<textarea asp-for="Content" class="form-control"></textarea>
<span asp-validation-for="Content" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<a asp-controller="Blog" asp-action="Index">Back to List</a>
</body>
</html>
步骤 4.添加客户端验证
启用客户端验证
将必要的脚本添加到_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - XSSAttackInAspNetCoreMVC</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/XSSAttackInAspNetCoreMVC.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">XSSAttackInAspNetCoreMVC</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-action="Index" asp-controller="Blog">Blog</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2024 - XSSAttackInAspNetCoreMVC - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
步骤 5. 运行应用程序
dotnet run
输出
如果我们想在完成上述步骤后在表单中提交脚本,网站将清理输入,如果发现脚本,网站将不会接受数据。
提交这些数据后,我们会调试代码中的输入,我的网站就不会受到脚本攻击。
在输出中,您可以看到,经过清理后,我们没有收到与视图中的脚本相关的任何输出
结论
按照这些步骤,您已创建了一个简单的 ASP.NET Core MVC 应用程序,该应用程序允许用户创建和查看博客文章,同时防止 XSS 攻击。该应用程序在将用户输入保存到数据库之前对其进行清理,并使用内置编码功能安全地显示内容。这种方法可确保您的应用程序免受常见的 XSS 漏洞攻击。