如何使用 SSL/TLS 和/或消息级安全性保护 RESTful php Web 服务

Posted

技术标签:

【中文标题】如何使用 SSL/TLS 和/或消息级安全性保护 RESTful php Web 服务【英文标题】:How to secure a RESTful php web service with SSL/TLS and/or Message level security 【发布时间】:2011-09-29 00:07:09 【问题描述】:

我有一个用 php 编写的 RESTful Web 服务,它使用 JSON 进行通信。传输的一些数据非常敏感(密码),我正在寻找一种方法来为服务实现合理的安全级别。客户端是一个 silverlight 4 应用程序。

我一直在寻找有关如何实施 SSL/TLS(我假设客户端证书身份验证属于该类别?)和消息级别安全性的明确信息,但我找不到有关在一个 php+json 网络服务。我将非常感谢任何信息和实际示例。我知道这些原则,我只是对 php 不是很有经验。 目前,我采用的唯一安全措施是一个非常基本的身份验证令牌系统,它在成功登录后创建一个服务器端会话并为用户提供身份验证令牌以进行任何进一步的通信(直到会话到期或用户从不同的IP)。我真的很想至少保护密码等敏感流量。

最后,在实施 TLS 和消息层安全之后,我必须注意哪些安全问题,例如漏洞和漏洞利用?

提前谢谢你。

【问题讨论】:

【参考方案1】:

假设您使用 SSL/TLS 正确配置了 HTTPS,您主要关心的是如何为您的 RESTful 服务实施身份验证。由于 HTTPS 将使用 SSL/TLS 来加密客户端和服务器之间的通信,因此您不必担心加密。如果您需要了解如何正确配置 SSL/TLS,请阅读 Understanding SSL/TLS

保护 RESTful 服务的最佳实践已在 RESTful Authentication 和 Best Practices for securing a REST API / web service 中讨论。

总结它讨论了 3 个选项

基于 HTTPS 的 HTTP 基本身份验证 Cookie 和会话管理 使用附加签名参数查询身份验证。

另一种选择是探索 OAuth2 进行身份验证。如果是这样,您可以在Beginner’s Guide to OAuth Part III : Security Architecture

中对 Oauth2 有一个很好的了解

【讨论】:

【参考方案2】:

您应该已经在使用 SSL 来建立身份验证。

然后,您可以使用身份验证后获得的相同令牌作为您的秘密哈希来来回加密/解密该连接的数据,直到它变得无效。

如果系统被正确锁定(内部),如果您需要更快的速度,您可以跳过 SSL 进行加密数据传输(只要原始令牌是通过 SSL 生成的,并且系统知道令牌分配给/等的 IP) .

【讨论】:

感谢您提供有关使用令牌加密/解密数据的提示。您能否向我指出一篇关于如何使用 SSL 建立身份验证以及如何使用令牌加密/解密数据的文章/教程?我已经有了只接受 SSL 连接的服务,但我不知道如何实现实际的证书验证和使用。 我手头没有任何东西,理想情况下,在处理关键任务数据时,您需要通过安全/加密的 *** 链接发送它。如果您除了与安全服务器 (https) 通信之外还缺少该选项,您可以为每个客户端分配两种方式哈希,用于加密/解密身份验证数据(但永远不要通过与令牌不同的链接发送)只是为了保持一点更安全。【参考方案3】:

这对于您的情况来说可能太简单了,因为我对 Silverlight 一无所知,但是为您的 Web API 获取 SSL 证书怎么样?就像在制作 API 时一样,它通过 https:// 协议而不是 http:// 访问。这将加密客户端和服务器之间传输的任何内容。

【讨论】:

嗨,这绝对不是太基本,因为使用 SSL 是我想要实现的方法之一。问题是我不知道也找不到关于如何实现证书机制的好信息,包括 php Web 服务中的服务器端和客户端。 我明白了。我不知道该告诉你什么,因为我以前不必自己配置 SSL。但我知道这取决于您使用的 HTTP 服务器(例如 Apache、nginx 等),并且与 PHP 没有直接关联。在网络上搜索您的特定 HTTP 服务器的“ssl 教程”?【参考方案4】:

据我了解,您有一个现有的代码。 为了方便起见,我将向您展示一个简单的示例以及如何使用下面的代码。 随意只使用您需要的部分(应该非常简单)。

您当前创建应用的方式在服务器端会话中运行良好。

在下面的代码下,我将包含更多解释和资源链接,这将有助于您更好地理解代码、测试和调试您的应用程序。

$Web_Service_URL = 'https://website.tld/webservice.lang?wsdl';
$debug = false;
$proto = 'https'; // e.g. 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 "<pre>";
    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)."<br />");
 else 
    //echo "Success!<br />";


/** 
 * @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;

正如您在代码中看到的,您可以定义多个安全密码,但只能定义一个 SSL 版本参数。我不会使用早于 TLS 1.1 的任何东西。任何早期的 SSL 版本都容易受到攻击。从最安全的 TLS 1.2 开始并测试您的应用程序是否正常工作(通常,您应该没有任何问题。如果您遇到任何连接问题,请尝试 TLS 1.1。 TLS 1.1 版也容易受到攻击,唯一安全的(目前,直到他们发现一些漏洞)是 TLS 1.2。

如果安全性是重中之重,请使用最高可用的 TLS 版本 (TLS1.2)。当存在服务提供商安全责任时,客户端兼容性不是您的问题。

其他一些 cURL 参数要查看:

CURLOPT_SSL_VERIFYHOST CURLOPT_SSL_VERIFYPEER CURLOPT_CAINFO(cURL 在他们的网站上提供了 cacrt.crt CA CERTs) CURLOPT_SSL_CIPHER_LIST

根据 Qualys SSL Labs 强列表 (2014) 检查了密码,并删除了弱密码。随意添加/删除任何密码。

    在您做出决定之前,请查看 Qualys SSL 实验室的 projects 关于安全性的信息。 查看this SSL Labs' article,了解完美的前向保密和最佳实践。 使用SSL Labs' web tool 测试您的客户端(Web 浏览器)是否存在任何漏洞。这将使您了解在您的服务器和应用上应该查看哪些内容以及需要改进和保护哪些内容。 使用 Qualys 的 SSL 实验室 SSL tool 测试您的网站/网络服务。

漏洞和攻击:Longjam、FREAK、POODLE,应有尽有!谁知道还有哪些其他攻击或漏洞未被发现?是的!它们都会影响您对 SSL/TLS 连接的选择。

可能的 CURLOPT_SSLVERSION 选项可以在官方 cURL 页面找到:http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html

您的应用周围还有一个不错的OWASP guide for creating a secure layer。

OWASP 和 Qualys SSL Labs 是很好的入门资源。我什至会对 cURL 和 OpenSSL 进行一些研究,以熟悉弱点、可能的安全选项和最佳实践。

有一些安全点,我没有提到,也没有,但我们无法涵盖所有​​内容。

如果您有任何问题,如果可以的话,我会在附近为您解答。

【讨论】:

以上是关于如何使用 SSL/TLS 和/或消息级安全性保护 RESTful php Web 服务的主要内容,如果未能解决你的问题,请参考以下文章

HTTPS 安全最佳实践之SSL/TLS部署

什么是ssl,tls?两者有什么区别?

SFTP 和FTPS的区别

HTTPS 和 SSL 的区别

无法创建 SSL/TLS 安全通道 - GetRequestStream()

通配符 SSL/TLS 证书