Cómo proteger un servicio web RESTful de php con SSL / TLS y / o seguridad de nivel de mensaje

Tengo un servicio web RESTful escrito en PHP que usa JSON para la comunicación. Algunos de los datos transmitidos son realmente confidenciales (contraseñas) y estoy buscando una manera de lograr un nivel de seguridad razonable para el servicio. El cliente es una aplicación Silverlight 4.

He estado buscando información clara sobre cómo implementar SSL / TLS (¿supongo que la autenticación del certificado del cliente entra en esa categoría?) Y la seguridad del nivel del mensaje, pero no puedo encontrar buenos ejemplos con respecto a la implementación real de estas medidas de seguridad en un php + json servicio web. Estaría muy agradecido por cualquier información y ejemplos prácticos. Soy consciente de los principios, simplemente no tengo mucha experiencia con php. Actualmente, la única medida de seguridad que tengo es un sistema de token de autenticación muy básico que, al iniciar sesión con éxito, crea una sesión en el servidor y proporciona al usuario un token de autenticación para cualquier comunicación posterior (hasta que la sesión expire o el usuario se conecte desde un IP diferente). Realmente quiero al menos proteger el tráfico sensible, como las contraseñas.

Finalmente, ¿cuáles son los problemas de seguridad que tengo que tener en cuenta después de implementar TLS y tal vez la seguridad de la capa de mensajes, como en las vulnerabilidades y los exploits?

Gracias de antemano.

Suponiendo que tiene HTTPS configurado correctamente utilizando SSL / TLS su principal preocupación es cómo implementar la autenticación para su servicio RESTful. Dado que HTTPS utilizará SSL / TLS para cifrar la comunicación entre el cliente y el cifrado del servidor, no es algo de lo que deba preocuparse. Si necesita comprender cómo configurar correctamente SSL / TLS, lea Understanding SSL / TLS

Las mejores prácticas para asegurar el servicio RESTful ya se han discutido en Autenticación RESTful y Mejores prácticas para asegurar una API REST / servicio web .

Para resumir, analiza 3 opciones

  • Autorización básica de HTTP sobre HTTPS
  • Cookies y gestión de sesiones
  • Autenticación de consulta con parámetros de firma adicionales.

Otra opción sería explorar OAuth2 para la autenticación. Si es así, puede obtener una buena comprensión de Oauth2 en la Guía para principiantes de OAuth Parte III: Arquitectura de seguridad

Ya deberías estar usando SSL para establecer la autenticación.

Luego puede usar la misma ficha que obtuvo después de la autenticación como su hash secreto para encriptar / descifrar datos de ida y vuelta de esa conexión hasta que se vuelva inválida.

Si los sistemas están correctamente bloqueados (internos) puede omitir SSL para la transferencia de datos cifrados si necesita más velocidad (siempre que el token original se genere a través de SSL, y el sistema sepa a qué IP se le asignó el token / etc).

Esto podría ser demasiado elemental para su situación ya que no sé nada sobre Silverlight, pero ¿qué pasa con obtener un certificado SSL para su API web? Al igual que en la creación de su API, solo se puede acceder a través del protocolo https: // en lugar de http: //. Eso encriptará todo lo que se transmita entre el cliente y el servidor.

Recuerde que el descanso no es un protocolo. Puedes pensar que tu API es como una página web (pero sin interfaz humana). La configuración https es la misma.

Por lo que yo entiendo, tienes un código existente en su lugar. Para hacerlo más fácil, le mostraré un ejemplo simple y cómo funcionan las cosas con el siguiente código. Siéntase libre de usar solo las partes que necesita (que deberían ser bastante sencillas).

La forma en que creó su aplicación actualmente funciona bien con las sesiones del lado del servidor.

Bajo el código a continuación, incluiré más explicaciones y enlaces a los recursos, que te ayudarán a comprender mejor el código, probar y depurar tu aplicación.

$Web_Service_URL = 'https://website.tld/webservice.lang?wsdl'; $debug = false; $proto = 'https'; // eg str 'https' $agent = 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0'; $download = false; // just to make a call and fetch nothing set to false //$download = '/location/my_file.html'; to fetch content and save to file set the file location // Init the cURL session $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $Web_Service_URL); /** * * Start Fix SSLv3/TLS connectivity problems * * CURLOPT_SSL_VERIFYHOST and CURLOPT_SSL_VERIFYPEER prevent MITM attacks * WARNING: Disabling this would prevent curl from detecting Man-in-the-middle (MITM) attack * */ /** * @param CURLOPT_SSL_VERIFYPEER * * FALSE to stop CURL from verifying the peer's certificate. * Alternate certificates to verify against can be specified with the CURLOPT_CAINFO option or a certificate directory can be specified with the CURLOPT_CAPATH option. * CURLOPT_SSL_VERIFYHOST may also need to be TRUE or FALSE if CURLOPT_SSL_VERIFYPEER is disabled (it defaults to 2). * Setting CURLOPT_SSL_VERIFYHOST to 2 (This is the default value) will garantee that the certificate being presented to you have a 'common name' matching the URN you are using to access the remote resource. * This is a healthy check but it doesn't guarantee your program is not being decieved. * */ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); /** * @param CURLOPT_VERBOSE * Set the on/off parameter to 1 to make the library display a lot of verbose information about its operations on this handle. * Very useful for libcurl and/or protocol debugging and understanding. The verbose information will be sent to stderr, * or the stream set with CURLOPT_STDERR. * You hardly ever want this set in production use, you will almost always want this when you debug/report problems. */ curl_setopt($ch, CURLOPT_VERBOSE, $debug); /** * * @param CURLOPT_SSL_VERIFYHOST * * Check the existence of a common name in the SSL peer certificate. * Check the existence of a common name and also verify that it matches the hostname provided. * * @value 1 to check the existence of a common name in the SSL peer certificate. * @value 2 to check the existence of a common name and also verify that it matches the hostname provided. * In production environments the value of this option should be kept at 2 (default value). * Support for value 1 removed in cURL 7.28.1 */ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); /** * * Force use of TLS * */ if($proto == 'https') { /** * * Let's explain the magic of comparing your TLS certificate to the verified CA Authorities and how does that affect MITM attacks * * Man in the middle (MITM) * Your program could be misleaded into talking to another server instead. This can be achieved through several mechanisms, like dns or arp poisoning. * The intruder can also self-sign a certificate with the same 'comon name' your program is expecting. * The communication would still be encrypted but you would be giving away your secrets to an impostor. * This kind of attack is called 'man-in-the-middle' * Defeating the 'man-in-the-middle' * We need to to verify the certificate being presented to us is good for real. We do this by comparing it against a certificate we reasonable* trust. * If the remote resource is protected by a certificate issued by one of the main CA's like Verisign, GeoTrust et al, you can safely compare against Mozilla's CA certificate bundle, * which you can get from http://curl.haxx.se/docs/caextract.html * */ //TODO: If TLSv1_1 found insecure and/or unreliable change to TLSv1_1 or TLS1_2 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); // CURL_SSLVERSION_TLSv1_1; CURL_SSLVERSION_TLSv1_2 curl_setopt($ch, CURLOPT_HEADER, 0); // Don't return the header, just the html if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { $crt = substr(__FILE__, 0, strrpos( __FILE__, '\\'))."\crt\cacert.crt"; // WIN } else { $crt = str_replace('\\', '/', substr(__FILE__, 0, strrpos( __FILE__, '/')))."/crt/cacert.crt"; // *NIX } // The cert path is relative to this file curl_setopt($ch, CURLOPT_CAINFO, $crt); // Set the location of the CA-bundle /** * Fix Error: 35 - Unknown SSL protocol error in connections * * Improve maximum forward secrecy */ // Please keep in mind that this list has been checked against the SSL Labs' WEAK ciphers list in 2014. $arrayCiphers = array( 'DHE-RSA-AES256-SHA', 'DHE-DSS-AES256-SHA', 'AES256-SHA', 'ADH-AES256-SHA', 'KRB5-DES-CBC3-SHA', 'EDH-RSA-DES-CBC3-SHA', 'EDH-DSS-DES-CBC3-SHA', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA', 'ADH-AES128-SHA', 'AES128-SHA', 'KRB5-DES-CBC-SHA', 'EDH-RSA-DES-CBC-SHA', 'EDH-DSS-DES-CBC-SHA:DES-CBC-SHA', 'EXP-KRB5-DES-CBC-SHA', 'EXP-EDH-RSA-DES-CBC-SHA', 'EXP-EDH-DSS-DES-CBC-SHA', 'EXP-DES-CBC-SHA' ); curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, implode(':', $arrayCiphers)); } curl_setopt($ch, CURLOPT_TIMEOUT, 60); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); if($debug == true) {curl_setopt($ch, CURLOPT_HEADER, 1);} // Get HTTP Headers Code curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); // ini_set('user_agent', 'NameOfAgent (http://www.example.net)'); curl_setopt($ch, CURLOPT_USERAGENT, $agent); /** * DEBUG cURL Call * Don't forget to uncomment the CURLOPT_HEADER' */ // Get HTTP Headers Code // Show Http Header if($debug == true) { echo "
"; echo curl_getinfo($ch, CURLINFO_HTTP_CODE); } // TODO:Check if any cURL connection error occurred // see http://php.net/manual/en/function.curl-errno.php /** if(curl_errno($ch)) { echo 'Curl error: ' . curl_error($ch); } */ // Send the request and check the response if (($result = curl_exec($ch)) === FALSE) { die('cURL error: '.curl_error($ch)."
"); } else { //echo "Success!
"; } /** * @function cURL_GetInfo * other debug info, if needed * * The var_dump output: * array(26) { * ["url"]=> string(61) "https://www.example.com" * ["content_type"]=> string(24) "text/html; charset=UTF-8" * ["http_code"]=> int(200) * ["header_size"]=> int(2462) * ["request_size"]=> int(493) * ["filetime"]=> int(-1) * ["ssl_verify_result"]=> int(0) * ["redirect_count"]=> int(2) * ["total_time"]=> float(0.286363) * ["namelookup_time"]=> float(7.1E-5) * ["connect_time"]=> float(0.011754) * ["pretransfer_time"]=> float(0.082954) * ["size_upload"]=> float(0) * ["size_download"]=> float(119772) * ["speed_download"]=> float(418252) * ["speed_upload"]=> float(0) * ["download_content_length"]=> float(262) * ["upload_content_length"]=> float(0) * ["starttransfer_time"]=> float(0.156201) * ["redirect_time"]=> float(0.076769) * ["certinfo"]=> array(0) { } * ["primary_ip"]=> string(14) "xxx.xxx.xxx.xxx." * ["primary_port"]=> int(443) * ["local_ip"]=> string(12) "192.168.0.15" * ["local_port"]=> int(54606) * ["redirect_url"]=> string(0) "" * } */ $info = curl_getinfo($ch); $arrCodes = array( "client_error" => array("400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410", "411", "412", "413", "414", "415", "416", "417"), "server_error" => array("500", "502", "503", "504", "505") ); // Return the error code, if any and exit if(in_multi_array($info['http_code'], $arrCodes)) { file_put_contents("logs/dberror.log", "Date: " . date('M j Y - G:i:s') . " --- Error: " . $info['http_code'].' URL: '.$info['url'].PHP_EOL, FILE_APPEND); return $info['http_code']; exit; } curl_close($ch); // If download is defined download to the specified file if($download!= false) { $f = fopen($download, "w"); fwrite($f, $result); fclose($f); echo 'Web content downloaded to a file'; } echo $result;

Como puede ver en el código, puede definir múltiples cifrados seguros, pero solo un parámetro de versión SSL. No usaría nada antes que TLS 1.1. Cualquier versión anterior de SSL es vulnerable al ataque. Comience con el TLS 1.2 más seguro y pruebe, si su aplicación está funcionando (por lo general, no debería tener ningún problema. Si experimenta algún problema de conectividad, pruebe TLS 1.1. La versión 1.1 de TLS también es vulnerable, la única segura (por ahora, hasta que descubran cierta vulnerabilidad) es TLS 1.2.

Si la seguridad es la principal prioridad, vaya con la versión TLS disponible más alta (TLS1.2). La compatibilidad del cliente no es su problema cuando existe una responsabilidad de seguridad del proveedor del servicio.

Algunos otros cURL parámetros para mirar:

  • CURLOPT_SSL_VERIFYHOST
  • CURLOPT_SSL_VERIFYPEER
  • CURLOPT_CAINFO (cURL proporciona cacrt.crt en sus CA CERT de sitio web)
  • CURLOPT_SSL_CIPHER_LIST

Las cifras se compararon con la sólida lista Qualys SSL Labs (2014) y se eliminaron las cifras débiles. Siéntase libre de agregar / eliminar cualquier cifra.

  1. Antes de tomar una decisión, eche un vistazo a los proyectos de Qualys SSL Labs sobre seguridad.
  2. Eche un vistazo a este artículo de SSL Labs sobre el secretismo perfecto y las mejores prácticas.
  3. Pon a prueba tu cliente (navegador web) en busca de vulnerabilidades con la herramienta web de SSL Labs . Esto le dará una idea de qué mirar y qué mejorar y asegurar en su servidor y aplicación.
  4. Pon a prueba tu sitio web / servicio web con la herramienta SSL Labs SSL de Qualys.

Vulnerabilidades y ataques: Longjam, FREAK, POODLE, ¡lo que sea! ¿Quién sabe qué otros ataques o vulnerabilidades no se han descubierto? ¡Sí! Todos afectan su elección de conexión SSL / TLS.

Las opciones posibles de CURLOPT_SSLVERSION se pueden encontrar en la página oficial de cURL: http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html

Aquí también hay una buena guía OWASP para crear una capa segura alrededor de su aplicación.

Los laboratorios OWASP y Qualys SSL Labs son excelentes recursos para comenzar. Incluso investigaría sobre cURL y OpenSSL para familiarizarme con las debilidades, las posibles opciones de seguridad y las mejores prácticas.

Hay puntos de seguridad, que no menciono y faltan, pero no podemos cubrir todo.

Si tiene alguna pregunta, responderé si puedo.