How to secure a RESTful php web service with SSL/T

2019-02-09 05:01发布

I have a RESTful web service written in php that uses JSON for communication. Some of the data transmitted is really sensitive (passwords) and I am looking for a way to achieve a reasonable security level for the service. The client is a silverlight 4 application.

I have been searching for clear information on how to implement SSL/TLS(I assume that client certificate authentication falls in that category?) and Message level security, but I cannot find good examples regarding the actual implementation of these security measures in a php+json web service. I would be very grateful for any information and practical examples. I am aware of the principles, I am just not very experienced with php. Currently the only security measure that I have in place is a very basic authentication token system, which upon successful login creates a server side session and supplies the user with an authentication token for any further communication(until the session expires or the user connects from a different IP). I really want to at least secure the sensitive traffic such as passwords.

Finally, what are the security issues that I have to look out for after implementing TLS and maybe message layer security, as in vulnerabilities and exploits?

Thank you in advance.

4条回答
来,给爷笑一个
2楼-- · 2019-02-09 05:19

As far as I understood, you have an existing code in place. To make it easier, I will show you a simple example and how things work with the code below. Feel free to use only the parts you need (which should be pretty straight forward).

The way you have currently created your app works just fine with the server side sessions.

Under the code below, I will include some more explanation and links to resources, which will help you better understand the code, test and debug your app.

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

查看更多
smile是对你的礼貌
3楼-- · 2019-02-09 05:28

This might be too elementary for your situation since I don't know anything about Silverlight, but what about getting an SSL certificate for your Web API? As in making your API so it's accessed only via the https:// protocol instead of http://. That will encrypt anything transmitted between client and server.

查看更多
走好不送
4楼-- · 2019-02-09 05:38

Assuming you have HTTPS properly configured using SSL/TLS your main concern is how to implement authentication for your RESTful service. Since HTTPS will use SSL/TLS to encrypt the communication between client and server encryption is not something you should worry about. If you need to understand how to properly configure SSL/TLS read Understanding SSL/TLS

Best practices for securing RESTful service is already discussed in RESTful Authentication and Best Practices for securing a REST API / web service.

To summarize it discusses 3 options

  • HTTP basic auth over HTTPS
  • Cookies and session management
  • Query Authentication with additional signature parameters.

Another option would be to explore OAuth2 for authentication. If so you can get a good understanding about Oauth2 in Beginner’s Guide to OAuth Part III : Security Architecture

查看更多
Lonely孤独者°
5楼-- · 2019-02-09 05:40

You should already be using SSL to get the authentication established.

Then you can use same token you got after authentication as your secret hash to encrypt/decrypt data back and forth for that connection until it becomes invalid.

If systems are properly locked down (internal) you can skip SSL for encrypted data transfer if you need more speed (as long as original token is generated over SSL, and system is aware what IP the token is assigned to/etc).

查看更多
登录 后发表回答