我有一个用php编写的RESTful web服务,使用JSON进行通信。有些传输的数据非常敏感(密码),我正在寻找一种方法来实现服务的合理安全级别。客户端是silverlight 4应用程序
我一直在寻找关于如何实现SSL/TLS(我假设客户端证书认证属于该类别?)和消息级安全的明确信息,但我找不到关于在php+json web服务中实际实现这些安全措施的好例子。我将非常感谢任何信息和实际的例子。我知道这些原则,我只是对php不是很有经验。目前,我唯一的安全措施是一个非常基本的身份验证令牌系统,它在成功登录后创建一个服务器端会话,并为用户提供一个身份验证令牌,用于任何进一步的通信(直到会话过期或用户从不同的IP连接)。我真的希望至少保护敏感的流量,比如密码。
最后,在实现TLS和可能的消息层安全性之后,我必须注意的安全问题是什么,如漏洞和利用?
提前谢谢你。
假设您已经使用SSL/TLS正确配置了HTTPS,您主要关心的是如何为您的RESTful服务实现身份验证。由于HTTPS将使用SSL/TLS加密客户端和服务器之间的通信,因此加密不是您应该担心的事情。如果您需要了解如何正确配置SSL/TLS,请阅读理解SSL/TLS
保护REST式服务的最佳实践已经在RESTful身份验证和保护REST API/web服务的最佳实践中讨论过。
总结一下,讨论了3个选项
- 基于HTTPS的HTTP基本授权
- cookie和会话管理
- 查询带有附加签名参数的认证。
另一个选择是探索OAuth2进行身份验证。如果这样,您可以在OAuth入门指南第三部分:安全体系结构
中对Oauth2有一个很好的理解,您应该已经使用SSL来建立身份验证。
然后你可以使用认证后获得的令牌作为你的秘密哈希值来为该连接来回加密/解密数据,直到它失效。
如果系统被正确锁定(内部),如果您需要更快的速度,您可以跳过SSL进行加密数据传输(只要原始令牌是通过SSL生成的,并且系统知道令牌分配给/等的IP)。
这对于您的情况来说可能太基本了,因为我对Silverlight一无所知,但是如何为您的Web API获取SSL证书呢?在制作API时,它只能通过https://协议而不是http://.访问这将加密客户端和服务器之间传输的任何内容。
据我所知,您有一个现成的代码。为了方便起见,我将向您展示一个简单的示例,以及如何使用下面的代码。请随意只使用您需要的部分(这应该是相当直接的)。
你目前创建应用程序的方式与服务器端会话工作得很好。
在下面的代码中,我将包含更多的解释和资源链接,这将帮助您更好地理解代码,测试和调试您的应用程序。
$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。(CA CERTs)
- CURLOPT_SSL_CIPHER_LIST
密码根据强Qualys SSL实验室列表(2014)进行检查,弱密码被删除。请随意添加/删除任何密码。
- 在你做决定之前,看看Qualys SSL实验室关于安全的项目。
- 看一下SSL实验室关于完美前向保密和最佳实践的文章。
- 使用SSL实验室的web工具测试您的客户端(web浏览器)的任何漏洞。这将给你一个想法,看看什么,什么改进和保护你的服务器和应用程序。
- 使用Qualys的SSL实验室SSL工具测试您的网站/web服务。
漏洞和攻击:Longjam, FREAK, POODLE,你能想到的!谁知道还有哪些未被发现的攻击或漏洞?是的!它们都会影响您选择SSL/TLS连接。
可能的CURLOPT_SSLVERSION选项可以在官方cURL页面找到:http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html
这里还有一个很好的OWASP指南,用于在应用程序周围创建安全层。
OWASP和Qualys SSL Labs是很好的入门资源。我甚至会对cURL和OpenSSL做一些研究,以熟悉它们的弱点、可能的安全选项和最佳实践。
有一些安全要点,我没有提到,也没有提到,但我们不能涵盖所有内容。
如果你有任何问题,如果可以的话,我将随时为你解答。