How to properly secure remote API calls over SSL from PHP code

Lets make something clear from the very start: JUST BECAUSE THERE IS https:// IN THE URL OF THE REMOTE SERVICE IT DOES NOT MEAN THE CONNECTION IS SECURE!

I am sorry for the tone of this post but i am enraged by how popular this issue is online. If you ask why i suggest a little experiment.

Steps to follow

  • Change your host file settings to point something like www.somedomain.com to your development server
  • Create a self signed certificate for this domain and setup a https virtual host to serve it on local IP or alias
  • Put a test file on that URL
  • Create a PHP script that accesses that file on that fake service over https. Use Zend HTTP client or CURL or whatever like file_get_contents or what you prefer.
  • Run script and check the result.

Well so what has happened? Did you get a big fat error like below?

Error in cURL request: SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

No you did not?

Result of the experiment and interpretation

It is almost certain that your script has downloaded the test file and it all worked. That is a huge problem. What it means is that the way you accessed the file did not enforce SSL certificate verification. What does it mean? Well it means that anyone who can foll your DNS client or mess with IP routing, Ethernet, has access to your local network, controls some of the routers on the path etc can hijack your communication without you knowing it. All the attacker has to do is find a way to make your web server connect to his fake service instead of the real one. There are many many attack types that can let hacker do that and it is difficult to prevent all of them at once.

So if you are sending credit cards data, passwords, personal data, emails or whatever you better watch your back. Attacker can easily create a proxy to intercept all the transferred data and save the details without you ever finding out.

So why am i so angry and write with all-caps all the time?

Because there are dozens of libraries and code samples online that are wrong! and they are not written by some poor students, they are on corporate websites of biggest payment services. How could this ever happen? how could they be so reckless to show people the WRONG way to integrate? I really do not know.

This is how you should not be doing it:

	// Set the curl parameters.
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $API_Endpoint);
	curl_setopt($ch, CURLOPT_VERBOSE, 1);

	// Turn off the server and peer verification (TrustManager Concept).
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_POST, 1);

THERE IS NO SUCH THING AS TrustManager Concept THAT CAN PROTECT YOU FROM MAN IN THE MIDDLE ATTACKS! THIS SAMPLE IS SIMPLY WRONG!

All the samples below are wrong (i just spent like 15 mins googling there are tons of examples like this):
http://www.eztexting.com/developers/sms-api-code-examples-php.html
https://cms.paypal.com/cms_content/US/en_US/files/developer/nvp_DoCapture_php.txt
https://cms.paypal.com/cms_content/US/en_US/files/developer/IPN_PHP_41.txt
http://www.eway.com.au/Developer/payment-code/eway-sample-api-code.aspx

This sample can potentially be dangerous as well as by default path to certificates is empty - meaning we have same issue unless we dig deep enough to set it to something meaningful:
http://code.google.com/p/google-checkout-php-sample-code/source/browse/trunk/library/googlerequest.php

How to protect yourself from man in the middle attacks in PHP?

All you have to do is make sure you verify the SSL certificate when you connect to the remote service. Make a test, point host file to your fake service and you should get SSL certificate verification error. If you do not get the error your client code is not secure. There are samples that do it right so have a look for example here Smashing Magazine integration sample.

60	         CURLOPT_SSL_VERIFYPEER => true,
61	         CURLOPT_SSL_VERIFYHOST => 2,
62	         CURLOPT_CAINFO => dirname(__FILE__) . '/cacert.pem', //CA cert file

The article does not explain exactly what are the risks but it shows how to do it. The cert.pem is a bundle of SSL certificate authorities that are trusted and can be used to safely verify authenticity of a website's certificate. It does not contain certificates of all websites in the world just certificates of authorities like verisign who sign certificates sold to clients.

You can generate the bundle by downloading
latest curl library and running the certificate bundle generator (lib/mk-ca-bundle.pl). You can find more details and a pre-generated CA bundle here on the CURL certificate bundle page.

This should last for years but it is a good idea to put an update automation to get latest bundles from time to time.

Spread the word!

Art

Comments

Yeah I think it is easiest to

Yeah I think it is easiest to have a flag based on environment and run insecure curl in dev so that it would not try to verify certificates.

Alternatively, maybe you could create a selfsigned CA cert and insert it into the bundle of trusted certs on dev. Then use this "trusted" CA cert to generate the certificate of your web server. Feels like a lot of hassle for little gain ;) I would just use 'insecure' curl in dev.

2014-07-11 04:45
admin

I realized this is an old

I realized this is an old post, but how would one make this work with a self signed cert (on a local development server)? Should I only do the insecure curl code when in debug mode (on dev server)?

2014-06-16 21:51
Anonymous

Thank you for this

Thank you for this disambiguation! I've been looking at sample PayPal code from their SDK, staring at the VERIFYPEER and VERIFYHOST options set to FALSE, and saying to myself, "That just seems so wrong".

2012-02-27 14:37
Oliver

Well it will not happen by

Well it will not happen by default as far as i know. If you want to be sure make a simple test as described above to check if your code is actually verifying invalid SSL certificates. I think you can make it work in multiple ways for example you can change context options as described here:

http://www.php.net/manual/en/context.ssl.php

2011-09-27 05:31
admin

Is there the same risk if a

Is there the same risk if a connection is made using fsockopen to an ssl/443 port? If there is, then is there any way of making the open call verify the SSL cert?

2011-09-20 06:01
Luke

Post new comment

Image CAPTCHA

About the author

Artur Ejsmont

Hi, my name is Artur Ejsmont,
welcome to my blog.

I am a passionate software engineer living in Sydney and working for Yahoo! Drop me a line or leave a comment.

Follow my RSS