Etiqueta: Seguridad

  • Cómo validar que el usuario tiene permisos para ver la pagina web

    Cómo validar que el usuario tiene permisos para ver la pagina web

    Me encuentro a menudo con problemas como estos:

    Estoy haciendo un sistema para validar las personas que ingresan a un modulo especifico.
    El sistema solicita una clave de acceso que yo les genero, por ejemplo la página del formulario es el index.html y cuando completan e introducen la clave correcta los lleva a view.html, el problema está en que si una persona pone en la url directamente view.html, pueden ingresar sin pasar por el formulario.
    Como puede hacer para si alguien conoce la URL view.html, al ingresarla en el navegador lo redirija al index.html


    Diseñando mi sitio web con PHP, he notado algo que no es correcto. En la primera pagina, el usuario ingresas sus datos (usuario y contraseña). Luego de que es validado en la base de datos identificando que existe, se le da acceso a la pagina del menú que le permite hacer consultas a la base de datos. Al copiar la URL de la pagina donde esta el menú, note que se puede ingresar sin hacer la validación de usuario y contraseña. Entonces pensé, que pasaría si el usuaria le comparte la URL de la pagina donde esta el menú a otra persona que no tiene permisos? pues ingresará a los datos.

    Está claro que si un usuario accede a un módulo para el que no tiene permisos estamos ante un problema de seguridad… al fin y al cabo, ¡es lo mismo que no haber puesto un formulario de login! (Y en tal caso al menos nos habríamos ahorrado algo de trabajo, ¿no? 🙂 )

    La solución a este problema puede pensarse como la solución a dos sub-problemas:

    Cómo saber si un visitante pasó antes por una página de tu sitio

    Una forma de saber si un usuario ha visitado o no una página de tu sitio antes de llegar a la actual (En este caso view.html) es utilizar algún tipo de marca.

    La implementación más sencilla es utilizar el mecanismo de sesiones que provee PHP.

    Al hacerlo de esta forma te garantizas que la información acompañe al visitante a lo largo de todo su recorrido.

    El uso es muy simple, sólo tienes que escribir algo como:

    <?php
    
    session_start();
    $_SESSION['paso_por_index'] = true;

    Al comienzo del archivo que responde a la URI /index.html

    Cómo redireccionar a un visitante no autorizado

    Luego, el archivo que quieras ocultar debe comenzar con algo como:

    <?php
    
    session_start();
    
    if (!array_key_exists('paso_por_index', $_SESSION)) {
       header('Location: index.html');
       die;
    }

    De esta forma, si la marca no está colocada el script entenderá que el usuario no ha pasado por index.html al llegar a view.html y, en consecuencia, lo redireccionará a la página inicial.

  • Cómo autenticar JWT con PHP de forma segura

    Cómo autenticar JWT con PHP de forma segura

    Un seguidor de Twitter me envía esta pregunta:

    ¿Cuál esería una forma segura de autenticar el jwt que llega en la petición?

    Ante todo gracias Ezequiel y perdona la tardanza en responder.

    Vamos por partes para que se entienda bien de qué estamos hablando.

    Qué es JWT

    JWT son las siglas de JSON Web Tokens. Se trata de una tecnología desarrollada por la empresa Auth0 cuyo objetivo es la transmisión segura de información a través de una red insegura (Internet).

    Como en la mayoría de los mecanismos de seguridad, se trata del envío de información codificada utilizando métodos muy difíciles de descifrar para quien no tenga la información completa pero muy simples para quien sí la posea.

    Desde afuera un token JWT es una cadena de caracteres aleatoria, por ejemplo:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    En realidad, si la miras con algo más de detenimiento notarás que se trata de una cadena particular formada por tres cadenas unidas por un .

    Cuáles son los componentes de un token JWT

    Un token JWT está conformado por:

    1. Un encabezado
    2. La carga útil o payload (El mensaje que se busca transmitir)
    3. Una firma

    El encabezado indica el algoritmo que se utilizará para realizar la codificación y la posterior verificación.

    El payload es una cadena que representa un objeto JSON.

    Por último, la firma se utilizará para darle confiabilidad a la transmisión.

    Dependiendo del algoritmo utilizado esta cadena puede ser una frase cualquiera o un certificado digital.

    Para qué se usa JWT

    Existen muchos casos en los que vale la pena utilizar un token JWT. Uno de los usos más comunes es cuando se busca garantizar la identidad de una entidad que requiere hacer uso de un Servicio Web de nuestra propiedad.

    En general, este mecanismo se adapta mejor que el uso de sesiones estándar.

    Más aún, si la comunicación se realiza entre más de dos actores, JWT ofrece un soporte difícil de lograr con las sesiones.

    Una ventaja clara de JWT contra otros tipos de autenticación es el hecho de que los tokens pueden ser verificados sin necesidad de acceder a información externa (Bases de datos por ejemplo).

    Un punto muy importante a tener en cuenta cuando se usan tokens JWT es que la información transportada no se encuentra encriptada. Sólo la firma lo está.

    Esto permite garantizar que la información transportada no ha sido alterada durante la transmisión, pero no puede garantizar que un agente malicioso la haya leído.

    Cómo leer un token JWT usando PHP

    En el caso de la pregunta que realiza Ezequiel vale el supuesto de que el token ha sido generado por algún tercero del cual se pretende validar su autenticidad.

    Para ello existen, además de la posibilidad de descifrar el token manualmente, una serie de librerías que se detallan en el sitio de Auth0. Dado que se trata de un estándar abierto cualquiera puede crear su propia implementación.

    Tomaré como base de este ejemplo este artículo publicado en el propio sitio de Auth0.

    El primer paso para trabajar con un token JWT es verificar su autenticidad.

    Cómo verificar la autenticidad de un token JWT usando PHP

    Para verificar la autenticidad del token se requiere calcular la firma y validarla contra la recibida.

    Si coinciden se puede asumir que el mensaje no ha sido alterado por terceros, en caso contrario el token debe ser rechazado.

    Podemos realizar esta verificación utilizando este código:

    <?php
    require 'bootstrap.php';
    use Carbon\Carbon;
    
    // get the local secret key
    $secret = getenv('SECRET');
    
    if (! isset($argv[1])) {
        exit('Please provide a key to verify');
    }
    
    $jwt = $argv[1];
    
    // split the token
    $tokenParts = explode('.', $jwt);
    $header = base64_decode($tokenParts[0]);
    $payload = base64_decode($tokenParts[1]);
    $signatureProvided = $tokenParts[2];
    
    // check the expiration time - note this will cause an error if there is no 'exp' claim in the token
    $expiration = Carbon::createFromTimestamp(json_decode($payload)->exp);
    $tokenExpired = (Carbon::now()->diffInSeconds($expiration, false) < 0);
    
    // build a signature based on the header and payload using the secret
    $base64UrlHeader = base64UrlEncode($header);
    $base64UrlPayload = base64UrlEncode($payload);
    $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
    $base64UrlSignature = base64UrlEncode($signature);
    
    // verify it matches the signature provided in the token
    $signatureValid = ($base64UrlSignature === $signatureProvided);
    
    echo "Header:\n" . $header . "\n";
    echo "Payload:\n" . $payload . "\n";
    
    if ($tokenExpired) {
        echo "Token has expired.\n";
    } else {
        echo "Token has not expired yet.\n";
    }
    
    if ($signatureValid) {
        echo "The signature is valid.\n";
    } else {
        echo "The signature is NOT valid\n";
    }

    Para que este código pueda ejecutarse será necesario crear un archivo composer.json con este contenido:

    {
      "require": {
        "vlucas/phpdotenv": "^2.4",
        "nesbot/carbon": ">=2.61.0"
      },
      "autoload": {
        "psr-4": {
          "Src\\": "src/"
        }
      }
    }

    Y ejecutar el comando composer install para instalar todas las dependencias usando composer.

    Por último, es necesario definir la clave secreta en un archivo .env por ejemplo:

    SECRET=Th1s1sMyS3cr3t!

    De esta forma será posible validar cualquier token que haya sido generado utilizando esta clave secreta.

    Puedes probar esto entrando en la página de JWT e ingresando estos datos:

    Con eso puedes copiar el texto en el cuadro «Encoded» y ejecutar el script de validación de esta forma:

    php validate_jwt.php eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.xSRMoZpDHCAREK9x0ELx5yL8Y0qkpPt3eVrK9ItiyUo

    Lo cual te dará un resultado como este:

    Header:
    {"alg":"HS256","typ":"JWT"}
    Payload:
    {"sub":"1234567890","name":"John Doe","exp":1516239022}
    Token has expired.
    The signature is valid.

    Con lo cual, se ha confirmado la autenticidad del token recibido.

    Algunas librerías JWT para PHP

    Esta verificación se realizó analizando manualmente el token y, si bien esto es perfectamente posible, no es lo más conveniente en un ambiente de producción (Principalmente por motivos de mantenibilidad a largo plazo).

    Algo más recomendable es la utilización de alguna librería.

    Una bastante simple es la desarrollada por Firebase.

    Una algo más compleja pero muy interesante para usos avanzados es la implementación de Spomky.

    https://www.techiediaries.com/php-jwt-authentication-tutorial/

  • Cómo autenticar usuarios en WebServices SOAP usando PHP

    Cómo autenticar usuarios en WebServices SOAP usando PHP

    Muchas organizaciones (Especialmente gubernamentales) optan por exponer sus servicios web mediante el protocolo SOAP.

    Para hacer uso de dichos servicios es necesario consumirlos.

    Existen algunas ocasiones en las que no basta con conocer la URL del servicio, también es necesario realizar algún tipo de autenticación para obtener el resultado buscado.

    Autenticación HTTP en WebServices SOAP

    El método más simple de autenticación es el propio de HTTP.

    Si este es el caso, basta con generar una URL del estilo http://usuario@password:dominio/web_service para poder acceder.

    El principal problema de este método es su poca seguridad, ya que las credenciales viajan en cada pedido, por lo tanto, es bastante poco frecuente su uso en servicios web.

    En aplicaciones web normales podrías llegar a encontrártelo (o incluso podrías querer implementarlo).

    API-Key en WebServices SOAP

    Una segunda forma de realizar autenticación es aquella basada en las API-Keys.

    En este escenario, el proveedor del servicio debe decirte cuál es tu clave, la cual deberás enviar mediante algún encabezado como parte de tu petición.

    Para ello, asumiendo que utilices la clase SoapClient, deberás crear un nuevo contexto en el que basar tus peticiones, algo como:

    $soapclient = new SoapClient($wsdl, [ 
        'stream_context' => stream_context_create([ 
         'http'=> [ 
          'header' => "X-Api-Key: 1234abcd"    
         ] 
        ]) 
    ]); 

    Este esquema es algo más seguro, siempre y cuando se realice la comunicación a través de SSL.

    Autenticación via encabezados SOAP

    Otro esquema que suele utilizarse es el de la autenticación mediante encabezados SOAP.

    Este método permite que las credenciales viajen como parte del mensaje enviado lo cual puede ser deseable para evitar depender del protocolo subyacente (HTTP en la mayoría de los casos).

    Para lograrlo debes usar el método __setSoapHeaders y la clase SoapHeader.

    Ejemplo:

    $header = new SoapHeader(
                       $namespace,
                       'UserCredentials'
                       [
                             $UserID,
                             $Pwd
                       ]
    );
    
    $client->__setSoapHeaders($header);

    Claro que tanto el namespace como el nombre y estructura exacta del encabezado deberás validarlo contra el archivo WSDL del servicio al que te quieras conectar, pero la adaptación es simple.

    Autenticación vía WSSE

    Por último hay que mencionar un protocolo especial de seguridad para servicios web: WSSE (o WS-Security).

    Este protocolo es bastante complejo ya que incluye, entre otros, firmas digitales.

    Desafortunadamente, a la fecha no existe una implementación nativa de PHP para este tipo de autenticación, con lo cual no queda mucha opción que crear la nuestra o usar alguna desarrollada por un tercero.

    El punto clave aquí es hacer algunos toques a los encabezados que enviaremos.

    Para ello una buena opción es extender la clase SoapHeader de esta forma:

    class WsseAuthHeader extends SoapHeader 
    {
        private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    
        function __construct($user, $pass, $ns = null) 
        {
            if ($ns) {
                $this->wss_ns = $ns;
            }
    
           $auth = new stdClass();
           $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns); 
           $auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
    
           $username_token = new stdClass();
           $username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns); 
    
           $security_sv = new SoapVar(
               new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
               SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
           parent::__construct($this->wss_ns, 'Security', $security_sv, true);
        }
    }

    Y luego simplemente se trata de agregar este nuevo encabezado a nuestro cliente:

    $client->__setSoapHeaders(new WsseAuthHeader( $UserID, $PWD));

    Y a partir de aquí ya es posible consumir los servicios con normalidad.

  • Cuál es el modo más seguro de tratar con passwords en PHP

    Es bastante común últimamente recibir noticias de que algún sitio de gran popularidad ha sido hackeado (O, como suele comunicarse, «su seguridad se ha visto comprometida»).

    Dependiendo del tipo de sitio del que se trate el problema puede preocuparnos más o menos.

    Claro que eso es cuando somos meramente usuarios del sitio… ¿qué pasa cuándo se trata de un sitio que está bajo nuestra responsabilidad?

    Aclaremos algo antes de seguir: es imposible hacer un sitio 100% libre de vulnerabilidades.

    Si hay gente decidida a romper nuestra seguridad lo van a lograr (Si tienen la capacidad técnica y/o el teléfono de alguien que la tenga).

    Es por eso que es muy importante, no sólo poner trabas a los atacantes si no también dejar un botín poco atractivo para el caso de que lo consigan.

    Algo bastante común es que la gente reutilice sus contraseñas en diferentes sitios, con lo cual, el acceso a las contraseñas de un sitio puede permitir a los atacantes hacerse «gratuitamente» con el acceso a otros…

    Nuevamente, no vamos a poder evitar que se ponga en juego la integridad de los datos que nuestros usuarios dejan en otros sitios, pero al menos podremos decir que no lo han logrado por nuestra bajada de guardia.

    Cómo proteger la información sensible

    La única medida real con la que contamos hoy en día es la encripción de los datos sensibles (Como ser las contraseñas).

    Hasta hace un tiempo se utilizaba mucho la función MD5 para transformar una clave legible humanamente en un texto indescifrable. Por ejemplo el MD5 de ‘Hola mundo!’ es ‘d501194c987486789bb01b50dc1a0adb’.

    Lo que se hacía entonces era calcular el md5 del texto ingresado como contraseña y eso era lo que se almacenaba en la base de datos (y cuando se intentaba un login se volvía a calcular el md5 con lo que se tenía guardado, de modo que la verdadera contraseña no estaba en la base).

    Desafortunadamente, todo avanza rápido en la informática y estas funciones (md5 y sha1) ya no son suficientemente seguras (Las computadoras de hoy son tan rápidas que se ha vuelto un problema menor romper este tipo de encripción).

    ¡Pero no todo está perdido! 🙂

    Más allá de md5 y sha1

    Desde la versión 5.5 de PHP nos encontramos con buenas librerías de criptografía internas. Dos funciones interesantes son password_hash y password_verify.

    Con la primera se obtiene un hash a partir de un texto plano (lo ingresado por el usuario).

    Con la segunda se verifica que un texto plano corresponda con un hash.

    A muy alto nivel lo que logra es lo mismo que antes, sólo que mucho más seguro (Los algoritmos usados por password_hash son más fuertes).

    Pero en realidad es bastante más lo que se logra… Al levantar el nivel de abstracción (usamos una función de hashing que internamente puede usar diversas estrategias para lograr su objetivo) logramos también independizarnos de la implementación particular.

    ¿Qué significa esto? Simplemente que si en un futuro el algoritmo de encripción usado cambia (Por ejemplo porque se encuentra uno mejor), nuestro código no se verá afectado.

    Y ahora, ¿qué pensás hacer con las passwords de tus sitios?

  • Cómo evitar la inyección SQL  en PHP

    Cómo evitar la inyección SQL en PHP

    Uno de los fantasmas más temidos por quienes contratan servicios de desarrollo (especialmente cuando se trata de su primera experiencia) es el de los ataques de hackers.

    Si bien es imposible asegurar al 100% un sistema (de software o de cualquier otro tipo), existe una serie de buenas prácticas que disminuyen sensiblemente la probabilidad de ocurrencia de tales ataques (o al menos, su probabilidad de éxito).

    Por lo general, los ataques se basan en la explotación de código vulnerable como ser algún caso raro que el desarrollador no tuvo en cuenta.

    Uno de los ataques más usuales es el conocido como sql injection.

    De lo que se trata es de ejecutar código sql sin autorización.

    Los scripts de PHP que no están bien escritos pueden ser atacados de esta forma.

    Veamos un ejemplo:

    <?php
    
    $sql = "SELECT * FROM users WHERE nombre = '".$_POST['nombre']."'";
    
    $db->query($sql);

    Si el $_POST se llena normalmente no habría problema (Si lo hace un usuario legítimo de nuestra aplicación), pero… ¿qué pasa si un usuario malicioso lo hace?

    Por ejemplo, qué tal si alguien pusiera algo como esto en el campo nombre:

    ';DROP TABLE users;--

    El sql total quedaría así:

    SELECT * FROM users WHERE nombre = '';DROP TABLE users;--'

    Por si lo querés ver más gráfico, te dejo este excelente comic.

    Bueno… formas de protegerse de esto hay muchas, una muy práctica es utilizar los prepared statements de PDO.

    El ejemplo quedaría de esta forma:

    <?php
    
    $sql = "SELECT * FROM users WHERE nombre = :nombre";
    $st = $db->prepare( $sql );
    $st->execute( [ ':nombre' => $_POST['nombre'] ] );

    De este modo, dejamos en manos de PDO la realización de las validaciones y el agregado de comillas donde corresponda, de modo que el sql a ejecutar quede de esta forma:

    SELECT * FROM users WHERE nombre = '\';DROP TABLE users;--\''

    Con lo cual se vuelve completamente inofensivo 🙂

  • Algunas consideraciones de seguridad cuando se suben archivos vía PHP

    Algunas consideraciones de seguridad cuando se suben archivos vía PHP

    Php permite subir archivos vía HTTP de un modo bastante simple (Si tenés dudas consultá acá).

    Un uso bastante común de esta funcionalidad es la de permitir al visitante ingresar imágenes, por ejemplo: su foto de perfil.

    Existe una serie de consideraciones respecto de la seguridad a tener en cuenta cuando se realiza una tarea de este tipo.

    Con estas medidas estamos intentando evitar que un atacante engañe a nuestro sistema subiendo código malicioso que podría, por ejemplo, darle acceso a información privilegiada.

    Validar que el archivo recibido sea del tipo esperado

    Lo primero de lo que querremos estar seguros es de que el archivo que acabamos de recibir sea efectivamente una imagen (GIF, JPG, etc…). La forma inocente de hacer esta verificación es a través del nombre del archivo (si las últimas tres letras son…).

    La forma correcta de hacer esta verificación es a través del mime-type (Ver acá para más información).

    No almacenarlos en un directorio accesible públicamente

    En general, es conveniente que los archivos recibidos desde terceras partes (especialmente cuando se trata de visitantes), sean almacenados fuera del directorio raíz de nuestro proyecto.

    Desde un script de nuestro proyecto siempre se puede abrir un archivo con algo como:

    fopen(__DIR__.'/../uploads/foto.jpg');

    Contar con un método propio para ver su contenido

    Por último, es conveniente contar con algún método (puede ser un script especialmente diseñado para este efecto) que reciba como parámetro el nombre del archivo que se está solicitando y devuelva su contenido.

    Por ejemplo:

    showFile.php:
    
    <?php
    
    echo file_get_scontents($_GET['f']);

    De este modo, si alguien lograra de algún modo subir código malicioso, al intentar ejecutarlo se encontraría con que en su navegador se vería su propio código (sin haber sido ejecutado nada).

  • Dónde almacenar la configuración de una aplicación PHP de forma segura

    Dónde almacenar la configuración de una aplicación PHP de forma segura

    Una pregunta que me llegó de un amigo que viene del mundo .Net (Que parece ser un poco más organizado o estandarizado que el nuestro :).

    Lo primero que deberíamos preguntarnos es de qué nos estamos queriendo proteger.

    Por lo general, la posibilidad de que alguien externo a nuestra organización tenga acceso a la configuración de nuestra aplicación no parece muy tentadora… ¿por qué? Básicamente porque puede haber información sensible (Como ser contraseñas, nombres de hosts donde están las bases de datos, etc…) que podrían dar a un atacante una ventaja importante si decidiera hacernos un daño.

    Una línea de defensa que tenemos es guardar estos archivos en un directorio que no sea públicamente accesible.

    Si usás el webserver Apache, conocerás seguramente la directiva DocumentRoot: lo que está «a la vista» de los visitantes es exclusivamente lo que está dentro de ese directorio (O subdirectorios, según cómo esté configurado el servidor), pero (y esta es la parte interesante), no es lo único que está a la vista para un script php.

    Ejemplo:

    <VirtualHost *:80>
     ServerName my.domain.com
     ServerAdmin webmaster@localhost
     DocumentRoot /usr/share/apache2/mysite
    
     <Directory /usr/share/apache2/mysite>
      Options FollowSymLinks
      AllowOverride All
     </Directory>
    </VirtualHost>

    Con esta configuración, cuando yo ingrese a la URL http://my.domain.com/index.php, el servidor me dará el HTML generado por la ejecución del archivo /usr/share/apache2/mysite/index.php.

    Imaginemos que index.php tiene algo como esto:

    <?php 
    
    require_once '../config.php';
    echo 'Hola Mundo!';

    Si yo intentara entrar (desde mi browser) al archivo config.php debería escribir algo como http://my.domain.com/../config.php (Eso obviamente, sabiendo que ahí se encuentra el archivo que busco).

    Si bien podría llegar a pasar que el webserver esté mal configurado y permita hacer eso, en la gran mayoría de los casos, eso simplemente será imposible (Los requests están confinados a leer archivos que están dentro del DocumentRoot).

    Si estás usando un framework estándar (Symfony, CakePHP, Laravel, etc…), esto ya estará previsto (Habrá un directorio especial para almacenar los archivos de configuración, pero siempre en última instancia se va a tratar de un tema de permisos configurados a nivel de webserver).

    ¿Conocés alguna otra forma de proteger tus archivos de configuración? ¡Compartila en los comentarios!