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

9
我有一个使用JSON进行通信的PHP RESTful Web服务,其中一些传输的数据非常敏感(例如密码),因此我正在寻找一种方法来为服务实现合理的安全级别。客户端是Silverlight 4应用程序。
我一直在寻找如何实现SSL/TLS(我假设客户端证书身份验证属于该类别?)和消息级别安全性的清晰信息,但我无法找到关于在php+json Web服务中实际实现这些安全措施的好例子。如果您有任何信息和实际示例,我将不胜感激。我了解原则,但在php方面并不是很有经验。目前,我所采取的唯一安全措施是一个非常基本的身份验证令牌系统,成功登录后会创建服务器端会话,并为进一步的通信提供用户认证令牌(直到会话过期或用户从不同的IP连接)。我真的想至少保护敏感的流量,例如密码。
最后,在实施TLS和消息层安全性之后,我需要注意哪些安全问题,例如漏洞和攻击?
谢谢您的帮助。
4个回答

5
假设您已经使用SSL/TLS正确配置了HTTPS,那么您主要关心的是如何为RESTful服务实现身份验证。由于HTTPS将使用SSL/TLS加密客户端和服务器之间的通信,因此加密不是您需要担心的问题。如果您需要了解如何正确配置SSL/TLS,请阅读《理解SSL/TLS》
保护RESTful服务的最佳实践已经在RESTful身份验证保护REST API / Web服务的最佳实践中讨论过。
总结一下,它讨论了3个选项:
  • 通过HTTPS进行HTTP基本身份验证
  • Cookie和会话管理
  • 使用附加签名参数进行查询身份验证。
另一个选择是探索OAuth2进行身份验证。如果是这样,您可以在OAuth入门指南第三部分:安全架构中了解OAuth2。

2

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

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

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


感谢您提供有关使用令牌进行数据加密/解密的提示。您能否指导我如何使用SSL建立身份验证并使用令牌加密/解密数据的文章/教程?我已经让服务只接受SSL连接,但我不知道如何实现实际的证书验证和使用。 - Kiril
我手头上没有,理想情况下,在处理关键数据时,需要通过安全/加密的VPN连接发送。如果您缺乏此选项,除了与安全服务器(https)通信外,您还可以为每个客户端分配双向哈希,用于加密/解密身份验证数据(但绝不能像令牌那样通过链接发送),只是为了使其更加安全。 - Aleksey Korzun

0

据我所知,您已经有了现成的代码。

为了让您更容易理解,我将展示一个简单的例子,并演示下面的代码如何运作。

请随意使用您需要的部分(应该非常直观)。

您目前创建应用程序的方式与服务器端会话完全相符。

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

$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;

As you can see in the code, you can define multiple secure ciphers, but only one SSL version parameter. I wouldn't use anything earlier than TLS 1.1. Any earlier SSL version is vulnerable to attack. Start with the most secure TLS 1.2 and test, if your app is working (usually, you shouldn't have any problems. If you experience any connectivity issues try TLS 1.1. TLS version 1.1 is also vulnerable, the only secure (for now, until they discover some vulnerability) is TLS 1.2.

If security is top priority, go with the highest available TLS version (TLS1.2). Client compatibility is not your problem when there is service provider security liability.

Some others cURL parameters to look at:

The ciphers were checked against the strong Qualys SSL Labs list (2014) and weak ciphers were removed. Feel free to add/remove any ciphers.

  1. Before you make a decision take a look at the Qualys SSL Labs' projects about security.
  2. Take a look at this SSL Labs' article about perfect forward secrecy and best practices.
  3. Test your client (web browser) for any vulnerabilities with SSL Labs' web tool. This will give you an idea what to look at and what to improve and secure on your server and app.
  4. Test your website/web service with Qualys' SSL Labs SSL tool.

Vulnerabilities and attacks: Longjam, FREAK, POODLE, you name it! Who knows what other attacks or vulnerabilities are undiscovered? Yes! They all affect your choice of SSL/TLS connection.

Possible CURLOPT_SSLVERSION options can be found at the official cURL page: http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html

Here's also a nice OWASP guide for creating a secure layer around your app.

OWASP and Qualys SSL Labs are great resources to start with. I would even do some research on cURL and OpenSSL to get familiar with weaknesses, possible security options and best practices.

There are security points, which I am not mentioning and are missing, but we can't cover everything.

If you have any questions, I will be around to answer, if I can.


0
这可能对你的情况来说太基础了,因为我不知道关于Silverlight的任何信息,但是你考虑为你的Web API获取一个SSL证书怎么样?也就是使你的API只能通过https://协议访问,而不是http://。这将加密客户端和服务器之间传输的任何内容。

嗨,这绝对不是太基础的问题,因为使用SSL是我想要实现的方法之一。问题在于,我不知道如何在php web服务端和客户端上正确地实现证书机制,并且找不到好的信息。 - Kiril
我明白了。由于我以前没有配置过SSL,所以不太确定该告诉您什么。但是我知道这取决于您使用的HTTP服务器(例如Apache、Nginx等),而不是直接与PHP相关联的。请在网络上搜索“ssl教程”以获取您特定的HTTP服务器的相关信息。 - curtisdf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接