Securing A Website With Client SSL Certificates

In the comments of the last article Morgan came up with the idea of client SSL certificates to secure the admin panel. This is not authentication in a classical sense, it is saying which SSL certificates (which you self-signed) you allow to access a particular site. This is a better solution than limiting the access to various IP adresses when you are a work nomad and you have to access it from different parts in the world.

The steps to do this are:

  1. Setup OpenSSL to become a Certificate Authority (CA)
  2. Create a root CA key
  3. Create a key for the (sub)domain in question
  4. Setup your web server
  5. Create a client certificate and install it in your browser

Here is the HOWTO: Securing A Website With Client SSL Certificates

[WebAppSec] Twitter’s admin panel compromised

One of the best known Rails application, Twitter, was compromised very recently. A French hacker claimed that he gained access to Twitter’s admin panel at https://admin.twitter.com/. Twitter confirmed that an outside individual gained access to details of several accounts, including accounts from Ashton Kutcher, Lily Allen, Britney Spears and Barack Obama.

It seems that the hacker gained access to a Yahoo Mail account of a Twitter employee by answering his “secret question” and thus he could reset the password and access his mail account. In one of the e-mails he found the Twitter administration password.

Here is list of must-have security countermeasures for admin panels:

  • Don’t make the admin panel publicly available unless you really have to! It seems that admin.twitter.com was secured with a .htaccess file. I recommend to at least allow access only from several IP addresses.
  • Don’t make admin panels pretty, make sure they are Cross-Site Scripting and CSRF-safe! A simple message to the support panel containing Cross-Site Scripting is sometimes already enough to gain access to the panels.
  • Forgotten passwords are a huge problem. Resetting it with a simple answer to an easy question is definitely not enough. Sending a password-reset URL to an e-mail address is currently one of the best solutions (but it isn’t totally secure).
  • It seems that everyone with access to the Twitter admin panel may do everything. Why can everyone download “emails to gzipped CSV file”? Why not require to re-enter another password for sensitive actions or use a role-based admin user model?
  • Someone suggested using authentication tokens that provide a randomly generated key upon login

I wrote about this already a while ago.

[Server] Nessus vulnerability scanner for networks and systems

Tenable Network Security has announced the release of Nessus 4 a short while ago. Nessus is a network vulnerability scanner that can be used to identify potential vulnerabilities in your systems and networks. You can use it to find open ports, unpatched software, configuration errors and possibly leaks of private data.

The software package consists of a client and server. The server keeps the scanner plugins up to date and the client performs the actual scan.

  1. Download the package from the homepage (you need to provide your e-mail address)
  2. Install it, start the server program and download the newest scanner plugins
  3. After you started the server, you can connect to it from the client
  4. Add an IP address on the left side and add scanner types to the right side (choose all plugins for a start)
  5. „Scan now“: The scan takes about 5 minutes

Nessus is available free of charge for non-enterprise and personal use. The commercial version (Professional Feed) costs $1,200 which you can evaluate for 15 days. Even a one-time check gives you a great overview of what a potential attacker would see.

Hidden actions render templates

Sometimes you have to temporarely exclude actions from a controller or someone just forgot to remove legacy actions. The hide_action method can be used in controllers to hide the given methods from being callable as actions. However, it might not work as expected, because it still renders the template associated with it, but it doesn’t call the code in the action method. This could be a security issue if the template contained only text or if it didn’t throw errors on nil objects.

Now, one could think that moving an action to the protected or private part of the controller solves the problem and hides the methods from being callable as actions. No, this is the same problem. The only way to actually hide actions is to remove them altogether or remove the route for it. Remember that there might be the standard route still in place.

Common Apache Misconception

There is an interesting article over at sans.org about a common Apache misconception. In Apache you usually have a configuration directive like this:

LoadModule php4_module modules/libphp4.so 
AddType application/x-httpd-php .php

The misconception is about the .php part. It does not mean that it handles all files ending with .php as PHP code. The .php part can be anywhere in the file name, as in: file.php.txt. The impact of this is that if you have this module enabled and someone uploads a file.php.txt, Apache will execute the PHP code in it. Of course this only happens when the upload directory is in the DocumentRoot of Apache.

The original article has a checklist about how to upload files.

SSL and Rails

About SSL
Secure Socket Layer (SSL), or its successor Transport Layer Security (TLS), gives us assurance of two things: Firstly when a client connects to a web server, the client can be sure that it is talking to the right server by checking the certificate the server sends it. Secondly, SSL assures you of the confidentiality of the data, as the client and the server exchange encrypted messages that cannot be understood by anybody else. Before messages can be encrypted, the client and the server need to shake hands and exchange a secret key for this session, the so-called session key. This is how the SSL handshake works (using RSA):

  1. Server sends certificate: When the client requests for a SSL page, the server sends a certificate that it has obtained from a trusted certificate authority.
  2. Client generates session key: This certificate contains the public key of the server. After satisfying itself that the certificate is correct and the server is a genuine one, the client generates one random number, the session key.
  3. Encrypt the session key and send it: This key is encrypted using the public key of the server and the private key of the client. Then it will be sent across.
  4. Server decrypts it: The server decrypts the message with its private key. Now both sides have a session key known only to the two of them, because only the server and the client have access to their private keys needed for decryption and encryption of the session key. All communication to and fro will now be encrypted and decrypted with the session key.

SSL Ciphers and Protocol
This is a simple SSL handshake, because only the client makes sure talking to the right server, but not the other way round. Today’s browsers normally perform only a simple SSL handshake. In the first phase of the handshake the server and the client also exchange the highest TLS protocol version and a list of suggested cipher and compression suites. In most browsers SSL version 2 is disabled by default as it is insecure. But you can also disable SSL version 2 and several cipher suites on the server side. In order to find out about what ciphers with high strength (128 or more bits) your server machine supports, type in:

$ openssl ciphers -ssl3 -v ‘HIGH:!ADH:@STRENGTH’

This will display a list of ciphers ordered by its strength and without anonymous DH (!ADH) which is very basic. Make sure to specify the SSL ciphers your webserver is going to use in its configuration to at least the “HIGH:!ADH” level. Many web servers try to be as compatible as possible by default and accept 40, 56 and 80 bit DES keys, but they are very weak. This configuration works in all modern browsers. It is highly recommended denying weak ciphers by only accepting 3DES and AES SSL cipher suites of at least 128 bits and above. Here is an example SSL configuration for nginx – Apache configuration should be similar.

ssl on;
ssl_certificate /etc/nginx/certs/example.com.crt;
ssl_certificate_key /etc/nginx/certs/example.com.key;
keepalive_timeout 70; #reduces the cpu load
ssl_protocols SSLv3; # only use SSL version 3
ssl_ciphers HIGH:!ADH;
ssl_prefer_server_ciphers on; # don’t trust the client
# caches 1 MB of SSL sessions in memory, faster than OpenSSL’s cache:
ssl_session_cache shared:SSL:1m;
# cache the SSL sessions for 5 minutes, just as long as today’s browsers
ssl_session_timeout 5m;

Replay attacks
Hackers might also try to repeat recorded requests. In order to fend of these replay attacks, the SSL handshake protocol uses random numbers and sequence numbers. If someone repeats old sequence numbers, the server will reset the connection.

Cryptographic attacks
The attacks against SSL include cryptographic attacks. Brute Force Attacks on the cryptography aim at breaking the cryptography in use. It is feasible to break a 40-bit key by brute force attacks, but it takes a fairly large number of computers. Therefore keys should be at least 128-bits long, though 256-bits are standard now. You also want to use AES encryption, as it is the successor of 3DES. When generating a public and private key for the certification authority, you can choose to enter a pass phrase with at least 8 characters. This pass phrase is like a password and you will need to type it in to start the web server using the SSL key. You can leave the pass phrase out if you want, but this is less secure. One reason to leave it out would be that automatic restarts of the web server will work then.

Man-in-the-middle attack
A man-in-the-middle (MITM) attack is only successful when the attacker can impersonate the client and the server and when he has access to the network of the client (like an insecure wireless LAN). The man-in-the-middle behaves like the server for the client and sends the clients’ requests to the original server. It also behaves like the client to the server. The MITM uses his self-generated certificate to authenticate against the user. Therefore today’s browsers include a list of mutual trusted certification authorities and throw an error when certificate is self-signed. There are also many cases where the client’s browser issued a warning that the certificate is not valid, but in real life most users ignore this message and just accept it. This protection might only be circumvented if a certification authority cooperated with the attacker or if the attacker managed to create his own certificates signed by a certification authority known to the victim’s browser. The latter means breaking a cryptographic hash function, such as MD5, which is already theoretically possible. SSH (Secure Shell) provides a method to check the fingerprint of the server’s certificate after the first login. This fingerprint will be verified on each next login and it will throw an error if it changes. MITM attacks are quite unlikey, but not totally unfeasible.

Software versions
It is very important to always run the latest versions of OpenSSL and nginx or Apache. Recently there was a vulnerabilty in OpenSSL which rendered several SSL certificates insecure.

SSL and Rails

Rails provides the request.ssl? method to find out whether it is HTTP or HTTPS. In order to make this method work correctly, you have to include the following line in your web server configuration file in the HTTPS part:

proxy_set_header X-FORWARDED_PROTO https;

This is how it works in nginx, and it should be similar in Apache. More details are here. The problem is, that a malicious user could pretend that this request is HTTPS by intercepting a request and sending a “X-FORWARDED_PROTO: https” header field over HTTP. Whether this may do any harm to your application depends on your application. But you don’t want the users to pretend, don’t you? Note that this works with a stand-alone Mongrel, but I couldn’t manage it when a front-end web server (nginx, Apache) is there. Nevertheless, it might be possible.

Countermeasures and Conclusion

  1. Securely configure you web server as described above
  2. Do use proxy_set_header X-FORWARDED_PROTO https; in the HTTPS part of the configuration
  3. Do use proxy_set_header X-FORWARDED_PROTO http; in the HTTP part of the configuration, just to be sure

XSS and CSRF Vulnerabilities in the in_place_editing plugin

The Ruby on Rails Weblog reports two vulnerabilities in the in_place_editing plugin:

  • The actions generated by in_place_edit_for perform no verification of the request method, allowing a hostile website to bypass built in CSRF protection.
  • The the input controls generated by in_place_editor_field perform no output sanitization, leaving the application vulnerable to XSS attacks.

Users of this plugin are advised to update the plugin from git://github.com/rails/in_place_editing.git . The original post provides a zip file and a patch if you’re unable to use git.

MIME sniffing countermeasures

This post is a follow-up on my previous post.

Sending out files is a tricky thing. Rails’ send_file() method may send a file inline or as attachment. But in order to send a file inline, you have send the correct content type (:type parameter) as well. If you set send_file :disposition => ‘inline’ AND the :type is set to a correct and matching content type, the file will be displayed inside the browser if it understands the file format. If the :type attribute is not set correctly or the default (application/octet-stream) then Firefox will present the download box, but IE6 and IE7 still display it in the browser. If the content type, file extension and file signature do not match, IE6 and IE7 will start the MIME sniffing (see previous post for details). So if the first 256 bytes of the file to be downloaded contain any HTML, the browsers will show it and run any included JavaScript code.

If you set send_file :disposition => ‘attachment’, the browser will display a download box even if the browser could display this type (such as HTML).

To sum it up: If someone uploads a poisoned image with file extension “.html”, “.pdf” or whatever and you send out the file with send_file :disposition => ‘inline’, IE6 and IE7 will run the included JavaScript code. This is because most upload plugins (such as the popular attachment_fu) assume the content type to be “text/html” or “application/pdf” in this case. More correct: Actually the browser already assumes it when uploading the file, but the plugin doesn’t verify it.

The conclusion of this is, that (1) setting the correct content type when sending files with send_file :disposition => ‘inline’ is crucial. And (2) it is very likely that you want to reject images or other files the browser can display that have malicious code in it.

1. Setting the correct content type

I use attachment_fu so I added a before_create :set_content_type_by_content method to my model. If you allow users to upload another file when editing, you can do the same in before_update. This method uses the shared-mime-info package and its Ruby wrapper with the same name. This package is basically a XML-database with known MIME types, its typical file extensions and some magic numbers to determine a file’s real MIME type. Magic numbers are constants found in binary files used to identify a file format.

You can install the package on OS X using MacPorts:

sudo port install shared-mime-info

and follow the OS X specific instructions here. Use your favourite installer for other systems.

Install the gem like this:

sudo gem install shared-mime-info

Now you can require ‘shared-mime-info’ and get the MIME type for a file: MIME.check(filename). The check() method actually uses the file extension to determine the MIME type in cases where MIME.check_magics(filename) doesn’t work. I overwrote this behaviour, i.e. method, so that it doesn’t, because that’s what we wanted to avoid.

Here’s the code to overwrite the behaviour. You can put in lib/core_extensions.rb and require the file in initializer (config/initializers). And here is the before_create callback for the model with attachment_fu:

def set_content_type_by_content
   mime = MIME.check_magics(self.temp_path) #try magic numbers first
  mime = MIME.check(self.temp_path) if self.content_type.nil? #do other checks if it failed
   self.content_type = mime.to_s
   self.filename += mime.typical_file_extension unless mime.match_filename?(self.filename)
end

At the end I will add the typical file extension for this MIME type to the file name if the current doesn’t match the MIME type.

2. Reject malicious files

So far so good, now there won’t be any MIME type sniffing in the browser because the MIME type, the filename and the content matches. But nonetheless, you may want to reject images/files that contain JavaScript in the first place. In order to check that I suggested a regular expression in my previous post. As I said, this was meant as a starting point, because it is not quite good. There were suggestions using a whitelist, but unfortunately binary files contain all kinds of <> so that a whitelist filter is not feasible. We do need a blacklist filter that recognizes all kind of injection tricks.

Here is the solution I came up with. The magic happens in the regular expression in the MimeTypeInjection module. The regex is quite complicated, because it has to recognize tags like <sc\0ript>, too (yes, these quirks work in some browsers). The browser_displays_this_type? method determines which content types you want to check for HTML. Of course the “text/html” content type contains HTML, so you have to adjust this method according to your requirements (what types of files users are allowed to upload). Also, this list might not be complete, there might be other content types the browser displays inline (post a comment for other types).