0% found this document useful (0 votes)
20 views

AppSecUS - Building Secure ASP - NET Core MVC Applications

Uploaded by

Andra Peruc
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

AppSecUS - Building Secure ASP - NET Core MVC Applications

Uploaded by

Andra Peruc
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

Building Secure ASP.

NET Core MVC


Applications
Niels Tanis - Veracode
About me
• Niels Tanis
• Security Researcher
• Background in:
• .NET development
• Pen tester
• Security Consultancy
• CSSLP
Building (secure) applications is hard!

https://xkcd.com/1349/
Agenda
• Introduction
• Building Secure ASP.NET Core MVC Applications
• Processing data
• Returning data
• Adapt web standards
• Analysing existing solutions
• Conclusion
• Q&A
.NET Core and ASP.NET Core MVC
• .NET Core
• Open Source
• Modular based
• Multi platform (Windows, MacOSX, Linux)
• ASP.NET Core MVC
• Complete rewrite of ASP.NET MVC
• Runs on .NET Core and .NET Framework
Model-View-Controller
Model

View Controller
Program
namespace WebApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

host.Run();
}
}
}
Controller

namespace WebApplication
{
public class DefaultController
{
public string Index()
{
return $"Hello from {this.GetType().ToString()}!”;
}
}
}
Controller
• Convention based resolving; “*Controller”
• Any referenced assembly can expose controllers!
• ConfigureServices in Startup
• ApplicationPartsManager composes set of
resolved controllers
MVC Routing & Model Binding
• Configure in Startup
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

• https://localhost/webapp/info/edit?input=data
• https://localhost/webapp/info/delete/2
• Model binding will fetch from Form inputs, Route Parameters and
Query String
Processing Data
public class OrdersController : Controller
{
private readonly OrderDataContext _context;

public OrdersController(OrderDataContext context)


{
_context = context;
}

public async Task<IActionResult> CreateNew(Order order)


{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
}
Processing Data

[HttpPost]
public async Task<IActionResult> CreateNew(Order order)
{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Processing Data

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateNew(Order order)
{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Processing Data
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateNew(Order order)
{
if (ModelState.IsValid)
{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(order);
}
Processing Data

public class Order


{
public int ID { get; set; }
[EmailAddress]
public string Email { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public decimal TotalPrice { get; set; }
public IEnumerable<OrderDetail> Details { get; set; }
}
Processing Data
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateNew(
[Bind("Description,Email")]Order order)
{
if (ModelState.IsValid)
{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(order);
}
[HttpPost]
Processing Data
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditOrderDetail(int? id)
{
if (id == null) return NotFound();
var orderDetail = await _context.Details
.SingleOrDefaultAsync(c => c.ID == id);
if (await TryUpdateModelAsync(orderDetail)) {
try {
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */) {
ModelState.AddModelError("ERR", "Unable to save changes.”);
}
return RedirectToAction("Index");
}
return View(orderDetail);
}
[HttpPost]
Processing Data
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditOrderDetail(int? id)
{
if (id == null) return NotFound();
var orderDetail = await _context.Details
.SingleOrDefaultAsync(c => c.ID == id);
if (await TryUpdateModelAsync(orderDetail, x => x.Ammount)) {
try {
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */) {
ModelState.AddModelError("ERR", "Unable to save changes.”);
}
return RedirectToAction("Index");
}
return View(orderDetail);
}
Processing Data
• HTTP GET should never change internal state
• [ValidateForgeryToken] or AutoValidateForgeryToken
filter in MVC pipeline
public void ConfigureServices(IServiceCollection services)
{

services.AddMvc(options => options.Filters
.Add(new AutoValidateAntiforgeryTokenAttribute()));

}

• New tag helpers for ASP.NET Core 2.0


Processing Data
• Explicit model validation and binding
• Data Annotations, ModelState.IsValid
• [FromHeader], [FromQuery], [FromRoute], [FromForm] &
[FromBody]
• Overposting data
• [Bind(“…”)]
• TryUpdateFromAsync<T>(objectToUpdate,””,x=>x.Property)
• Specific ViewModels
Presenting Data

public class InfoController


{
public string Index(string name)
{
return $”Hello {name}!”;
}
}
Presenting Data

public class InfoController


{
[Produces("text/html")]
public string Index(string name)
{
return $”Hello {name}!”;
}
}
Presenting Data

public class InfoController


{
public ContentResult Index(string name)
{
return new ContentResult
{
Content = $"Hello {name}!"
};
}
}
Presenting Data

public class InfoController


{
public ContentResult Index(string name)
{
return new ContentResult
{
Content = $"Hello {name}!”,
ContentType = "text/html"
};
}
}
public class InfoController
Presenting Data
{
readonly HtmlEncoder _htmlEncoder;

public InfoController(HtmlEncoder htmlEncoder)


{
_htmlEncoder = htmlEncoder;
}

public ContentResult Index(string name)


{
return new ContentResult
{
Content = $"Hello {_htmlEncoder.Encode(name)}!",
ContentType = "text/html"
};
}
}
Razor View - Presenting Data
@model IEnumerable<WebApplication.Models.Order>

<table class=“table”>
@foreach (var item in Model) {
<tr>
<td>@item.Email</td>
<td>@Html.Raw(item.Description)</td>
<td>@Html.DisplayFor(modelItem => item.TotalPrice)</td>
<td>
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Delete" asp-route-id=“@item.ID">Delete</a>
</td>
</tr>
}
</table>
Presenting Data
• Input validation and context specific output encoding!
• Default encoders
• HtmlEncoder, JavascriptEncoder & UrlEncoder
• TagHelpers do a good job!
• HtmlString
• @Html.Raw(..)
Extending ASP.NET Core MVC
• SameSite Cookie
• https://tools.ietf.org/html/draft-ietf-httpbis-
cookie-same-site-00
• Lax and Strict policies
• Mitigating control against e.g. CSRF
• Timing and Information leakage resource size attack
https://tom.vg
Override CookieManager in Startup
public void ConfigureServices(IServiceCollection services)
{

services.AddDbContext<OrderDataContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString(“OrderConnection")));

services.AddMvc();
services.Configure<CookieAuthenticationOptions>(opts =>
{
opts.CookieManager = new SameSiteCookieManager();
});

}
ASP.NET Core MVC 2.0 - CookiePolicyMiddleware
namespace Microsoft.AspNetCore.Builder
{
public class CookiePolicyOptions
{

/// <summary>
/// Affects the cookie's same site attribute.
/// </summary>
public SameSiteMode MinimumSameSitePolicy { get; set; }
= SameSiteMode.Lax;

}
}
Analyse Existing Solutions
• Roslyn compiler
• Microsoft.CodeAnalysis
Microsoft.CodeAnalysis
public void AnalyseController(SyntaxTree tree)
{
var root = (CompilationUnitSyntax)tree.GetRoot();
var publicMethods = root.DescendantNodes().OfType<MethodDeclarationSyntax>()
.Where(x => x.Modifiers.Any(SyntaxKind.PublicKeyword));

foreach (var method in publicMethods) {


var attributes = method.AttributeLists.SelectMany(x => x.Attributes);
if (attributes.Any(x => x.Name.ToString() == “HttpPost")) {
//Validate that ValidateForgeryToken is also present.
}
var invocations = method.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.Where(x => x.Name.ToFullString() == "ModelState.IsValid").ToList();
//If no found flag method for not checking modelstate.
}
}
Conclusion
• Be aware of attack surface and basic rules like
validation and proper output encoding!
• Compared to ASP.NET MVC defaults are better!
• API’s give good defaults an guidance explicit
changes needed to be ‘unsafe’.
• Quick release cycle allows lot of innovation and
change.
Questions?
Thanks!
▸ ntanis at veracode.com
▸ https://twitter.com/nielstanis
▸ https://github.com/nielstanis

You might also like