Rails Security Article - Brainstorm for Brute Force?

no comments

NOTE: This is a disproven thought experiment: unable to prove guessed password succeeded and pass the information along.

Prompt

I’ve just finished reading (yes, the whole thing) an article about securing Rails applications. While it talks about security with Rails specifically in mind, it isn’t limited to just Rails applications. It is a good read for anyone who creates, maintains, or audits web applications of any flavor. You can read the Ruby on Rails Security Guide now or later if you like. I’ll explain as much as necessary to get my point across for this article.

One section in particular concerns Cross-Site Request Forgery attacks. According to the article, such attacks are carried out by placing HTML (yes, HTML) pointing to a specific action in a Rails application. The article used the example of using an image to delete critical information from a target site. The article continues to explain that one can use javascript to create a form that a user mistakenly clicks to use POST attacks instead of simple GET requests. (I have figured out a way to modify this so clicking is not required below)

This method of attack got me thinking. I had never specifically thought of doing this to attack my own sites. It makes sense though and naturally follows from the application of the semantics of the HTTP protocol. It also got me thinking of how one might use it to further exploit a site.

Is it worth it?

I believe it is. If using no more evidence that it exists as chapter 3 in this article of 8 regarding security measures, the practical result is that, unmitigated, your application’s security is based upon the security of other, well-traveled applications. The article continues to explain a simple fix as provided by Rails. The ONLY fix is the protect from forgery secret. I haven’t evaluated in depth exactly how effective it is, but I’m currently cooking up a new attack based on the CSRF vector.

What’s your Vector Victor?

Security is no joke. I believe I’ve come up with an attack using the CSRF vector that could potentially be devastating. There’s really no one to report this to as it applies to every site that is vulnerable to this type of attack. I believe it should be called the CSRF-MPDBF: The Cross-Site Request Forgery Massively Parallel Distributed Brute Force attack.

How does it work?

It uses the POST exploit as discussed in the article using javascript to commandeer the computers of any visitor to try a random password or two and report success to another site using the same CSRF exploit.

The Set-up (Huh? No really, how would that work?)

Let’s presume there are 2 sites: A and B. Site A is some website for which the attacker wants login credentials. The attacker may, or may not know the login name of a user. Having it certainly helps the attacker, but it’s not strictly necessary for this exercise. Site B is a forum with lose security policies that allows javascript code to be inserted into pages by users. Examples aren’t limited to forums, but a multitude of other “public service” sites.

Presume that Site A has a simple log-in form, with or without SSL; encryption is moot. Also presume that this site doesn’t use request forgery protection. It’s just a simple login for such as:

<form action="/security/login" method="POST" />
<p><label for="username">Username:</label><input name="username" type="text" /></p>
<p><label for="password">Password:</label><input name="password" type="password" /></p>
<p><input type="submit" value="Login" />
</form>

Site A will look at the username and password in the POST body and do a look up of the username, then see if the password matches. I’m not talking about SQL injection here. Presume it’s a Rails application and it’s using the sanitize_sql functions correctly. If the login is successful, the URL will change (a redirection will occur), otherwise, presume the application displays the same message (same URL) without redirecting. Most applications redirect after login. But it is possible to confirm login another way. This is just one example. The key point is that an attacker is able to reliably confirm a successful login (and conversely, an unsuccessful one).

Are we there yet?

Sorry, let’s get to some steps of the attack:

The attack code

The first step is for the attacker to create special javascript code as in the above article with one key difference: target the login script and include a username and GUESSED password.

Say what? A GUESSED password?

Yes, guessed. I told you that this was a brute force attack. Javascript includes a random number generator:

Math.random()
So why not use it to generate a random password?

But that will never work!

Oh really? Why not? Every javascript engine is different. No browser and no visit to the page will likely use the same password. If you post some comment to a popular site and get a lot of people to trigger this, the target site A could be compromised in a matter of days, if not hours.

Dude, where’s my code?

FIIINE! This javascript is inserted on site B to attack site A (modified from the article: “On Rails Security”

<a href="http://www.b.com/" onmouseover="
  var f = document.createElement('form');
  function passwordGuess() { /* ... details here ... */ }
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.a.com/account/destroy';
  var x = document.createElement('input'));
  f.appendChild(x);
  x.setAttribute('name','username');
  x.setAttribute('value','TARGETUSER');
  x = document.createElement('input'));
  f.appendChild(x);
  x.setAttribute('name','password');
  x.setAttribute('value',passwordGuess());
  f.submit();
  return false;">To the harmless survey</a>

Here’s how it works. I added a stub function for the password guessing routine as that is not what is at issue here. This code is to be injected into a forum post or SQL injection or whatever vector such that it appears to public users of site B. When the javascript is called, on click, it creates a form in HTML. This is a bit strange. Javascript modifies the DOM to add a form. The form is then submitted as though the user had submitted a form from site B. But server A doesn’t know the form exists on site B and processes the username and password as though the request came from site A. This, my friends, is effectively a login attempt. The user is completely unaware that anything occurred. It’s executed when the hover over the link and when they click, the link works as desired. This is different from the original article as it worked on click. The unwitting user at site B cannot detect that this attack has occurred unless he or she inspects the traffic. What normal user does that?

2010/02/01 – Editor’s Note: I’m trying to get this to work, I cannot seem to get the status of a password guess using Javascript. This might be a hair-brained idea after all.

OK, but the attacker can’t get that random password!

Not true. Use the same attack vector. Presume site B or some other site C is vulnerable to the same type of attack. To deliver the credentials back to the attacker simply modify the javascript to detect if the page was redirected. Once the credentials are verified, post the brute forced credentials to a public location. An attacker could even use the same type of attack to post those results. The attacker simply checks the destination site for updates and snarfs the credentials. Then the real fun begins and I don’t have to explain what can happen at this point.

Impact?

Well, think of it in simple terms. Every browser visiting site B’s compromised page becomes a weapon. Each person that goes to this page on site B with the code contributes to the attack. Assuming site B is very popular, but lax on code insertion filtering, site A will be attacked x times, where x is the number of users that trigger the attack on site B. If the attack runs m times (multiple attempts), then site A will be attacked x*m times. If B is a popular site, then x can easily approach millions of triggers in a period of a few weeks. Assuming a small dictionary can be loaded, a clever attacker could author the script to try word combinations, or insert symbols between, before, and after words, try numbers, etc. If the username is known in advance, it’s only a matter of time before that account on site A is compromised by visitors on site B. If multiple sites with B’s lax security exist, then the attack is: m*(x1 + x2 + …xn) where x is set sites like B to which the malicious code is posted.

It’s sort of like creating a bot network that only exists when users go to a specific page or pages.

But it’s a random password, won’t the same password be tried again and again?

Yes. There will, undoubtedly, be collisions in the the attack space. The key is that, depending on the javascript random engine, there will be significantly greater quantities of untried attacks. Even pseudo-random number generators try to be difficult to predict. In this case, this randomness works in favor of the attack by created more unique generations. A super-sophistocated attack could post failed results to a forum. These results could then be compiled to a random number black list that will skip over previously failed trials. That would significantly reduce the collisions (not eliminate) but is more sophisticated and again, if the PRNG has a decent algorithm, won’t be necessary.

Proof of concept?

Ha, no way. I would not write one in the wild.

Has anyone else thought of this?

I suppose I should have looked this up before I started writing. However, writing down an idea helps solidify the thought. I didn’t find anything with a quick Google Search but that isn’t thorough research. Most of the hits involve the tokens getting brute forced. My example assumes there are no tokens. But you can see how even brute forcing the tokens leads to slowing the progress of an attack.

Why is this so bad?

Because there’s no one IP source to block. It’s distributed and not easily detectable. Couple this with a password dictionary somewhere and you have a serious attack. Couple it further without the need to click something to trigger the attack (below) and it’s even more wide-spread.

No Clicking Required

The article gives the example of javascript code that creates a form, then replaces a link a user would normally click with the form such that it performs the CSRF attack. It is possible to create an exploit using the same vector but not require a click to activate it. Simply create a form as indicated in the article with my modifications, then write a script to periodically execute it asynchronously and without redirecting the user to the login page. This hides the attack and permits multiple guesses; as many as can be squeezed into the time the user is on the B site.

How bad is it?

I would imagine most of the big sites already mitigate this attack using forgery tokens. While no security expert, I believe that Facebook is using them based on their login form. I’ve removed some of the worthless table formatting, added line breaks, and only used the HTML form fields. For some reason, the field is listed as having the id=“lsd”. I have no idea why.

<form method="POST" action="https://login.facebook.com/login.php?login_attempt=1" id="login_form">
<input type="hidden" name="charset_test" value="&euro;,&acute;,€,´,水,Д,Є" />
<input type="hidden" id="locale" name="locale" value="en_US" autocomplete="off" />
<input type="hidden" id="non_com_login" name="non_com_login" autocomplete="off" />
[...]
<input type="checkbox" class="inputcheckbox " id="persistent" name="persistent" value="1" />
[...]
<input type="text" class="inputtext" title="Email" placeholder="Email" id="email" name="email" value="" />
<input type="password" class="inputpassword" id="pass" name="pass" value="" />
<input type="text" class="inputtext hidden_elem DOMControl_placeholder" id="pass_placeholder" name="pass_placeholder" value="" />
<input value="Login" type="submit" class="UIButton_Text" />
<input type="hidden" name="charset_test" value="&euro;,&acute;,€,´,水,Д,Є" />
<input type="hidden" id="lsd" name="lsd" value="KboH0" autocomplete="off" />
</form>

The 5 characters seem to change from browser to browser. It appears to be generated, then persisted using cookies (cookie name: lsd).

The real problem will be with smaller sites that are written with custom code that do not use the forgery prevention tokens. I cannot even begin to guess how many sites are affected.

“And what was the real lesson? Don’t leave things in the fridge.”

Spike may have been onto something there (quote is from Cowboy Bebop, “Toys in the Attic”). Don’t get soft on your security. As you can see, we’re all in this together. Your weakness is someone else’s exploit vector. You won’t just become a statistic, you’ll be actively, albeit unwittingly, contributing to an attack on other sites at the discretion of less scrupled persons.

Read the article! Don’t leave your applications in security cold-storage. It’s important.

Disclaimer

Again, this is an informational article to help site programmers build more secure sites. I am not responsible for people that misuse these ideas or examples.

Creating your own Apache SSL Certificate Signed by your Root CA

no comments

This article is part 2 of Going SSL with Your Own Root CA

Putting your Root CA to work

Now that you have your own Root CA, it doesn’t do anyone any good unless you use it. You use it by creating certificates for services. The rule is one certificate per-IP address. If you need additional certificates, you should have an IP address for each certificate. Any domain (or sub-domain, etc.) listed in the Certificate’s CN (Common Name) should resolve to its own unique IP address. Now, nothing is set in stone. But that’s the way it should be done. I’ve used multiple certificates for different domains on the same server using Virtual Hosts (same IP) before, but Apache warned me every time I started the server that this is bad practice. If you’re using Apache, it will still work, but you’ve been warned (again).

Disclaimers

This guide is for amateur, non-production use only. I make no warranties or guarantees as to the correctness of this document. This guide involves escalating privileges and may cause irreparable damage if used improperly. You use this document at your own risk and I hereby disclaim any responsibility or liability for your actions or non-actions.

Assumptions

I’m assuming you’re using Apache 2.X with mod_ssl already installed. I’m also assuming you’ve already set-up a vhost file with the site you want to SSL already created and configured minus the specification of the server certificate, and trust chain. I realize this is a lot to assume. This can help you setup an Apache server with SSL.

Creating a Server Key

Just as with the Root CA key, you need a server key. Now, if you already have one, relax, skip this step (though I still urge you to create and migrate to the “secure” environment created in the next step). You can re-use your server key as many times as you like until you want to upgrade the private key quality (or the key is compromised). If you have not created a server key, you’ll need to do so now.

We’ll use OpenSSL again, but first, let’s create a safe place for our key. The key needs to be readable by the server upon start-up (meaning, the key cannot be encrypted) or Apache (or any other server) will be unable to use it without first asking you for a password. If you don’t mind typing it in each time, that’s your prerogative. However, for live or even development environments, it’s simply impractical.

Just as with the Root CA key, you must keep this server key confidential as well. If it is breached, anyone can masquerade as your web server; thus defeating the purpose of the certificate. If you need to move the key, you are advised to use OpenSSL to encrypt it first, however, that is a tutorial for another day.

Create the “secure” environment

Create a comfy place for your key. I like to put it in /etc/apache2/ssl with Gentoo, but the better place is likely /usr/local/etc/apache2/ssl and if you use Windows: there are no rules for file placement. With that: create your directory:

sudo mkdir /usr/local/etc/apache2
sudo mkdir /usr/local/etc/apache2/ssl
sudo chmod 0770 /usr/local/etc/apache2/ssl
sudo groupadd apache_ssl_admins
sudo chown :apache_ssl_admins /usr/local/etc/apache2/ssl
sudo usermod -a -G apache_ssl_admins YOURUSERNAME # varies by OS

Here’s what I’ve done:

  1. Created an apache2 directory (if it didn’t exist, if this call fails, just make sure the directory is there)
  2. Created the SSL directory where we’ll store the server key
  3. Changed the permissions for the directory such that only root can access it
  4. Created a group called “apache_ssl_admins”
  5. Changed the ownership of the new directory to allow members of the apache_ssl_admins access
  6. Added you to the new group called “apache_ssl_admins”, note that this step may vary among Operating Systems so modify this command as required.

You’ll need to log out and log back into your server for the group membership to take effect.

Now, I’ve put the word “secure” in quotes as if anyone were to compromise the server and gain access to the apache_ssl_admins group, your key is then accessible. Your key is only as secure as your server.

Once you’ve logged back in, cd to the newly created “secure” environment.

cd /usr/local/etc/apache2/ssl

Creating the server key

You will now generate a server key. I highly recommend that you study the documentation for OpenSSL. It’s very very rough, but it will help you understand the commands you’re entering. It may be necessary to update these commands as machines become faster and the encryption level becomes insufficient.

openssl genrsa -out www.pem 2048

This generates a new file called www.pem in your “secure” environment. This is an RSA private key with a bit length of 2048. This bit-length is considered to be good by today’s standards. You may use any power of two; though, be warned, larger numbers require more time to initiate SSL connections. Smaller numbers will result in a less secure SSL handshake (and vicariously a less secure SSL session).

Again, it is a good idea to encrypt this key with 3des. I’ve not done so to facilitate this demonstration. Please see the previous tutorial for instructions about encrypting your server’s private key. The instructions for encrypting your Root CA private key are applicable to this purpose.

Generate a Certificate Signing Request

The Certificate Signing Request (CSR) is used by Root CA’s to create certificates for people who need them. A CSR is a uniform way to telling Root CA’s exactly what the certificate should say. The Root CA merely signs the CSR and viola: a certificate. Well, it’s not all that simple. The Root CA also adds an expiration date to the signature or other fields deemed necessary or desired. But before it can be signed, the CSR must be generated by using the server’s key:

openssl req -new -sha1 -out www.csr -key www.pem

This command will create a file called www.csr in your “secure” environment. This file is a Certificate Signing Request. You may expose this file publicly, though that is not required if you’re signing it yourself. This new request requires that it be identified using the SHA1 hashing algorithm. This algorithm is safer than MD5 as it has been proven that MD5 collisions can be generated in a reasonable time.

You will be asked a series of questions after running this command. Answer them honestly. When you see the CN (Common Name), enter the domain name and subdomain for your certificate. I.E. for my domain: christopher.wojno.com, use as the CN: “christopher.wojno.com”

Sign your CSR and create a real server Certificate

Now that you have a CSR, we’ll use our Root CA certificate and key to sign the CSR. This creates a certificate you can use in your Apache server (or other SSL-capable web server). If you created your Root CA using my previous article, then you may use the paths I have specified here. Otherwise, you’ll need to adjust the -CA, -CAkey and -CAserial parameters to match what you have used.

openssl x509 -req -days 365 -in www.csr -CA /usr/local/etc/certificate_authority/certs/cacert.crt -CAkey /usr/local/etc/certificate_authority/private/cakey.pem -CAserial /usr/local/etc/certificate_authority/serial.srl -out www.crt

This is a lot to swallow in one step. This used to be where I became completely lost with regard to SSL Certificate generation. The command is very complex so I’ll break it down part by part:

  • We’re asking OpenSSL to do something
  • Specifically, we’re asking it to invoke it’s x509 library (SSL certificate chains)
  • We’re then specifying that our input will be a CSR (-req)
  • We want the resulting certificate to be valid for 365 days (-days 365) from date of signing
  • The CSR is the input (-in www.csr)
  • We’re using the CA public certificate created in the previous tutorial: (-CA /usr/local/etc/certificate_authority/certs/cacert.crt)
  • We’re using the CA’s private key created in the previous tutorial to sign this CSR: (-CAkey /usr/local/etc/certificate_authority/private/cakey.pem)
  • We’re using the CA’s serial file to mark the resulting certificate: (-CAserial /usr/local/etc/certificate_authority/serial.srl)
  • Finally, we’re outputting a new certificate for the web server: (-out ../www.crt)

That little command does quite a bit. After running it, you will be prompted for the CA Root private key password (if you created one as I recommended that you do). It should automatically increment the serial file (serial.srl) so that future certificates do not have the same serial number used to identify certificates.

Now we have an SSL certificate for your server located in /usr/local/etc/apache2. Time to tell Apache where it is.

Telling Apache about our certificate

This next step takes place in the Virtual Host configuration file (or in your httpd.conf if your OS distro has not broken that file up yet). The true location of this next step can only be described in a universal fashion by describing the purpose: website configuration. Locate that file. In Gentoo, it’s located at: /etc/apache2/vhosts.d/00_default_ssl_vhost.conf I am unsure as to its location on other operating systems.

Once you’ve located this file, you’ll need to edit it (this may require privilege escalation). Edit this file in your favorite editor (or least favorite, there is no favorite requirement).

Add or amend the following lines in the VirtualHost section of the website you wish to secure using the new SSL Certificate. The hostname of this virtual host must match the CN name you specified when you created the CSR for this site.

SSLCertificateFile /usr/local/etc/apache2/ssl/www.crt
SSLCertificateKeyFile /usr/local/etc/apache2/ssl/www.pem
SSLCertificateChainFile /usr/local/certificate_authority/certs/cacert.crt

This tells Apache:

  1. Where our certificate can be accessed so that the server may present it to requesters
  2. Where the server’s private key file is so that the certificate may be used to decrypt encrypted requests from requesters (these requests are encrypted using the certificate in step 1)
  3. Where the certificate authority’s certificate is located so that Apache is able to append it to the certificate presented in step 1 (to avoid having the user’s browser look it up first)

Again, I’m assuming that you’ve already set up Apache to use SSL:

  1. You’ve installed mod_ssl
  2. You have enabled it in the /etc/conf.d/apache2 file
  3. You’ve allowed access to port 443 through all firewalls (or other port you wish to use for SSL)
  4. You’ve configured your Apache instance to listen on the SSL port

If you’ve not done so, Apache will not understand the SSL commands and will not start.

If all goes well, when you access the website at the CN name on your certificate and use the https protocol (i.e. https://CN where CN is the name used when you were asked for the Common Name when generating the CSR), you will see your website and will not be prompted to accept the authenticity of the certificate. Again, I assume you’ve completed the previous tutorial and have installed your own Root CA (used to sign this certificate) into your machine’s trusted Root certificates.

Should something go wrong

Debugging OpenSSL certificate problems is tricky and complicated. OpenSSL provides a tool for doing so over the network. I used it extensively during my first attempts at creating my certificates. Use:

openssl s_client -connect CN:PORT -debug

See the documentation# for more information. Be warned, this is a developer tool and comes with few instructions and brief explanations.