Saturday, April 28, 2007

Captcha on Rails

Thinking about how Captcha handling could be implemented in Rails is a good mental exercise. Leaving aside the issue with entry barriers it creates for some people, and possible underlying technology implementations (like not-so-simple mathematical questions), let's formulate typical Captcha assumptions and requirements for a medium-sized Rails application:


  1. Image based
  2. Image/code combination could not be reused
  3. On an unsuccessful attempt, a new Captcha image is displayed
  4. Potentially multiple Rails application servers
  5. The attacker potentially knows the details on how you implemented Captcha

Let's pretend that we know how to generate a perfect Captcha image based on a supplied code string. All we need now is to meet the rest of the requirements.

Image/code combination could not be reused, means a need to prevent a human from entering the correct code, capturing a post sent to a server, and then replaying it many times via a robot.

Another point to remember is that a form with a link is generated on the first browser request, an image is retrieved with another request, and the Captcha validation goes with the third one. Potentially all three might arrive to different application servers.

Now, if you like challenges, try to think about possible solutions. The rest of this post talks about a few I have come up with by myself, though some might have been influenced by brainstorming sessions with the Revolution On Rails team.

In many of the solutions, a shared storage is used to overcome the multiple servers limitation. It could be a DB (ActiveRecord), memcached (Session), or some other solution. There is a clean up issue for cases when users abandon a Captcha form without entering a code. For some storage types (like memcached) it is taken care of by the engine itself, while for others a periodic cleaning job is required.

Unbreakable

Image/Code Generation on Request #1

Request #1. The application generates a code and an image and puts them into a Storage. A unique Captcha id (for example a captcha record id from ActiveRecord) is used in an image URL as well as in a hidden form field.
Request #2. Based on the Captcha id, the image is retrieved and deleted from the Storage. The image is sent to a browser via send_file.
Request #3. The entered code is compared with code retrieved from the Storage, based on the captcha id. Regardless of the result, the record is deleted from the Storage.


Code Generation on Request #1, Image Generation on Request #2

Request #1. The application generates a code and puts it into Storage. A unique captcha id is used in an image URL as well as in a hidden form field.
Request #2. Based on the Captcha id, the code is retrieved from Storage and used for generation of an image. The image is being displayed via send_file.
Request #3. The entered code is compared with one retrieved from the Storage based on the Captcha id. Regardless of the result, the record is deleted from the Storage.


Image/Code Generation on Request #2

Request #1. A unique captcha id is generated and is used in an image URL as well as in a hidden form field.
Request #2. The application generates a code and puts it into a Storage. An image, based on the code, is generated and is being showed via send_file.
Request #3. The entered code is compared with one retrieved from the Storage based on the captcha id. Regardless of the result, the record is deleted from the Storage.


Image/Code Generation on Request #1 with Code Hash

Request #1. The application generates a code and puts a hash of it in a hidden field form. An image is generated and is put into a Storage. A unique Captcha id is used in an image URL as well as in a hidden form field.
Request #2. Based on the Captcha id, the image is retrieved and deleted from the Storage. The image is sent to a browser via send_file.
Request #3. The entered code is compared with the hashed one. Regardless of the result, the record is deleted from the Storage.


Potentially Breakable

Some of the potentially breakable solutions are perfectly acceptable for a practical usage since the risk could be very low while benefits of doing image generation offline or having no storage involved could be significant.

Pre-Generated Code/Image

There are several scenarios with code/image pairs pre-generated off-line but none that can sustain an attack based on calculation of an image hash, noting the correct code, and calling the form till the same image comes again. The volume of images and frequency of their regeneration defines the level of risk.


No Storage

It is probebly not possible to come up with a generic solution involving no storage of any kind, while meeting all the requirements. There are some special cases though, with very low security risk. For example, a two-step login process with a standard login screen presented first, and the captcha next:

Request #1: The login form is sent.
Request #2: The application generates a code, encrypts it is with login name and password, and puts the encrypted string in a hidden field form. The image url generates using an encrypted code with some salt (timestamp.)
Request #3. The code is retrieved from the requested url. An image based on the decrypted code is generated, and is displayed via send_file.
Request #4. The entered code is compared with the decrypted and the account is then verified if the correct code was entered.

The obvious attack vector is to crack/steal the key, though in practice it is a well-understood problem to address.


Conclusion

Most likely there are papers on Captcha describing all the aforementioned solutions and many more, so this post could be seen as reinventing the wheel. Still, it was fun thinking about them. It would be great to hear about a perfect generic no-storage solution, feel free to share if you have an idea.

Thursday, April 26, 2007

[PLUGIN RELEASE] ActsAsSecure

Introduction

ActsAsSecure adds an ability to store ActiveRecord model's fields encrypted in a DB. When a model is marked with acts_as_secure, the :binary type fields are recognized as needed to be stored encrypted. The plugin does before_save/after_save/after_find encryption/decryption thus making it transparent for a code using secured models.

The plugin supports a master key approach as well as individual records encryption keys. It does not contain any crypto provider but allows to plug in any external one as long as it supports encrypt/decrypt methods.

The fields are converted to a YAML form before encryption. After description they are restored via YAML.load. Since fields are stored encrypted, the find usage is very limited.

Usage

Master Key Provider Usage

class SecureModel < ActiveRecord::Base
acts_as_secure :crypto_provider => MasterKeyProviderClassOrInstance
end

SecureModel.create()
SecureModel.find(:first)

Individual Keys Provider Usage
class SecureModel < ActiveRecord::Base
acts_as_secure
end

SecureModel.with_crypto_provider(SomeProvider.new(some_param)) { SecureModel.find(:first) }
SecureModel.with_crypto_provider(SomeProvider.new(some_param)) { SecureModel.create() }

Other Options

acts_as_secure :storage_type => :text -- changes the secure storage type from :binary to the supplied one
acts_as_secure :except => [:field1, :field2] -- disables the secure behavior for the :binary type fields in a supplied list


Example

Let's define three models: Fruit (not secure), SecretFruit (uses the master key approach), and UberSecretFruit (uses the individual key approach).

Rot13CryptoProvider represents a master key provider. SaltedRot13CryptoProvider depends on a salt individual for each record.

Models
class Fruit < ActiveRecord::Base
has_one :secret_fruit
has_one :uber_secret_fruit
end

class CreateFruits < ActiveRecord::Migration
def self.up
create_table :fruits do |t|
t.column :name, :string
end
end
end

class Rot13CryptoProvider
class << self
def encrypt(arg)
arg.tr("A-Za-z", "N-ZA-Mn-za-m")
end
alias_method :decrypt, :encrypt
end
end

class SecretFruit < ActiveRecord::Base
acts_as_secure :crypto_provider => Rot13CryptoProvider
belongs_to :fruit
end

class CreateSecretFruits < ActiveRecord::Migration
def self.up
create_table :secret_fruits do |t|
t.column :name, :binary
t.column :fruit_id, :integer
end
end
end

class SaltedRot13CryptoProvider
def initialize(salt)
@salt = salt
end
def encrypt(arg)
@salt + arg.tr("A-Za-z", "N-ZA-Mn-za-m")
end
def decrypt(arg)
arg[@salt.size .. -1].tr("A-Za-z", "N-ZA-Mn-za-m")
end
end

class UberSecretFruit < ActiveRecord::Base
acts_as_secure
belongs_to :fruit
end

class CreateUberSecretFruits < ActiveRecord::Migration
def self.up
create_table :uber_secret_fruits do |t|
t.column :name, :binary
t.column :fruit_id, :integer
end
end
end



Usage

>> f = Fruit.create(:name => 'passion fruit')
>> SecretFruit.create(:name => 'maracuya', :fruit => f)
>> puts f.secret_fruit.name
maracuya
>> secret = readline.chomp
uber_secret
>> crypto_provider = SaltedRot13CryptoProvider.new(secret)
>> UberSecretFruit.with_crypto_provider(crypto_provider) { UberSecretFruit.create(:name => 'Passiflora edulis', :fruit => f) }
>> UberSecretFruit.with_crypto_provider(crypto_provider) { puts f.uber_secret_fruit.name }
Passiflora edulis


DB
> select * from secret_fruits;
+----+---------------+----------+
| id | name | fruit_id |
+----+---------------+----------+
| 1 | --- znenphln | 1 |
+----+---------------+----------+

> select * from uber_secret_fruits;
+----+-----------------------------------+----------+
| id | name | fruit_id |
+----+-----------------------------------+----------+
| 1 | uber_secret--- Cnffvsyben rqhyvf | 1 |
+----+-----------------------------------+----------+


Installation

As plugin:
script/plugin install svn://rubyforge.org/var/svn/acts-as-secure/trunk/vendor/plugins/acts_as_secure


License

ActsAsSecure released under the MIT license.


Support

The plugin RubyForge page is http://rubyforge.org/projects/acts-as-secure

Monday, April 23, 2007

[APPLICATION RELEASE] R::PasteBin

Introduction

Wikipedia pastebin definition:

A Pastebin, Also known as a nopaste, is a web application which allows its users to upload snippets of text, usually samples of source code, for public viewing. It is very popular in IRC channels where pasting large texts is considered bad etiquette.


R::Pastebin

We created this project because we needed a pastebin for internal usage. Being a rails shop, we wanted one any developer could enhance. We looked around for rails-based pastebins and even though there is a great Attachr available, we could not find anything to install internally. Since pastebins are trivial, we just implemented our own in no time, thanks to Rails and CodeRay. It is very basic but it works fine for us so we decided to release it.


Features

Code highlights for Ruby, RHTML, HTML, Plaintext, and C. The list of supported languages is likely to grow.

An optional irc notification (controlled by config/irc.yml)


Dependencies

rpastebin uses the coderay gem for syntax highlighting. The latest version can be installed via gem install coderay -rs http://rd.cYcnus.de/coderay

The IRC client needs the Ruby-IRC gem.

The rails code is 1.2.3 so if you have to stay on 1.1.6 (as we are), just rake rails:freeze:edge in the project directory.

The search feature utilizes fulltext so it is only compatible with the MySQL MyISAM engine.


Home

http://rubyforge.org/projects/rpastebin/


License

Distributed under MIT License