Recibir reportes de violaciones Content Security Policy y HTTP Public Key Pinning en Json con PHP

Las cabeceras de seguridad CSP (Content Security Policy) y HPKP (HTTP Public Key Pinning), tienen la posibilidad de incluir la opción “report-uri” para especificar una URL a la que enviar un reporte (JSON) si alguna de las políticas es incumplida. Se mostrará de forma muy básica la creación de un script PHP que reciba dicho reporte y lo almacene en un archivo. También es posible utilizar servicios del tipo “report-uri.io” en vez de utilizar nuestra propia URL o código.

Ejemplo de cabecera HPKP: Public-Key-Pins / Public-Key-Pins-Report-Only.

Public-Key-Pins: pin-sha256="IGSslWCxf82ibQegGB4vxKCbe4AuKICYfgTqRVMNjG8="; pin-sha256="+kMuUCZKtW4uAIwWVMDIQWB6ppGGTZhTD08o3aaBiaI="; pin-sha256="iwLBVDWmS8LxRUMXmJkvgeouEyQ+V98PVrd/E2Wl6T4="; max-age=600; report-uri="https://test.report-uri.io/report/ScottHelme"

Ejemplo de cabecera CSP: Content-Security-Policy / Content-Security-Policy-Report-Only.

Content-Security-Policy: default-src 'self'; script-src 'self' https://ajax.googleapis.com https://maxcdn.bootstrapcdn.com https://www.google-analytics.com https://platform.twitter.com https://cdn.datatables.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com; img-src 'self' data: https://www.google-analytics.com https://secure.gravatar.com; font-src 'self' https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com; connect-src 'self'; report-uri https://report-uri.io/report/ScottHelme/

Código PHP para recibir reportes HPKP / CSP.

<?php
 
function getUserIP()
{
    $client  = @$_SERVER['HTTP_CLIENT_IP'];
    $forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
    $remote  = $_SERVER['REMOTE_ADDR'];
 
    if(filter_var($client, FILTER_VALIDATE_IP))  { $ip = $client; }
    elseif(filter_var($forward, FILTER_VALIDATE_IP)) { $ip = $forward; }
    else { $ip = $remote; }
    return $ip;
}
 
 
$user_ip = getUserIP();
 
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$data = file_get_contents("php://input");
file_put_contents($myFile, $data);
 
// Visualizar el valor insertado.
//print_r($data); 
 
$myFile = "/usr/share/nginx/www/XXX/hpkp_csp_reportes/".$user_ip."_".date('m-d-Y_hia');
$fh = fopen($myFile, 'w') or die("ERROR: can't open file");
fwrite($fh, $data);
fclose($fh);
}
 
?>

Para el ejemplo dado el directorio “/usr/share/nginx/www/XXX/hpkp_csp_reportes/” debe tener el mismo propietario del servidor web para poder escribir dentro. Sería recomendable también completar el script para filtrar el contenido según las necesidades o separar el tipo de reporte (CSP / HPKP). Cada reporte genera un fichero del tipo “171.10.5.120_02-14-2014_0312am” con un contenido similar a este si se trata de HPKP.

{    "date-time": "2014-12-26T11:52:10Z",    "hostname": 'www.example.org',    "port": 443,    "effective-expiration-date": "2014-12-31T12:59:59",    "include-subdomains": true,    "served-certificate-chain": [        "-----BEGINCERTIFICATE-----\nMIIAuyg[...]aqU0CkVDNx\n-----ENDCERTIFICATE-----"    ],    "validated-certificate-chain": [        "-----BEGINCERTIFICATE-----\nEBDCCygAwIBA[...]PX4WecNx\n-----ENDCERTIFICATE-----"    ],    "known-pins": [        "pin-sha256=\"dUezRu9zOECb901Md727xWltNsj0e6qzGk\"",        "pin-sha256=\"E9CqVKB9+xZ9INDbd+2eRQozqbQ2yXLYc\""    ]}

Simular un reporte de violación de Content-Security-Policy / Content-Security-Policy-Report-Only con Curl.

curl -H 'Content-Type: application/csp-report;charset=utf-8' --data '{"hpkp-report":{"document-uri":"https://example.com/foo/bar","referrer":"https://www.google.com/","violated-directive":"default-src self","original-policy":"default-src self; report-uri /csp-hotline.php","blocked-uri":"http://evilhackerscripts.com"}}'   https://report-uri.io/report/ed1db1a9e487d9d21967da15aee8034d

Simular un reporte de violación de Public-Key-Pins / Public-Key-Pins-Report-Only con Curl.

curl -vX POST https://midominio.com/report-pinning.php -d @fichero.json --header "Content-Type: application/json"
fichero.json
{
    "date-time": "2014-12-26T11:52:10Z",
    "hostname": 'www.example.org',
    "port": 443,
    "effective-expiration-date": "2014-12-31T12:59:59",
    "include-subdomains": true,
    "served-certificate-chain": [
        "-----BEGINCERTIFICATE-----\nMIIAuyg[...]tq<marquee>U0CkVDNx\n-----ENDCERTIFICATE-----"
    ],
    "validated-certificate-chain": [
        "-----BEGINCERTIFICATE-----\nEBDCCygAwIBA[...]PX4WecNx\n-----ENDCERTIFICATE-----"
    ],
    "known-pins": [
        "pin-sha256=\"dUezRu9zOECb901Md727xWltNsj0e6qzGk\"",
        "pin-sha256=\"E9CqVKB9+xZ9INDbd+2eRQozqbQ2yXLYc\""
    ]
}

Explicación del funcionamiento del test HPKP para navegadores.

El test se basa en visitar la URL https://projects.dm.id.lv/Public-Key-Pins_test y posteriormente el subdominio. https://pkptest.projects.dm.id.lv/pkp-testresult.html.

La primera URL define por medio de la cabecera “Public-Key-Pins” las huellas digitales SPKI permitidas para el dominio + subdominios. La segunda URL pertenece a un subdominio que no maneja la misma clave pública y por lo tanto la comprobación (pinning) de dicho subdominio mostrará un aviso de seguridad.

# Huella digital SPKI en uso por projects.dm.id.lv.
openssl s_client -servername projects.dm.id.lv -connect projects.dm.id.lv:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 
8MfHQC9XAUF/XBmQ/mZ8S/XEc5aSYzOlj0EHTj870+s=
# Huella digital SPKI en uso por pkptest.projects.dm.id.lv.
openssl s_client -servername pkptest.projects.dm.id.lv -connect pkptest.projects.dm.id.lv:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 
ufn+l7wffXClJwW5mrRFm8LNqWwUz6QDLdIbdA1yEtc=

Como se puede observar ninguna fingerprint (SPKI) coincide con “ufn+l7wffXClJwW5mrRFm8LNqWwUz6QDLdIbdA1yEtc=”

 # Cabecera Public-Key-Pins de projects.dm.id.lv.
curl -qsIL https://projects.dm.id.lv/Public-Key-Pins_test | grep -i Pin
Public-Key-Pins: pin-sha256="8MfHQC9XAUF/XBmQ/mZ8S/XEc5aSYzOlj0EHTj870+s="; pin-sha256="tpFbv65QoYvcWNVl7gAEd1FAWWn/pjL8Fo2+f1pTrC8="; pin-sha256="WKbBsAclTiyDM7EEJ5yUmrWmp9DxWM/hG+D+wcCLA24="; pin-sha256="nxpEakAMgSw92zksspA8LdZyrdW/MGGr70VfcIT7DBU="; max-age=31536000; includeSubDomains

En navegadores Firefox se obtiene el siguiente mensaje.

Su conexión no es segura

El propietario de pkptest.projects.dm.id.lv ha configurado su sitio web de manera incorrecta. Para evitar que su información sea robada, Firefox no ha conectado con este sitio web.

Este sitio usa Seguridad estricta de transporte de HTTP (HSTS) para especificar que Firefox sólo se conecte a él de modo seguro. Como resultado, no es posible añadir una excepción para este certificado.

pkptest.projects.dm.id.lv usa un certificado de seguridad no válido. No se confía en el certificado porque el certificado emisor es desconocido. El servidor podría no estar enviando los certificados intermedios apropiados. Puede ser necesario importar un certificado raíz adicional. (Código de error: sec_error_unknown_issuer)

Como se ve, el servidor ofrece una serie de huellas digitales válidas, eso es para evitar problemas si la clave privada en producción es vulnerada. Al tener otros juegos de llaves como backup, si la que está en uso es vulnerada, se puede rápidamente solicitar otro certificado usando otro juego de llaves. De esta manera los visitantes podrán volver a entrar en la web ya que el pinning vuelve a dar un resultado válido.

Los navegadores aceptan cualquiera de las huellas digitales que previamente hayan sido enviadas por el servidor. En el caso del ejemplo tenemos tres posibles juegos de llaves para configurar el servidor HTTPS. Si se vulneran las tres llaves privadas, los clientes con navegadores compatibles con “Certificate Pinning” no podrán entrar a la web mientras dure la restricción (max-age), que en este caso sería de un año (31536000 segundos).

También es posible usar la cabecera “Public-Key-Pins-Report-Only” para no bloquear el acceso y solo reportar mediante “report-uri”.

Realizar un test de soporte HPKP para el navegador en local.

  1. Visitar una web con “Certificate pinning”, por ejemplo, freetsa.org
  2. Modificar el fichero /etc/hosts para que el dominio apunte a otra IP: “54.195.242.167 freetsa.org”
  3. Visitar de nuevo la web y en Firefox se mostrará el siguiente mensaje.
Conexión segura fallida

An error occurred during a connection to freetsa.org. The server uses key pinning (HPKP) but no trusted certificate chain could be constructed that matches the pinset. Key pinning violations cannot be overridden. (Error code: mozilla_pkix_error_key_pinning_failure)

La página que está intentando ver no se puede mostrar porque la autenticidad de los datos recibidos no ha podido ser verificada.

Contacte con los propietarios del sitio web para informarles de este problema.

Informar de la dirección y la información del certificado de freetsa.org nos ayudará a identificar y bloquear los sitios maliciosos. ¡Gracias por ayudar a crear una web más segura!

Borrar del navegador la configuración guardada relativa a HSTS (HTTP Strict Transport Security)

Navegadores Mozilla (Firefox, Iceweasel, Seamonkey).

Fichero dentro del perfil de mozilla: $HOME/.mozilla/firefox/XXXX.default/SiteSecurityServiceState.txt

www.busindre.com:HPKP	375	17635	1523733712743,1,0,C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=
chat.busindre.com:HSTS	9	17627	1586092838606,1,1,2
busindre.com:HPKP	381	17635	1523733705640,1,0,C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=
busindre.com:HSTS	389	17635	1586805605639,1,1,2
www.busindre.com:HSTS	392	17635	1586805612741,1,1,2
freetsa.org:HSTS	235	17635	1586736764591,1,1,2
freetsa.org:HPKP	73	17635	1526256764592,1,0,C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=
www.freetsa.org:HPKP	34	17635	1526256706056,1,0,C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=
www.freetsa.org:HSTS	55	17635	1586736706055,1,1,2
...

Chrome / Chromium: Visitar la siguiente URL, escribir el dominio y borrarlo.

chrome://net-internals/#hsts

Safari.

Borrar el fichero $HOME/Library/Cookies/HSTS.plist

Enlaces de interés