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):
- 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.
- 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.
- 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.
- 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.
keepalive_timeout 70; #reduces the cpu load
ssl_protocols SSLv3; # only use SSL version 3
ssl_prefer_server_ciphers on; # don't trust the client
# caches 1 MB of SSL sessions in memory, faster than OpenSSL's cache:
# cache the SSL sessions for 5 minutes, just as long as today's browsers
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.
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.
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.
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
- Securely configure you web server as described above
- Do use proxy_set_header X-FORWARDED_PROTO https; in the HTTPS part of the configuration
- Do use proxy_set_header X-FORWARDED_PROTO http; in the HTTP part of the configuration, just to be sure