PHP Secure the shell

From LXF Wiki

Extensions special script programming

(Original version written by Paul Hudson for LXF issue 81.)

Original article (PDF) (http://www.linuxformat.co.uk/wikipdfs/LXF81.tut_php.pdf)

Sick of people breaking through your elite rot26 encryption? Get some security skills with Paul Hudson.

Herodotus once said: “Call no extension happy until it’s being used.” Well, it went something like that, and either way it’s correct: PHP, through the PHP Extension Community Library (PECL), has hundreds of weird, wacky and wonderful extensions waiting for us to try out. But until you actually know about them – until you try them yourself – they might as well not exist.

This issue we’re going to look at one of the hidden gems of PECL: the SSH2 extension. This allows you to form secure, encrypted connections over the internet using PHP, and execute shell commands, transfer files and do whatever else you would normally do over SSH. Yes, this is potentially risky if you’re working over the web, but as long as you a) put a password on the web page and b) prompt people to enter their password for the SSH connection, you’re quite safe. On the other hand, if you’re writing scripts for local console use, this extension is a powerful tool for querying remote servers beyond the prying eyes of crackers.

Table of contents

Making the connection

You almost certainly don’t have the PHP SSH extension installed, which isn’t surprising: it’s not bundled with PHP, and its primary dependency (libssh2) is very rarely found in distros. Once you’ve gone through the steps in the Installation Guide box (below), we need to do a quick test connection to make sure you have it all set up correctly. Try this out:

<?php
 $conn = ssh2_connect(“192.168.133.98”, 22);
 if (!$conn) die(“Could not connect!”);
 echo ssh2_fingerprint($conn);
?>

This introduces two of the SSH functions: ssh2_connect(), which forms an SSH connection with a server (parameter 1) on a specific port (parameter 2), and ssh2_fingerprint(), which takes a connection as its only parameter and prints out the MD5 fingerprint of the server. These fingerprints don’t change. As a result, it’s quite easy to spot man-in-the-middle attacks (where hackers intercept your connection to sniff your password), simply because their key will be different from expected.

Run the above script and you should see the fingerprint printed out. This will be the same thing you’d see if you had typed ssh localhost on the remote machine, except that the command line SSH usually breaks the fingerprint into twocharacter blocks separated by colons.

When you form a connection by providing only the IP address (or domain name) and the port number, the SSH library will automatically pick strong encryption and a strong keyexchange algorithm, but it will gracefully downgrade if your client or the server don’t support certain algorithms. You can override the requests by specifying connection methods, using the third parameter to ssh2_connect(). This should be an array, which can contain keys called “kex”, “client_to_server” and “server_to_client”. The first, “kex”, is the key exchange algorithm that you want to request. This can be diffie-hellman-group1-sha1, diffiehellman-group14-sha1 or diffie-hellman-group-exchange-sha1.

The “client_to_server” and “server_to_client” keys contain their own arrays defining what encryption algorithms (“crypt”), compression methods (“comp”) and MAC methods (“mac”) to advertise. For example, if you wanted to use a fairly insecure SSH connection (something for a trusted network where speed is key), you could request 3DES encryption rather than the AES default. Here’s the code:

<?php
 $methods = array(
  “client_to_server” => array(“crypt” => “3des-cbc”),
  “server_to_client” => array(“crypt” => “3des-cbc”)
 );
 $conn = ssh2_connect(“192.168.133.98”, 22, $methods);
 if (!$conn) die(“Could not connect!”);
 $methods_neg = ssh2_methods_negotiated($conn);
 echo “Keys negotiated with: {$methods_neg[‘kex’]}\n”;
 echo “Client-to-server uses these methods:\n”;
 echo “ Encryption: {$methods_neg[“client_to_server”][“crypt”]}\n”;
 echo “ Compression: {$methods_neg[“client_to_server”][“comp”]}\n”;
 echo “Server-to-client uses these methods:\n”;
 echo “ Encryption: {$methods_neg[“server_to_client”][“crypt”]}\n”;
 echo “ Compression: {$methods_neg[“server_to_client”][“comp”]}\n”;
?>

The $methods array contains “client_to_server” and “server_to_client”, which are themselves arrays that contain “3des-cbc” in the “crypt” key. CBC stands for Cipher Block Chaining, which breaks your transmissions down into small blocks and XORs each block against the block preceding it before it is encrypted, which makes it much less likely that two plain text blocks look the same in cypher text.

If you want to try setting “comp” to enable compression, just add the “comp” key to the “client_to_server” and “server_to_client” arrays, specifying “zlib” as its value.

Our new code introduces the ssh2_methods_negotiated() function, which returns an array containing the key exchange, encryption, compression and MAC (Message Authentication Code) algorithms that were agreed upon between our PHP script and the remote server. We’re just printing some of them out here, but you might want to perform your own checks at this point to ensure that you’re using a secure connection.

Installation guide

Eight steps to SSH on PHP

  1. Install OpenSSL and its development libraries: libssl-dev or libssl-devel.
  2. Snag libssh2 from the Magazine/PHP directory of your disc (look for libssh2-0.13.tar.gz). Unpack it and run ./configure, then switch to root and run make all install.
  3. Get the PHP SSH2 extension from the Magazine/PHP directory of your disc (look for ssh2-0.10.tgz), unpack it, run phpize, ./ configure --with-ssh2, then make. This creates the ssh2.so in the modules directory. Copy that into your PHP extensions directory.
  4. To find your extensions directory, run php –i | grep ini to find your php.ini file – it’s probably in /etc/php.ini or /usr/local/lib/php.ini. If you don’t have a php.ini file, you need to copy the php.ini-recommended over from the PHP source code for your version.
  5. Open up the php.ini file and look for the ‘extension_dir’ line. This should be set to something like /usr/local/lib/php/extensions/
  6. Still in the php.ini file, search for ‘dll”, and you’ll come to the extensions section. You need to add this line:
    extension=ssh2.so.
  7. Save the file, and now move the file modules/ssh2.so to the location of your PHP extensions directory.
  8. Run php –m – you should see ssh2 in there. If not, you’ve mucked up one of the steps!

File it away

Like the SSH client suite, PHP’s SSH extension contains support for SFTP and SCP, the secure file transfer programs. Both use the same encryption algorithms and so are equally secure; it’s really down to whether you want a full FTP-style connection or whether you just want to read and write files.

Let’s look first at the SCP implementation, which is designed to implement cp-style copying over an encrypted internet connection. This requires two new functions: ssh2_auth_password(), which authenticates us to the server as a valid user, and ssh2_scp_send(), which copies a local file to the remote server. You’ll need to create a local file first, so run the following:

echo “Hello, world” > hello.txt

Now let’s transfer that file to our remote server. You’ll need have a remote username and password ready; just replace the “username” and “password” strings in this script:

<?php
 $conn = ssh2_connect(“192.168.133.98”, 22);
 if (!$conn) die(“Could not connect!”);
 ssh2_auth_password($conn, “username”, “password”);
 ssh2_scp_send($conn, “hello.txt”, “/home/
 yourremoteusername/hello.txt”);
?>

Note that you must specify the entire remote location for the file – in this case, /home/yourremoteusername/hello.txt. Receiving files is just as easy, and uses the ssh2_scp_recv() function, flipping the second and third parameters:

ssh2_scp_recv($conn, “/home/yourremoteusername/hello.txt”, “hello.txt”);

The other file transfer option is of course SFTP, which gives you some FTP-style commands (mkdir, stat etc) through an SSH-encrypted connection. But most importantly, it’s written as an fopen wrapper, which means that you can use it with all the standard PHP file functions. For example:

<?php
 $conn = ssh2_connect(“192.168.133.98”, 22);
 if (!$conn) die(“Could not connect!”);
 ssh2_auth_password($conn, “username”, “password”);
 $sftp = ssh2_sftp($conn);
 $file = file_get_contents(“ssh2.sftp://$sftp/home/paul/hello.txt”);
 echo $file;
?>

So, we connect to the server and authenticate with a username and password. Then we call ssh2_sftp() on our connection, which opens up an FTP connection for our files, and returns the handle to that connection. This is what we need to use for our file functions, so it must be stored away in a variable. Now, the call to file_get_contents() uses the ssh2.sftp:// wrapper, which means that PHP will read the file transparently through the SSH connection. This is where we use our $sftp variable for the SFTP connection stream, and again we need to specify the exact path to the file that we want to read, because it looks in the root directory by default.

She sells secure shells

Our last SSH trick will involve getting a full-on SSH shell that we can read and write to as if we were actually at the terminal. Again, PHP treats these as if they were local files, so we can fread() and fgets() from them as per normal. The one important rule here is that we need to wait for the computer to actually process each command, otherwise we’ll look for output before it’s ready. Here’s the code:

<?php
 $conn = ssh2_connect(“192.168.133.98”, 22);
 if (!$conn) die(“Could not connect!”);
 ssh2_auth_password($conn, “username”, “password”);
 $stdio = ssh2_shell($conn);
 sleep(1);
 while($line = fgets($stdio)) echo $line;
 fwrite($stdio, “uname -a\n”);
 sleep(1);
 while($line = fgets($stdio)) echo $line;
 fclose($stdio);
?>

Everything up to ssh2_shell() is same old, same old, but then we introduce the new function. This takes an SSH connection as its only parameter, and returns a stream resource that points to the STDIN/STDOUT of that shell – where all the program output is printed. This is where we need to write our commands and read the responses.

First things first, though: we need to sleep() for a second, to give the server time to clean up the connection and print out its banner (the welcome message for remote users). Then there’s a one-line while loop that reads in all the output from the server (“Welcome to XYZServer!” etc) and prints it straight to the local output. With the initial messages out of the way, we write our command: “uname –a”. This will print out all the system information for this computer, such as the name of the distro, the kernel version and architecture, and so on.

After waiting another second for this command to be processed and for its output to be fully printed, we have another while loop to print all the output waiting for us. Finally, we fclose() the shell to clean up after ourselves, and the script is done. Neat, eh?