En este post trataremos un problema que sufren muchos sitios web, los ataques CSRF (Cross Site Request Forgery).

Las páginas web sufren este tipo de ataques debido a dos factores:

  • No comprobar que los formularios de los que la aplicación recibe la información son los suyos propios.
  • La ingeniería social.

¿Pero qué quiere decir esto?, paso a explicarlo:

¿De que va todo esto?

Un ataque CSRF se basa en el uso de las sesiones de usuario para actuar.

Cuando un usuario inicia sesión en la aplicación atacada, normalmente la sesión perdura durante todo el tiempo que el usuario utiliza esta aplicación, o incluso, cuando el usuario vuelve a utilizar la aplicación tras días después de haberla cerrado. A veces, incluso, la sesión puede no tener fecha de caducidad.

Por eso es muy importante comprobar que la información que recibe la aplicación proviene de un origen conocido.

Un ataque CSRF aprovechara esta falta de comprobación, y que la sesión del usuario este abierta (aquí entra el factor de ingeniería social) para colarle al usuario un formulario que parezca inofensivo, o incluso que sea idéntico al de nuestra aplicación, pero internamente envía información maliciosa para que esta actúe de manera que el atacante saque provecho.

Entremos en código

Nuestra base de datos es muy simple, solo necesitamos una simple tabla con usuarios y contraseñas para hacer login:

CREATE DATABASE CSRFDEMO;
USE CSRFDEMO;

CREATE TABLE CSRFUSER(
CSRF_ID INT IDENTITY(1,1) PRIMARY KEY,
CSRF_USERNAME NVARCHAR(50) UNIQUE,
CSRF_USERPASSWORD NVARCHAR(50)
)

INSERT INTO CSRFUSER VALUES 
('PACO', 'PACO1234'),
('JUAN', 'JUAN1234'),
('JESUS', 'JESUS1234'),
('JOHN', 'JOHN1234')

Podemos copiar este script y ejecutarlo en nuesto SqlServer para crear la BBDD.

En Visual Studio abriremos una nueva solución en la que incluiremos 2 proyectos, en mi caso, uno llamado “CSRF_demo_post”, que será el “cliente” normal de la aplicación.

El otro proyecto llamado “Hacker” emulara lo que haría el atacante. Los dos proyectos son .NET Framework MVC

create1
create2
Como crear los proyectos

Podéis crearlos con contenido MVC, pero yo lo voy a hacer vacío y lo rellenaré a mano explicando paso a paso las partes del proyecto.

proyectos

Para nuestro “cliente” (CSRF_demo_post) vamos a dividir la aplicación en partes.

Para poder conectar a la BBDD debemos añadir la dependencia de Entity Framerwork

entity
Nuget de Entity Framework

Tendremos un contexto para acceder a la base de datos e identificar a los usuarios, crearemos una nueva carpeta “Contexts” sobre el proyecto y añadiremos una nueva clase para el contexto.

CsrfContext.cs

using CSRF_post_demo.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace CSRF_post_demo.Contexts
{
    public class CsrfContext : DbContext
    {
        //Context of the database
        public CsrfContext() : base("name=tjConnStr") { }
        //DbSet to retrieve users
        public DbSet<User> Users { get; set; }
    }
}

Y añadiremos una cadena de conexión con nuestra base de datos en el fichero Web.config del proyecto escribiendo estas líneas dentro de <configuration>.

Web.config de Csrf_demo_post

<connectionStrings>
    <add name="[Nombre de tu conexion]" connectionString="Data Source=LOCALHOST;Initial Catalog=CSRFDEMO;Persist Security Info=True;User ID=[Tu usuario];Password=[Tu contraseña]"
      providerName="System.Data.SqlClient"/>
  </connectionStrings>

También crearemos un repositorio con un modelo para mapear la base de datos, igual crearemos una nueva carpeta “Repositories” para el repositorio y añadiremos las clases.

RepositoryUser.cs

using CSRF_post_demo.Contexts;
using CSRF_post_demo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace CSRF_post_demo.Repositories
{    
    //This class provides the functionallity for handleling users to the controllers.
    public class RepositoryUser
    {
        CsrfContext Context;
        public RepositoryUser()
        {
            Context = new CsrfContext();
        }
        /// <summary>
        /// Searchs for a user in the DDBB by the parameters given.
        /// </summary>
        /// <param name="user">string. Users´s name.</param>
        /// <param name="password">string. User´s password</param>
        /// <returns>An user object or null.</returns>
        public User ExistsUser(string user, string password )
        {
            User existingUser = (from data in Context.Users
                          where data.Name.Equals(user)
                          && data.Password.Equals(password)
                          select data).FirstOrDefault();
            return existingUser;
        }
    }
}

User.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace CSRF_post_demo.Models
{
    //This class maps the users of the application
    [Table("CSRFUSER")]
    public class User
    {
        [Key]
        [Column("CSRF_ID")]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ID { get; set; }
        [Column("CSRF_USERNAME")]
        public string Name { get; set; }
        [Column("CSRF_USERPASSWORD")]
        public string Password { get; set; }
    }
}

Controladores

Ahora crearemos cuatro controladores, Click derecho sobre Controllers > Add Controller:

  • HomeController, como inicio de aplicación. Nos servirá el index
  • ValidationController, que será el encargado de autenticar los usuarios e iniciar sesión

HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace CSRF_post_demo.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
    }
}

ValidationController.cs

using CSRF_post_demo.Models;
using CSRF_post_demo.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace CSRF_post_demo.Controllers
{
    public class ValidationController : Controller
    {
        RepositoryUser Repo;
        public ValidationController()
        {
            Repo = new RepositoryUser();
        }
        // GET: Vulnerable
        public ActionResult Index()
        {
            return View();
        }
        // GET: Login
        public ActionResult Login()
        {
            return View();
        }
        // POST: Login
        [HttpPost]
        public ActionResult Login(string user, string password)
        {
            User currentUser = Repo.ExistsUser(user.ToUpper(), password.ToUpper());
            if (currentUser != null)            
                Session["USER"] = currentUser.Name;
                        
            return RedirectToAction("Index", "Home");
        }       
    }
}

Validation utiliza un método muy simple de sesiones, porque para la demostración de este ataque no hace falta complicar mucho esa parte del código.

  • Y dos controladores más que funcionaran como si fuesen nuestra aplicación de verdad:
    • CSRFController, que es un controlador vulnerable al ataque.
    • ProtectedController, que es un controlador protegido contra el ataque.

CSRFController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace CSRF_post_demo.Controllers
{
    public class CSRFController : Controller
    {
        // GET: Principal
        public ActionResult Index()
        {
            return View();
        }        
        // GET: Buy
        public ActionResult Buy()
        {
            return View();
        }
        // POST: Buy
        [HttpPost]
        //HERE SHOULD BE THE DIRECTIVE "[ValidateAntiForgeryToken]" TO PREVENT THE ATTACK
        public ActionResult Buy(string product, string address)
        {
            TempData["SALE"] = "Product bought: " + product + "€. Sent to address: " + address + ".";
            return RedirectToAction("SaleReport", "CSRF");
        }
        // GET: SaleReport
        public ActionResult SaleReport()
        {
            ViewBag.SaleReport = TempData["SALE"].ToString();
            return View();
        }
    }
}

ProtectedController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace CSRF_post_demo.Controllers
{
    public class ProtectedController : Controller
    {
        // GET: Principal
        public ActionResult Index()
        {
            return View();
        }        
        // GET: Buy
        public ActionResult Buy()
        {
            return View();
        }
        // POST: Buy
        [HttpPost]
        //ATTRBUTE TO VALIDATE THE TOKEN FROM THE FORM
        //IF THE TOKEN IS NOR RECIEVED, THIS ATTRBUTE PREVENTS THE ACTIONRESULT FROM DOING NOTHING
        [ValidateAntiForgeryToken]
        public ActionResult Buy(string product, string address)
        {
            TempData["SALE"] = "Product bought: " + product + "€. Sent to address: " + address + ".";
            return RedirectToAction("SaleReport", "Protected");
        }
        // GET: SaleReport
        public ActionResult SaleReport()
        {
            ViewBag.SaleReport = TempData["SALE"].ToString();
            return View();
        }
    }
}

Estos dos controladores son exactamente iguales con la única diferencia de que uno está protegido y otro no.

Vistas

Tendremos una serie de vistas para cada controlador, podéis crearlas haciendo Click derecho sobre el nombre del ActionResult > Add View:

  • Index (HomeController).
  • Login (ValidationController).
  • Buy y SaleReport (Tanto para CSRFController como para ProtectedController).

Ademas del _Layout.cshtml, ya que es una aplicacion MVC

Index.cshtml

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<h1>CSRF demo</h1>

Login.cshtml

@{
    ViewBag.Title = "Login";
}

<h2>Who are you?</h2>

@using (Html.BeginForm())
{
    <div>
        <label>
            User:
            <input class="form-control" type="text" name="user" value="" required />
        </label>
    </div>
    <div>
        <label>
            Password:
            <input class="form-control" type="password" name="password" value="" required />
        </label>
    </div>
    <div>
        <button class="btn btn-primary" type="submit">Login</button>
    </div>
}

Buy.cshtml (CSRFController)

@{
    ViewBag.Title = "Buy";
}

<h2>Buy</h2>
@using (Html.BeginForm())
{
   @*WHE SHOULD PLACE HERE THE ANTI-FORGERY TOKEN*@
    <div>
        <label>
            Product:
            <select class="form-control" name="product">
                <option value="Iphone X 1000">Iphone X</option>
                <option value="Tesla model 3 10000">Tesla model 3</option>
                <option value="Diamond collar 1500">Diamond collar</option>
            </select>
        </label>
    </div>
    <div>
        <label>
            Address:
            <input class="form-control" type="text" name="address" value="" placeholder="Your address..." />
        </label>
    </div>
    <div>
        <button class="btn btn-warning" type="submit">Buy product</button>
    </div>
}

SaleReport.cshtml (CSRCController)

@{
    ViewBag.Title = "SaleReport";
}

<h2>Sales Report</h2>
<h1>@ViewBag.SaleReport</h1>

Buy.cshtml (ProtectedController)


@{
    ViewBag.Title = "Buy";
}

<h2>Buy</h2>
@using (Html.BeginForm())
{
    //THIS IS THE TOKEN THAT PREVENTS THE ATTACK FROM OCCUR
    @Html.AntiForgeryToken()
    <div>
        <label>
            Product:
            <select class="form-control" name="product">
                <option value="Iphone X 1000">Iphone X</option>
                <option value="Tesla model 3 10000">Tesla model 3</option>
                <option value="Diamond collar 1500">Diamond collar</option>
            </select>
        </label>
    </div>
    <div>
        <label>
            Address:
            <input class="form-control" type="text" name="address" value="" placeholder="Your address..." />
        </label>
    </div>
    <div>
        <button class="btn btn-warning" type="submit">Buy product</button>
    </div>
}

SaleReport.cshtml (ProtectedController)

@{
    ViewBag.Title = "SaleReport";
}

<h2>Sales Report</h2>
<h1>@ViewBag.SaleReport</h1>

_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />
    <script src="~/Scripts/modernizr-2.6.2.js"></script>
</head>
<body> 
    <div class="container">
        <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
            <a class="navbar-brand" href="#">CSRF</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarCollapse">
                <ul class="navbar-nav mr-auto">
                    @if (Session["USER"] != null)
                    {
                        <li class="nav-item active">
                            @Html.ActionLink("vulnerable", "Buy", "CSRF", null, new { @class = "nav-link" })
                        </li>
                        <li class="nav-item active">
                            @Html.ActionLink("not vulnerable", "Buy", "Protected", null, new { @class = "nav-link" })
                        </li>
                    }
                </ul>
                <form class="form-inline mt-2 mt-md-0">
                    @if (Session["USER"] != null)
                    {
                        <a class="navbar-brand" href="#">Welcome back @Session["USER"].ToString()!</a>
                    }
                    else
                    {
                        @Html.ActionLink("Login", "Login", "Validation", null, new { @class = "btn bg-light" });
                    }
                </form>
            </div>
        </nav>                                
    </div>    
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>© @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>

    <script src="~/Scripts/jquery-3.3.1.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
</body>
</html>

Ahora vamos con nuestro proyecto “Hacker”. Este es mucho más simple, solo necesitamos un HomeController con tres vistas: Index, HackerVulneravleCSRF y HackerNotVulneravleCSRF.

HomeController.cs (Proyecto Hacker)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Hacker.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult HackerVulnerableCSRF()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
        public ActionResult HackerNotVulnerableCSRF()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

Index.cshtml


@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@*This is just an index to access the two attack views.*@
<ul class="list-group">
    <li class="list-group-item">
        @Html.ActionLink("Attack to vulnerable controller", "HackerVulnerableCSRF", "Home")
    </li>
    <li class="list-group-item">
        @Html.ActionLink("Attack to not vulnerable controller", "HackerNotVulnerableCSRF", "Home")
    </li>
</ul>

HackerNotVulnerableCSRF.cshtml

@{
    ViewBag.Title = "HackerNotVulnerableCSRF";
}

<h2>This page attack to a not vulnerable CSRF controller</h2>

@*The formulary targets the Action method in the controller with hidden inputs to secretly send the information and perform the attack.*@
<form action="http://localhost:57336/Protected/Buy" method="post">
    <input type="hidden" name="product" value="10 Iphones X 10.000" />
    <input type="hidden" name="address" value="The hackers house" />
    <button class="btn btn-success" type="submit">Press this button for fun!!!</button>
</form>

HackerVulnerableCSRF.cshtml

@{
    ViewBag.Title = "HackerVulnerableCSRF";
}

<h2>This page attack to a vulnerable CSRF controller</h2>

@*The formulary targets the Action method in the controller with hidden inputs to secretly send the information and perform the attack.*@
<form action="http://localhost:57336/CSRF/Buy" method="post">
    <input type="hidden" name="product" value="10 Iphones X 10.000" />
    <input type="hidden" name="address" value="The hackers house" />
    <button class="btn btn-success" type="submit">Press this button for fun!!!</button>
</form>

_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - Mi aplicación ASP.NET</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark">
        <a class="navbar-brand" href="#">Hacker page</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample01" aria-controls="navbarsExample01" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarsExample01">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">It will look like a normal page<span class="sr-only">(current)</span></a>
                </li>                              
            </ul>            
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>© @DateTime.Now.Year - Mi aplicación ASP.NET</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

¡Y con esto ya lo tenemos todo listo para probar la demo!

Demostración

Arranquemos el proyecto “CSRF_demo_post”. Click derecho sobre el proyecto > Debug > Start new instance.

paso1

Nos logueamos en la aplicación.

paso2

En mi caso voy a loguearme con estas credenciales:

  • Usuario: john
  • Contraseña: john1234
paso3

Ahora podemos ver que tenemos la sesión iniciada con nuestro mensaje de bienvenida y que en nuestra barra de navegación han aparecido dos opciones, una a la parte de la aplicación vulnerable y otra a la parte protegida.

paso4

Las dos partes parecen idénticas, pero no lo son. Al acceder a cualquiera de ellas nos encontramos con un pequeño formulario de compra en el que podemos seleccionar un producto de un desplegable, una dirección de envío y podremos comprar el producto con un botón. Accedamos a “vulnerable”.

paso5
Formulario de compra sin proteger

Tras comprar el producto veremos un informe de la compra con el producto, su precio y la dirección de envío.

paso6
Informe de la operación

¡Y en este proyecto ya hemos acabado!, todo parece correcto ¿verdad?, pero si todo funcionase bien no estaríamos aquí, asique vamos a lanzar el proyecto del hacker y ver qué pasa si atacamos la aplicación.

Importante lanzar la aplicación mientras esta está aun iniciada con la sesión abierta.

Ataquemos la aplicación

Mismo proceso de antes, Click derecho sobre el proyecto > Debug > Start new instance.

paso7
Lanzamos la aplicacion hacker

Y al lanzar la aplicación del hacker nos encontraremos con esto .

paso8
Aplicacion del hacker

Una página que puede parecer exactamente igual a la nuestra o completamente distinta y que no levanta sospechas y que el hacker nos habrá hecho abrir sin darnos cuenta debido a la ingeniería social. Desde esta página podemos atacar a las dos partes de nuestro otro proyecto, vamos a probar que pasa si atacamos a la vulnerable.

paso9

La página nos muestra un botón de apariencia inofensiva, pero al pulsar en él…

paso10

¿¡Que ha pasado!?

¡Vaya, que ha ocurrido!, ¿cuándo hemos comprado 10 Iphones?

Lo que ha pasado es muy simple. La página con el botón contenía un formulario oculto con información apuntando a nuestra aplicación, que como no distingue la fuente de la que llegan las peticiones, ha utilizado los datos de nuestra sesión abierta para comprarle al hacker 10 teléfonos y enviarlos a su casa. Todo un desastre. Pero ya que el daño está hecho, no hay que llorar sobre la leche derramada, vamos a ponerle solución al problema.

Solucionemos el problema

Podemos arreglar esto añadiendo dos simples líneas de código en nuestra aplicación.

[ValidateAntiForgeryToken]

Pongamos esta directiva en nuestro controlador:

// POST: Buy
        [HttpPost]
        //ATTRBUTE TO VALIDATE THE TOKEN FROM THE FORM
        //IF THE TOKEN IS NOR RECIEVED, THIS ATTRBUTE PREVENTS THE ACTIONRESULT FROM DOING NOTHING
        [ValidateAntiForgeryToken]
        public ActionResult Buy(string product, string address)
        {
            TempData["SALE"] = "Product bought: " + product + "€. Sent to address: " + address + ".";
            return RedirectToAction("SaleReport", "Protected");
        }
        // GET: SaleReport
        public ActionResult SaleReport()
        {
            ViewBag.SaleReport = TempData["SALE"].ToString();
            return View();
        }

Y en nuestro formulario:

//THIS IS THE TOKEN THAT PREVENTS THE ATTACK FROM OCCUR
    @Html.AntiForgeryToken()

@{
    ViewBag.Title = "Buy";
}

<h2>Buy</h2>
@using (Html.BeginForm())
{
    //THIS IS THE TOKEN THAT PREVENTS THE ATTACK FROM OCCUR
    @Html.AntiForgeryToken()
    <div>
        <label>
            Product:
            <select class="form-control" name="product">
                <option value="Iphone X 1000">Iphone X</option>
                <option value="Tesla model 3 10000">Tesla model 3</option>
                <option value="Diamond collar 1500">Diamond collar</option>
            </select>
        </label>
    </div>
    <div>
        <label>
            Address:
            <input class="form-control" type="text" name="address" value="" placeholder="Your address..." />
        </label>
    </div>
    <div>
        <button class="btn btn-warning" type="submit">Buy product</button>
    </div>
}

Como funciona la protección

Estas dos líneas añaden un “token” para identificar que el formulario del que recibimos la información es en verdad nuestro.

paso11

Si inspeccionamos el código de la página vemos que se ha añadido un input hidden con un valor aleatorio con el nombre de _RequestVerificationToken.

Y además, si utilizamos una herramienta de inspección de cookies en nuestro navegador, podemos observar que también se ha añadido una nueva cookie con el token de verificación.

paso12

Lo que la aplicación está haciendo es lo siguiente:

  1. Genera un valor aleatorio antes de cargar la pagina
  2. Mete ese valor en el input y en la cookie
  3. Al devolver la petición desde el formulario compara esos dos valores, y si no coinciden, la aplicación sabe que es una petición maliciosa.

De esa forma el atacante, al no tener ese token, no puede atacar la aplicación y es expulsado al lanzar el ataque.

Vamos a hacer la prueba.

Ahora que estamos protegidos

Esta vez, atacaremos a la parte protegida de la aplicación.

paso13
paso14

Y ahora, como nuestra aplicación está protegida, ¡el hacker no puede hacer nada que perjudique a nuestros usuarios!

paso15

Autor/a: Andrés Sánchez Robleño

Curso: Microsoft MCSA Web Applications + Microsoft MCSD App Builder + Xamarin

Centro: Tajamar

Año académico: 2018-2019

Enlace a GitHub:https://github.com/AndresSanRo/CSRF_post_demo

Enlace a Linkedin:https://www.linkedin.com/in/andrés-sánchez-robleño-7a5863162/

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.