New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Microsoft Security Advisory CVE-2018-0785 - ASP.NET Core Templates enable Cross Site Request Forgery #284

Open
blowdart opened this Issue Jan 9, 2018 · 0 comments

Comments

Projects
None yet
2 participants
@blowdart
Member

blowdart commented Jan 9, 2018

Microsoft Security Advisory CVE-2018-0785

ASP.NET Core Templates enable Cross Site Request Forgery

Executive Summary

Microsoft is releasing this security advisory to provide information about a vulnerability in the public versions of ASP.NET Core 2.0. This advisory also provides guidance on what developers can do to update their applications correctly.

Microsoft is aware of a Cross Site Request Forgery vulnerability in the individual authentication templates for ASP.NET Core 2.0. An attacker who successfully exploited this vulnerability could change the recovery codes associated with the victim's user account without his/her consent. As a result, a victim of this attack may be permanently locked out of his/her account after loosing access to his/her 2FA device, as the initial recovery codes would be no longer valid.

The security update addresses the vulnerability by correcting the ASP.NET Core project templates.

Developers who have generated applications from the vulnerable templates should change their code using the following instructions. They should also change their code to address a further vulnerability, CVE-2018-0784, which is in the same templates.

You should also install the latest .NET Core SDK, version 2.1.4, from https://www.microsoft.com/net/download/ as will update the templates to correct the issue for any new applications created after its installation. Existing applications must be manually updated using the instructions below.

Discussion

Please use aspnet/Templating#224 for discussion of this advisory.

Mitigation Factors

ASP.NET Core applications which are not created using the ASP.NET Core 2.0 Individual Authentication templates are not vulnerable to this issue.

Affected Software

The vulnerabilities affect any Microsoft .NET Core project if it uses any of affected runtime versions listed below and have generated applications using Individual Authentication with usernames and passwords stored within the application. Applications which use Azure Active Directory, or Azure Active Directory B2C are not affected.

Vulnerable .NET SDK Version Fixed SDK Version
2.0.0, 2.0.2, 2.0.3, 2.1.2, 2.1.3 2.1.4

Advisory FAQ

How do I know if I am affected?

Your application will be affected if you generated it using the ASP.NET 2.0 Web Application template or the ASP.NET 2.0 Web Application (Model/View/Controller) template from a vulnerable SDK version where you have selected individual authentication where user accounts are stored in-app.

To check the runtimes installed on a computer you must view the contents of the runtime folder. By default these are

Operating System Location
Windows C:\Program Files\dotnet\sdk\
macOS /usr/local/share/dotnet/sdk/
Supported Linux platforms /usr/share/dotnet/sdk/

Each SDK version is installed in its own directory, where the directory name is the version number. If you do not have a directory for 2.1.4 then any applications generated for ASP.NET Core 2.0 using Individual Authentication may be vulnerable.. Downloads for all supported platforms can be acquired from https://www.microsoft.com/net/download/

How do I fix my affected application?

Applications can be fixed by changing the code created during application generation using the following instructions.

For ASP.NET Core 2.0 Web Application (Razor Pages)

  1. Open Pages\Account\Manage\EnableAuthenticator.cshtml.cs find the OnPostAsync() method. The last line of this file should be
return RedirectToPage("./GenerateRecoveryCodes");

Replace this line with the following code

var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
TempData["RecoveryCodes"] = recoveryCodes.ToArray();
return RedirectToPage("./ShowRecoveryCodes");
  1. Open the Pages\Account\Manage\GenerateRecoveryCodes.cshtml and replace its contents with
@page
@model GenerateRecoveryCodesModel
@{
    ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
    ViewData["ActivePage"] = "TwoFactorAuthentication";
}

<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
    <p>
        <span class="glyphicon glyphicon-warning-sign"></span>
        <strong>This action generates new recovery codes.</strong>
    </p>
    <p>
        If you lose your device and don't have the recovery codes you will lose access to your account.
    </p>
    <p>
        Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
        used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys</a>.
    </p>
</div>

<div>
    <form method="post" class="form-group">
        <button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
    </form>
</div>
  1. Open the Pages\Account\Manage\GenerateRecoveryCodes.cshtml.cs file. Replace its contents with
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Company.WebApplication1.Data;

namespace Company.WebApplication1.Pages.Account.Manage
{
    public class GenerateRecoveryCodesModel : PageModel
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly ILogger<GenerateRecoveryCodesModel> _logger;

        public GenerateRecoveryCodesModel(
            UserManager<ApplicationUser> userManager,
            ILogger<GenerateRecoveryCodesModel> logger)
        {
            _userManager = userManager;
            _logger = logger;
        }

        public async Task<IActionResult> OnGetAsync()
        {
            var user = await _userManager.GetUserAsync(User);
            if (user == null)
            {
                throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
            }

            if (!user.TwoFactorEnabled)
            {
                throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.");
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            var user = await _userManager.GetUserAsync(User);
            if (user == null)
            {
                throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
            }

            if (!user.TwoFactorEnabled)
            {
                throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
            }

            var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
            TempData["RecoveryCodes"] = recoveryCodes.ToArray();

            _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id);

            return RedirectToPage("./ShowRecoveryCodes");
        }
    }
}
  1. Create a new file, Pages\Account\Manage\ShowRecoveryCodes.cshtml with the following contents
@page
@model ShowRecoveryCodesModel
@{
    ViewData["Title"] = "Recovery codes";
    ViewData["ActivePage"] = "TwoFactorAuthentication";
}

<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
    <p>
        <span class="glyphicon glyphicon-warning-sign"></span>
        <strong>Put these codes in a safe place.</strong>
    </p>
    <p>
        If you lose your device and don't have the recovery codes you will lose access to your account.
    </p>
</div>
<div class="row">
    <div class="col-md-12">
        @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
        {
            <code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
        }
    </div>
</div>
  1. Create a new file Pages\Account\Manage\ShowRecoveryCodes.cshtml.cs with the following contents
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Company.WebApplication1.Pages.Account.Manage
{
    public class ShowRecoveryCodesModel : PageModel
    {
        public string[] RecoveryCodes { get; private set; }

        public IActionResult OnGet()
        {
            RecoveryCodes = (string[])TempData["RecoveryCodes"];
            if (RecoveryCodes == null)
            {
                return RedirectToPage("TwoFactorAuthentication");
            }

            return Page();
        }
    }
}
  1. Recompile your application and test you can generate 2fa recovery codes correctly, then redeploy your application.

For ASP.NET Core 2.0 Web Application (Model/View/Controller)

  1. Open the Controllers\ManageController.cs file and find
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";

and add a new declaration underneath,

private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
private const string RecoveryCodesKey = nameof(RecoveryCodesKey);
  1. Still in Controllers\ManageController.cs file and find the EnableAuthenticator(EnableAuthenticatorViewModel model) method. The last line in this method should be
return RedirectToAction(nameof(GenerateRecoveryCodes));

Replace this final line with

var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
TempData[RecoveryCodesKey] = recoveryCodes.ToArray();
return RedirectToAction(nameof(ShowRecoveryCodes));
  1. Create a new method in Controllers\ManageController.cs called ShowRecoveryCodes() with the following code
[HttpGet]
public IActionResult ShowRecoveryCodes()
{
    var recoveryCodes = (string[])TempData[RecoveryCodesKey];
    if (recoveryCodes == null)
    {
        return RedirectToAction(nameof(TwoFactorAuthentication));
    }

    var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes };
    return View(model);
}
  1. Still in Controllers\ManageController.cs Create a new action method in the controller file, GenerateRecoveryCodesWarning() containing the following code
[HttpGet]
public async Task<IActionResult> GenerateRecoveryCodesWarning()
{
    var user = await _userManager.GetUserAsync(User);
    if (user == null)
    {
        throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    }

    if (!user.TwoFactorEnabled)
    {
        throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.");
    }

    return View(nameof(GenerateRecoveryCodesWarning));
}
  1. Still in Controllers\ManageController.cs file and find the GenerateRecoveryCodes() method. The code should look as follows
[HttpGet]
public async Task<IActionResult> GenerateRecoveryCodes()
{
    ...
}

Replace the method with the following

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> GenerateRecoveryCodes()
{
    var user = await _userManager.GetUserAsync(User);
    if (user == null)
    {
        throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    }

    if (!user.TwoFactorEnabled)
    {
        throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
    }

    var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
    _logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);

    var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };

    return View(nameof(ShowRecoveryCodes), model);
}
  1. Create a new View in Views\Manage\ShowRecoveryCodes.cshtml with the following content
@model ShowRecoveryCodesViewModel
@{
    ViewData["Title"] = "Recovery codes";
    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}

<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
    <p>
        <span class="glyphicon glyphicon-warning-sign"></span>
        <strong>Put these codes in a safe place.</strong>
    </p>
    <p>
        If you lose your device and don't have the recovery codes you will lose access to your account.
    </p>
</div>
<div class="row">
    <div class="col-md-12">
        @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
        {
            <code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
        }
    </div>
</div>
  1. Create a new ShowRecoveryCodesViewModel class in the Models\ManageViewModels folder and create the class as follows
public class ShowRecoveryCodesViewModel
{
    public string[] RecoveryCodes { get; set; }
}
  1. Delete the Models\ManageViewModels\generateRecoveryCodesViewModel.cs file.

  2. Open the Views\Manage\GenerateRecoveryCodes.cshtml file. Replace the contents with

@{
    ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
}

<h2>@ViewData["Title"]</h2>

<div class="alert alert-warning" role="alert">
    <p>
        <span class="glyphicon glyphicon-warning-sign"></span>
        <strong>This action generates new recovery codes.</strong>
    </p>
    <p>
        If you lose your device and don't have the recovery codes you will lose access to your account.
    </p>
    <p>
        Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
        used in an authenticator app you should <a asp-action="ResetAuthenticatorWarning">reset your authenticator keys.</a>
    </p>
</div>

<div>
    <form asp-action="GenerateRecoveryCodes" method="post" class="form-group">
        <button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
    </form>
</div>
  1. Edit Views\Manage\TwoFactorAuthentication.cshtml to change <a asp-action="GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a> to <a asp-action="GenerateRecoveryCodesWarning" class="btn btn-default">Reset recovery codes</a>.

  2. Recompile your application and test you can generate 2fa recovery codes correctly, then redeploy your application.

Other Information

Reporting Security Issues

If you have found a potential security issue in .NET Core, please email details to secure@microsoft.com. Reports may qualify for the .NET Core Bug Bounty. Details of the .NET Core Bug Bounty including Terms and Conditions are at https://aka.ms/corebounty.

Support

You can ask questions about this issue on GitHub in the .NET Core or ASP.NET Core organizations. These are located at https://github.com/dotnet/ and https://github.com/aspnet/. The Announcements repo for each product (https://github.com/dotnet/Announcements and https://github.com/aspnet/Announcements) will contain this bulletin as an issue and will include a link to a discussion issue where you can ask questions.

Acknowledgments

Thanks to Kévin Chalet for reporting this issue.

External Links

CVE-2018-0785

Revisions

V1.1 (Jan 9, 2018): Updated Razor Pages fix to be correct. Note that new generated applications will have the incorrect link. Thanks @scottsauber.
V1.0 (Jan 9, 2018): Advisory published.

Version 1.1
Last Updated 2018-01-09

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.