1. Información general
En este tutorial, discutiremos los ataques CSRF de falsificación de solicitudes entre sitios y cómo prevenirlos usando Spring Security.
2. Dos ataques CSRF simples
Existen múltiples formas de ataques CSRF; analicemos algunas de las más comunes.
2.1. GET Ejemplos
Consideremos la siguiente solicitud GET utilizada por usuarios registrados para transferir dinero a una cuenta bancaria específica "1234" :
GET //bank.com/transfer?accountNo=1234&amount=100
Si el atacante desea transferir dinero de la cuenta de una víctima a su propia cuenta, "5678" , debe hacer que la víctima active la solicitud:
GET //bank.com/transfer?accountNo=5678&amount=1000
Hay varias formas de lograrlo:
- Enlace: el atacante puede convencer a la víctima de que haga clic en este enlace, por ejemplo, para ejecutar la transferencia:
Show Kittens Pictures
- Imagen: el atacante puede utilizar unetiqueta con la URL de destino como fuente de la imagen, por lo que el clic ni siquiera es necesario. La solicitud se ejecutará automáticamente cuando se cargue la página:

2.2. Ejemplo de POST
Si la solicitud principal debe ser una solicitud POST, por ejemplo:
POST //bank.com/transfer accountNo=1234&amount=100
Luego, el atacante necesita que la víctima ejecute un procedimiento similar:
POST //bank.com/transfer accountNo=5678&amount=1000
Ni el o la funcionará en este caso. El atacante necesitará un - como sigue:
Sin embargo, el formulario se puede enviar automáticamente usando Javascript, de la siguiente manera:
...
2.3. Simulación práctica
Ahora que entendemos cómo se ve un ataque CSRF, simulemos estos ejemplos dentro de una aplicación Spring.
Comenzaremos con una implementación de controlador simple, el BankController :
@Controller public class BankController { private Logger logger = LoggerFactory.getLogger(getClass()); @RequestMapping(value = "/transfer", method = RequestMethod.GET) @ResponseBody public String transfer(@RequestParam("accountNo") int accountNo, @RequestParam("amount") final int amount) { logger.info("Transfer to {}", accountNo); ... } @RequestMapping(value = "/transfer", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public void transfer2(@RequestParam("accountNo") int accountNo, @RequestParam("amount") final int amount) { logger.info("Transfer to {}", accountNo); ... } }
Y también tengamos una página HTML básica que activa la operación de transferencia bancaria:
Transfer Money to John Account Number Amount
Esta es la página de la aplicación principal, que se ejecuta en el dominio de origen.
Tenga en cuenta que hemos simulado tanto un GET a través de un enlace simple como un POST a través de un simple.
Ahora, veamos cómo se vería la página del atacante :
Show Kittens Pictures
Esta página se ejecutará en un dominio diferente: el dominio del atacante.
Finalmente, ejecutemos las dos aplicaciones, la aplicación original y la del atacante, localmente, y accedamos primero a la página original:
//localhost:8081/spring-rest-full/csrfHome.html
Luego, accedamos a la página del atacante:
//localhost:8081/spring-security-rest/api/csrfAttacker.html
Al rastrear las solicitudes exactas que se originan en esta página del atacante, podremos detectar inmediatamente la solicitud problemática, presionando la aplicación original y completamente autenticada.
3. Configuración de seguridad de primavera
Para usar la protección CSRF de Spring Security, primero debemos asegurarnos de usar los métodos HTTP adecuados para cualquier cosa que modifique el estado ( PATCH , POST , PUT y DELETE, no GET).
3.1. Configuración de Java
La protección CSRF está habilitada de forma predeterminada en la configuración de Java. Aún podemos deshabilitarlo si necesitamos:
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable(); }
3.2. Configuración XML
En la configuración XML anterior (anterior a Spring Security 4), la protección CSRF estaba deshabilitada de forma predeterminada y podríamos habilitarla de la siguiente manera:
...
A partir de Spring Security 4.x , la protección CSRF también está habilitada de forma predeterminada en la configuración XML; por supuesto, aún podemos deshabilitarlo si necesitamos:
...
3.3. Parámetros de forma adicional
Finalmente, con la protección CSRF habilitada en el lado del servidor, también necesitaremos incluir el token CSRF en nuestras solicitudes en el lado del cliente:
3.4. Usando JSON
No podemos enviar el token CSRF como parámetro si usamos JSON; en su lugar, podemos enviar el token dentro del encabezado.
Primero necesitaremos incluir el token en nuestra página, y para eso, podemos usar metaetiquetas:
Luego construiremos el encabezado:
var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(header, token); });
4. Prueba de CSRF desactivado
Con todo eso en su lugar, pasaremos a hacer algunas pruebas.
Primero intentemos enviar una solicitud POST simple cuando CSRF está deshabilitado:
@ContextConfiguration(classes = { SecurityWithoutCsrfConfig.class, ...}) public class CsrfDisabledIntegrationTest extends CsrfAbstractIntegrationTest { @Test public void givenNotAuth_whenAddFoo_thenUnauthorized() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) ).andExpect(status().isUnauthorized()); } @Test public void givenAuth_whenAddFoo_thenCreated() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()) ).andExpect(status().isCreated()); } }
Como habrás notado, estamos usando una clase base para contener la lógica auxiliar de prueba común : CsrfAbstractIntegrationTest :
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration public class CsrfAbstractIntegrationTest { @Autowired private WebApplicationContext context; @Autowired private Filter springSecurityFilterChain; protected MockMvc mvc; @Before public void setup() { mvc = MockMvcBuilders.webAppContextSetup(context) .addFilters(springSecurityFilterChain) .build(); } protected RequestPostProcessor testUser() { return user("user").password("userPass").roles("USER"); } protected String createFoo() throws JsonProcessingException { return new ObjectMapper().writeValueAsString(new Foo(randomAlphabetic(6))); } }
Tenga en cuenta que, cuando el usuario tenía las credenciales de seguridad adecuadas, la solicitud se ejecutó correctamente, no se requirió información adicional.
Eso significa que el atacante puede simplemente usar cualquiera de los vectores de ataque discutidos anteriormente para comprometer fácilmente el sistema.
5. Prueba habilitada para CSRF
Ahora, habilitemos la protección CSRF y veamos la diferencia:
@ContextConfiguration(classes = { SecurityWithCsrfConfig.class, ...}) public class CsrfEnabledIntegrationTest extends CsrfAbstractIntegrationTest { @Test public void givenNoCsrf_whenAddFoo_thenForbidden() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()) ).andExpect(status().isForbidden()); } @Test public void givenCsrf_whenAddFoo_thenCreated() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()).with(csrf()) ).andExpect(status().isCreated()); } }
Ahora, cómo esta prueba usa una configuración de seguridad diferente, una que tiene la protección CSRF habilitada.
Ahora, la solicitud POST simplemente fallará si el token CSRF no está incluido, lo que por supuesto significa que los ataques anteriores ya no son una opción.
Finalmente, observe el método csrf () en la prueba; esto crea un RequestPostProcessor que automáticamente completará un token CSRF válido en la solicitud con fines de prueba.
6. Conclusión
En este artículo, discutimos un par de ataques CSRF y cómo prevenirlos usando Spring Security.
La implementación completa de este tutorial se puede encontrar en el proyecto GitHub; este es un proyecto basado en Maven, por lo que debería ser fácil de importar y ejecutar tal como está.