This post tries to prove that vulnerabilities can in fact be very subtle and that even people who master their toolkit and libraries can easily fall for them. It is based upon a vulnerability in ownCloud server fixed in June 2015.

cURL is probably known to most readers of this blog. If not: It is a library and command-line tool that can be used to send HTTP requests to other servers. It has an official PHP wrapper maintained by the PHP team.

Everybody who has used cURL before will probably agree: cURL is a mighty and complex utility, the PHP wrapper is no exception. Stating it is used to send HTTP requests is a bit of an understatement, it supports as well DICT, FILE, FTP, GOPHER, IMAP, LDAP, POP3, RTMP, RTSP, SCP, SFTP, SMB, SMTP, TELNET and TFTP.

As with any mighty tool, there are a lot of possibilities to shoot yourself in your own foot. Let’s take as example the following script. It will take the GET parameter username and send it as POST parameter to http://example.org:

<?php
$ch = curl_init();

$data = ['name' => $_GET['username']];

curl_setopt($ch, CURLOPT_URL, 'http://example.org/');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

curl_exec($ch);

Do you happen to see anything dangerous with this? Most developers I showed this snippet to certainly did not. But let’s just take a closer look at the documentation:

CURLOPT_POSTFIELDS:

The full data to post in a HTTP “POST” operation. To post a file, prepend a filename with @ and use the full path. The filetype can be explicitly specified by following the filename with the type in the format ;type=mimetype.

This parameter can either be passed as a urlencoded string like para1=val1&para2=val2&... or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data.

As of PHP 5.2.0, value must be an array if files are passed to this option with the @ prefix. As of PHP 5.5.0, the @ prefix is deprecated and files can be sent using CURLFile. The @ prefix can be disabled for safe passing of values beginning with @ by setting the CURLOPT_SAFE_UPLOAD option to TRUE.

Yikes! – If an array that is being passed to CURLOPT_POSTFIELDS contains a @ in one of the values, cURL will upload the specified file to the remote host. That’s certainly quite an unexpected behavior. So the above script will actually upload any arbitrary file to the remote host if an attacker passes something like @/etc/passwd to $_GET['name'].

Luckily this “feature” has been deprecated as of PHP 5.5.0 and disabled by default with PHP 5.6.0. But the manual opt-in of PHP 5.5.0 using CURLOPT_SAFE_UPLOAD is still quite a risk factor.

Directly using a mighty utility such as cURL is indeed handy in some specific situations. In most situations using an abstraction layer such as Guzzle is a better and even more secure alternative. And now it might be time to check your own code :-)