How to check if a connection to a REST API is secure?
1) I'm building an API and want to make sure that the connection is running SSL before the response is generated and returned to the client.. Username and password is passed in every request
2) And how can you make a fsockopen connection with SSL?
$this->url = 'api.domain.com';
$this->path = '/v1/json/account/user/pass/?id=123';
if($this->fp = fsockopen($this->url, $this->port, $errno, $errstr, $this->timeout)){
$write = "$this->method $this->path HTTP/1.1\r\n";
$write .= "Host: $this->url\r\n";
$write .= "Content-Type: $this->content_type\r\n";
$write .= "Connection: Close\r\n\r\n";
fwrite($this->fp, $write);
while($line = fgets($this->fp)){
if($line !== false) $this->response .= $line;
}
fclose($this->fp);
}
Just check $_SERVER['HTTPS'].
The PHP manual at http://php.net/manual/en/reserved.variables.server.php says:
'HTTPS'
Set to a non-empty value if the script was queried through the HTTPS protocol.
Note: Note that when using ISAPI with IIS, the value will be off if the request was not made > through the HTTPS protocol.
Of course you can. You can even force it to be. HTTP works on port 80 and HTTPS on 443. You can discard all traffic not comming from 443.
Related
I've coded a non-evil, non-spammy IRC bot in PHP, using fsockopen and related functions. It works. However, the problem is that I need to support proxies (preferably SOCKS5, but HTTP is also OK if that is somehow easier, which I doubt). This is not supported by fsockopen.
I've gone through all search results for "PHP fsockopen proxy" and related queries. I know of all the things that don't work, so please don't link to one of them.
The PHP manual page for fsockopen mentions the function stream_socket_client() as
similar but provides a richer set of options, including non-blocking connection and the ability to provide a stream context.
This sounded promising at first, supposedly allowing me to just replace the fsockopen call with stream_socket_client and specify a proxy, maybe via a "stream context"... but it doesn't. Or does it? I'm very confused by the manual.
Please note that it must be a PHP code solution; I cannot pay for "Proxifier" or use any other external software to "wrap around" this.
All the things I've tried seem to always result in me getting a bunch of empty output from the server, and then the socket is forcefully closed. Note that the proxy I'm trying with works when I use HexChat (a normal IRC client), with the same network, so it's not the proxies themselves that are at fault.
As far as I know there is no default option to set a SOCKS or HTTP proxy for fsockopen or stream_socket_client (we could create a context and set a proxy in HTTP options, but that doesn't apply to stream_socket_client). However we can establish a connection manually.
Connecting to HTTP proxies is quite simple:
The client connects to the proxy server and submits a CONNECT request.
The server responds 200 if the request is accepted.
The server then proxies all requests between the client and destination host.
<!- -!>
function connect_to_http_proxy($host, $port, $destination) {
$fp = fsockopen($host, $port, $errno, $errstr);
if ($errno == 0) {
$connect = "CONNECT $destination HTTP/1.1\r\n\r\n";
fwrite($fp, $connect);
$rsp = fread($fp, 1024);
if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
return $fp;
}
echo "Request denied, $rsp\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
This function returns a file pointer resource if the connection is successful, else FALSE. We can use that resource to communicate with the destination host.
$proxy = "138.204.48.233";
$port = 8080;
$destination = "api.ipify.org:80";
$fp = connect_to_http_proxy($proxy, $port, $destination);
if ($fp) {
fwrite($fp, "GET /?format=json HTTP/1.1\r\nHost: $destination\r\n\r\n");
echo fread($fp, 1024);
fclose($fp);
}
The communication protocol for SOCKS5 proxies is a little more complex:
The client connects to the proxy server and sends (at least) three bytes: The first byte is the SOCKS version, the second is the number of authentication methods, the next byte(s) is the authentication method(s).
The server responds with two bytes, the SOCKS version and the selected authentication method.
The client requests a connection to the destination host. The request contains the SOCKS version, followed by the command (CONNECT in this case), followed by a null byte. The fourth byte specifies the address type, and is followed by the address and port.
The server finally sends ten bytes (or seven or twenty-two, depending on the destination address type). The second byte contains the status and it should be zero, if the request is successful.
The server proxies all requests.
<!- -!>
More details: SOCKS Protocol Version 5.
function connect_to_socks5_proxy($host, $port, $destination) {
$fp = fsockopen($host, $port, $errno, $errstr);
if ($errno == 0) {
fwrite($fp, "\05\01\00");
$rsp = fread($fp, 2);
if ($rsp === "\05\00" ) {
list($host, $port) = explode(":", $destination);
$host = gethostbyname($host); //not required if $host is an IP
$req = "\05\01\00\01" . inet_pton($host) . pack("n", $port);
fwrite($fp, $req);
$rsp = fread($fp, 10);
if ($rsp[1] === "\00") {
return $fp;
}
echo "Request denied, status: " . ord($rsp[1]) . "\n";
return false;
}
echo "Request denied\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
This function works the same way as connect_to_http_proxy. Although both functions are tested, it would be best to use a library; the code is provided mostly for educational purposes.
SSL support and authentication.
We can't create an SSL connection with fsockopen using the ssl:// or tls:// protocol, because that would attempt to create an SSL connection with the proxy server, not the destination host. But it is possible to enable SSL with stream_socket_enable_crypto and create a secure communication channel with the destination, after the connenection with the proxy server has been established. This requires to disable peer verification, which can be done with stream_socket_client using a custom context. Note that disabling peer verification may be a security issue.
For HTTP proxies we can add authentication with the Proxy-Authenticate header. The value of this header is the authentication type, followed by the username and password, base64 encoded (Basic Authentication).
For SOCKS5 proxies the authentication process is - again - more complex. It seems we have to change the authentication code fron 0x00 (NO AUTHENTICATION REQUIRED) to 0x02 (USERNAME/PASSWORD authentication). It is not clear to me how to create a request with the authentication values, so I can not provide an example.
function connect_to_http_proxy($host, $port, $destination, $creds=null) {
$context = stream_context_create(
['ssl'=> ['verify_peer'=> false, 'verify_peer_name'=> false]]
);
$soc = stream_socket_client(
"tcp://$host:$port", $errno, $errstr, 20,
STREAM_CLIENT_CONNECT, $context
);
if ($errno == 0) {
$auth = $creds ? "Proxy-Authorization: Basic ".base64_encode($creds)."\r\n": "";
$connect = "CONNECT $destination HTTP/1.1\r\n$auth\r\n";
fwrite($soc, $connect);
$rsp = fread($soc, 1024);
if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
return $soc;
}
echo "Request denied, $rsp\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
$host = "proxy IP";
$port = "proxy port";
$destination = "chat.freenode.net:6697";
$credentials = "user:pass";
$soc = connect_to_http_proxy($host, $port, $destination, $credentials);
if ($soc) {
stream_socket_enable_crypto($soc, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
fwrite($soc,"USER test\nNICK test\n");
echo fread($soc, 1024);
fclose($soc);
}
sorry for the title but I'm not reaaly sure how to call this.
I am registered to a ssm service which enables sending automatic sms with a php script.
the script basically builds an xml string with all of the sms parameters (sendername, ..).
then it uses this to send it :
$sms_host = "api.inforu.co.il"; // Application server's URL;
$sms_port = 80; // Application server's PORT;
////.... generating query
$sms_path = "/SendMessageXml.ashx"; // Application server's PATH;
$fp = fsockopen($sms_host, $sms_port, $errno, $errstr, 30); // Opens a socket to the Application server
if (!$fp){ // Verifies that the socket has been opened and sending the message;
echo "$errstr ($errno)<br />\n";
echo "no error";
} else {
$out = "GET $sms_path?$query HTTP/1.1\r\n";
$out .= "Host: $sms_host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)){
echo fgets($fp, 128);
}
fclose($fp);
the query is fine if I paste this
$url = "http://api.inforu.co.il/SendMessageXml.ashx?" . $query;
directly in the browser, then the sms gets send.
so the problem is that I'm getting an error
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /SendMessageXml.ashx
You have to urlencode() your $query, if you paste it to browser, the browser will encode it for you, but when you are dealing with a socket, you have to do it yourself.
I have a simple whois script
if($conn = fsockopen ($whois_server, 43)) {
fputs($conn, $domain."\r\n");
while(!feof($conn)) {
$output .= fgets($conn, 128);
}
fclose($conn);
return $output;
}
$whois_server = whois.afilias.info; //whois server for info domains
but I want to run in through proxy. So I need to connect to proxy -> then connect to whois server -> and then make the request. Something like this?
$fp = fsockopen($ip,$port);
fputs($fp, "CONNECT $whois_server:43\r\n $domain\r\n");
But it doesn't work, I don't know if i'm making the second connection right.
Sending your request to the proxy should do what you like:
<?php
$proxy = "1.2.3.4"; // proxy
$port = 8080; // proxy port
$fp = fsockopen($proxy,$port); // connect to proxy
fputs($fp, "CONNECT $whois_server:43\r\n $domain\r\n");
$data="";
while (!feof($fp)) $data.=fgets($fp,1024);
fclose($fp);
var_dump($data);
?>
Rather than doing that I would recommend you using CURL in combination with:
curl_setopt($ch, CURLOPT_PROXY, 1.2.3.4);
I'm trying to set up the PayPal IPN on my web application, I copied from PayPal's documentation on an example PHP snippet which is found here.
However, when I'm testing with the PayPal's sandbox, sending an IPN with the simulator which is found here.
Now, when PayPal sends the IPN, I log the actions and data of the IPN, when trying to open an connection with fsockopen, it is NULL when I do var_export on it.
I don't understand why it's not going any further with the code when the fsockopen connection is NULL.
I'm using Codeigniter for my application, and this is the part of the code that fails:
if($this->uri->segment(3) == 'ipn')
{
$error_msg = '';
$error_msg .= " initiated ";
$req = 'cmd=_notify-validate';
$error_msg .= " \n\n req: " . var_export($req, true);
foreach($this->input->post() as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= "&" . $key . "=" . $value;
}
$error_msg .= " \n\n req: " . var_export($req, true);
$header = '';
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$error_msg .= " \n\n headers: " . var_export($header, true);
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
$error_msg .= " \n\n fp: " . var_export($fp, true);
I use $error_msg to log the data, this is an example what is logged:
initiated
req: 'cmd=_notify-validate'
req: 'cmd=_notify-validate&test_ipn=1&payment_type=echeck&payment_date=17%3A30%3A40+Jan+03%2C+2012+PST&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123%2C+any+street&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name=something&item_number=DX4WYSur44CQICgO2lC%2FB10NmdaiPNH3xPZXQNAlfrEqpse0xnime22zaNXDFgbRrOL4Xsz4emkhqFw4JhOSHzCtaHt9%2B0p9p8xW6R71PVbFXNyEVjkPeHNdQm32PJg&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&txn_type=web_accept&txn_id=4014130¬ify_version=2.1&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=An5ns1Kso7MWUdW4ErQKJJJ4qi4-AN8d2a.xggmx9Dn4AgHpvPHJHTAp'
headers: 'POST /cgi-bin/webscr HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 969
'
fp: NULL
As you can see $fp is returning NULL on the last line of the logged data. Is there any idea why this is happening?
I can confirm I have OpenSSL enabled and installed on my server:
EDIT: Just tested fsockopen on port 80 to google.com, I still get NULL with no error number or message. So this problems occurs to every URL.
EDIT #2: Tested on my server by doing this:
fsockopen('ssl://www.paypal.com/cgi-bin/webscr', 443, $errno, $errstr, 30)
A PHP Error was encountered
Severity: Warning
Message: fsockopen(): unable to connect to
ssl://www.paypal.com/cgi-bin/webscr:443 (php_network_getaddresses:
getaddrinfo failed: nodename nor servname provided, or not known)
If anyone else is having the same problem, try using HTTPS instead of SSL
$fp = fsockopen ('https://www.paypal.com', 443, $errno, $errstr, 30);
And if your testing on Paypals Sandbox use:
$fp = fsockopen ('https://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
Be careful of $config['csrf_protection'] = TRUE; this will block all external POSTS as they will not come with a CSRF token, I've had this with my paypal IPN before, I needed to enable a crude but effective way to get the callback (in config.php):
if(stripos($_SERVER["REQUEST_URI"],'/paypal') === FALSE) {
// disable CSRF for the /paypal
$config['csrf_protection'] = TRUE;
} else {
$config['csrf_protection'] = FALSE;
}
I'm guessing this could be an issue, you would get null as no data would be captured as CI reviews your $_POST/$_GET vars for security reasons.
If I misunderstood your question and am way off track, just let me know via a comment.
Check List
sandbox url: "https://www.sandbox.paypal.com/cgi-bin/webscr";
like #jakub says CSRF must be disabled for your paypal controller
IPN wont validate on localhost, however you should still get vars back.
var_dump(fsockopen ('https://www.sandbox.paypal.com/', 443, $errno, $errstr, 30));
var_dump($_POST);
The variables $errno and $errstr probably hold the reason for the failure. Echo them out with your error message.
I would like to create a batch script, to go through 20,000 links in a DB, and weed out all the 404s and such. How would I get the HTTP status code for a remote url?
Preferably not using curl, since I dont have it installed.
CURL would be perfect but since you don't have it, you'll have to get down and dirty with sockets. The technique is:
Open a socket to the server.
Send an HTTP HEAD request.
Parse the response.
Here is a quick example:
<?php
$url = parse_url('http://www.example.com/index.html');
$host = $url['host'];
$port = $url['port'];
$path = $url['path'];
$query = $url['query'];
if(!$port)
$port = 80;
$request = "HEAD $path?$query HTTP/1.1\r\n"
."Host: $host\r\n"
."Connection: close\r\n"
."\r\n";
$address = gethostbyname($host);
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, $address, $port);
socket_write($socket, $request, strlen($request));
$response = split(' ', socket_read($socket, 1024));
print "<p>Response: ". $response[1] ."</p>\r\n";
socket_close($socket);
?>
UPDATE: I've added a few lines to parse the URL
If im not mistaken none of the php built-in functions return the http status of a remote url, so the best option would be to use sockets to open a connection to the server, send a request and parse the response status:
pseudo code:
parse url => $host, $port, $path
$http_request = "GET $path HTTP/1.0\nHhost: $host\n\n";
$fp = fsockopen($host, $port, $errno, $errstr, $timeout), check for any errors
fwrite($fp, $request)
while (!feof($fp)) {
$headers .= fgets($fp, 4096);
$status = <parse $headers >
if (<status read>)
break;
}
fclose($fp)
Another option is to use an already build http client class in php that can return the headers without fetching the full page content, there should be a few open source classes available on the net...
This page looks like it has a pretty good setup to download a page using either curl or fsockopen, and can get the HTTP headers using either method (which is what you want, really).
After using that method, you'd want to check $output['info']['http_code'] to get the data you want.
Hope that helps.
You can use PEAR's HTTP::head function.
http://pear.php.net/manual/en/package.http.http.head.php