Flatulent-0.0.3


#1

(the demo has been updated too)

NAME

flatulent : CAPTCHA for FIGLET.

SYNOPSIS

the flatulent gem provides brain dead simple to use, but
internally cunning,
ascii art (figlet) captcha for ruby.

URI

http://codeforpeople.com/lib/ruby
http://rubyforge.org/projects/codeforpeople

HOW DO I GET FLATULENT?

gem install flatulent

HISTORY
0.0.3:
- following are now all equivalent when posting (thanks botp)

     0==o==O==Q (zero, oh's, and queue)
     l==l       (one and el)
     2==z==Z    (two and z's)
     5==s==S    (5 and s's)

 - random horizontal and vertical displacement of each char

 - vastly improved background noise based on figlet char shapes

 - inputs are case sensitive (thanks john joyce, chris carter)

 - expanded rails examples

0.0.2

 - ajax gets stinky: Flatulent.ajax!  the result of this new

addition is
that the captcha itself doesn’t appear in the source file at all

 - blowfish encoding for timebomb and captcha fields

 - auto server key configuration using hostname and mac address

 - improved noise algorithm

 - improved character placement (chars shared edges to make

ocr’ing harder)

0.0.1

 - initial version

RAILS EXAMPLES

REGULAR METHOD (LESS SECURE):

 def controller_action
   if params.has_key? 'flatulent'
     Flatulent.validate! params
   end

   render :inline => <<-html
     <html><body>
       #{ Flatulent.form }
     </body></html>
   html
 end

AJAX METHOD (MORE SECURE):

 def controller_action
   if params.has_key? 'flatulent'
     Flatulent.validate! params
   end

   render :inline => <<-html
     <html>
       <head> <%= javascript_include_tag 'prototype' %> </head>
       <body>
         <form action='./' method='post'>
           <%= Flatulent.ajax %>
           <input type='submit' name='submit' value='submit' />
         </form>
       </body>
     </html>
   html
 end

DOCS

see source in ./lib/*
see the example rails project in ./rails

ONLINE SAMPLES

http://drawohara.tumblr.com/post/4791838
http://drawohara.tumblr.com/post/4944987
http://drawohara.tumblr.com/post/4968766

ONLINE DEMO OF AJAX METHOD

http://fortytwo.merseine.nu:3000/flatulent/ajax – try to break it!

enjoy.

-a


#2

On 7/5/07, ara.t.howard removed_email_address@domain.invalid wrote:

ONLINE DEMO OF AJAX METHOD

http://fortytwo.merseine.nu:3000/flatulent/ajax – try to break it!

I can only guess 1 in 5 of the “images” – that is one way to cut down
on spammers :slight_smile:
pth


#3

On Jul 5, 2007, at 7:51 AM, Patrick H. wrote:

On 7/5/07, ara.t.howard removed_email_address@domain.invalid wrote:

ONLINE DEMO OF AJAX METHOD

http://fortytwo.merseine.nu:3000/flatulent/ajax – try to
break it!

I can only guess 1 in 5 of the “images” – that is one way to cut down
on spammers :slight_smile:

Wow, no kidding. Here are a couple it tried on me:

http://grayproductions.net/ruby/images/scary_captcha.png

Yikes!

James Edward G. II


#4

On Jul 5, 2007, at 6:51 AM, Patrick H. wrote:

I can only guess 1 in 5 of the “images” – that is one way to cut down
on spammers :slight_smile:
pth

yeah - it’s a redering bug but i can’t figure our what causes it -
thanks!

-a


#5

Patrick H. wrote the following on 05.07.2007 14:51 :

On 7/5/07, ara.t.howard removed_email_address@domain.invalid wrote:

ONLINE DEMO OF AJAX METHOD

http://fortytwo.merseine.nu:3000/flatulent/ajax – try to break it!

I can only guess 1 in 5 of the “images” – that is one way to cut down
on spammers :slight_smile:
pth

I can confirm there are sometimes problems with the output (on Firefox
2.0.0.4). 1/4 of the time no recognizable character is shown.

Other notes:

I don’t like the obstrusive Ajax feature at all (I use NoScript…):
what’s the benefit? Spammers trying to get around captcha can easily
make an extra step and make XmlHTTPRequests too… From what I
understand, there’s at least a way to generate pure HTML, but I’d still
like to understand why AJAX is an option.

I’m not sure why there are nested span in the captcha :

 

?! Is it to accomodate rendering bugs?

There are   in a

 with style=’…,white-space:pre,… '. Seems
the author really wants to be sure that spaces can not be rendered
with newlines…

Lionel


#6

The method used in this captcha is very is to break. In fact I can
solve
the captchas 6 times as fast as it takes to generate them (locally)
in
only 63 lines of code. I do this by generating a regexp for each
possible
character. As the characters don’t get damaged by the noise (as they
get
in most image bases captchas) this works all of the time.

$ ruby benchmark.rb
user system total real
generate: 0.160000 0.020000 0.180000 ( 0.192005)
setup: 0.030000 0.000000 0.030000 ( 0.025381)
break: 0.010000 0.000000 0.010000 ( 0.010908)
generate 200: 12.100000 1.000000 13.100000 ( 13.125787)
break 200: 2.050000 0.100000 2.150000 ( 2.152749)
$ wc -l deflatulent.rb /usr/local/lib/ruby/gems/1.8/gems/
flatulent-0.0.3/lib/flatulent.rb
63 deflatulent.rb
604 /usr/local/lib/ruby/gems/1.8/gems/flatulent-0.0.3/lib/
flatulent.rb
667 total
$ cat benchmark.rb
require ‘deflatulent’
require ‘flatulent’
require ‘benchmark’

defl = html = code = nil
pairs = Array.new(200)

GC.disable

Benchmark.bm(13) do |x|
x.report(“generate:”) { flat = Flatulent.new; html = flat.form; code
= flat.string }
x.report(“setup:”) { defl = Deflatulent.new }
x.report(“break:”) { raise unless defl.deflatulent(html) ==
code }

x.report(“generate 200:”) { 200.times{|index| flat = Flatulent.new;
pairs[index] = [flat.form,flat.string] } }
x.report(“break 200:”) { pairs.map{|(html,code)| raise unless
defl.deflatulent(html) == code } }

end
$ cat deflatulent.rb
require ‘flatulent’

class Deflatulent

def initialize font=“big”
font = Text::Figlet::Font.new(File.join(Flatulent.fontdir,font
+".flf"))
typesetter = Text::Figlet::Typesetter.new font
letters = (‘A’…‘Z’).to_a + (‘1’…‘9’).to_a
@lines_array = letters.map{|letter| [letter,
gen_figlet_lines_array(typesetter[letter])] }
end

def deflatulent string
if string =~ /

(.*?)</
pre>/m
string = $1
  [[/<\/?span>/,''],["&nbsp;"," "],["<br>","\n"],["&lt;","<"],

[">",">"],[""",’"’],["&","&"]].each do |args|
string.gsub!(*args)
end
end

width = string.index("\n")
string.tr!("\n","")
solution = []

@lines_array.each do |(letter,(length,lines))|

  re = "(?="
  lines.each{|line| re << line << ".{#{width-length}}" }
  re << ")"

  string.scan(Regexp.new(re, Regexp::MULTILINE)) do
    solution[$~.begin(0) % width] = letter
  end
end

solution.join

end

private
def gen_figlet_lines_array string
lines = string.split("\n")
lines.shift while lines.first.strip.empty?
lines.pop while lines.last.strip.empty?

lines.each{|e|e[0,1]=""} while lines.all?{|e|e[0,1]==' '}
lines.each{|e|e[-1,1]=""} while lines.all?{|e|e[-1,1]==' '}

[lines[0].length,lines.map{|e|e.split('').map{|q|(q == ' ' ? '.' :

Regexp.escape(q))}.join}]
end

end

if FILE == $0
defl = Deflatulent.new(ARGV[0] || “big”)
loop do
input = “”
while line=gets and not line.chomp.empty?
input << line
end
puts defl.deflatulent(input)
break unless line
end
end


#7

On Jul 6, 2007, at 12:29 PM, removed_email_address@domain.invalid wrote:

The method used in this captcha is very is to break. In fact I can
solve
the captchas 6 times as fast as it takes to generate them (locally)
in
only 63 lines of code. I do this by generating a regexp for each
possible
character. As the characters don’t get damaged by the noise (as they
get
in most image bases captchas) this works all of the time.

hmmm - not for me?

cfp:~ > ruby a.rb
user system total real
generate: 0.140000 0.020000 0.170000 ( 0.178928)
setup: 0.020000 0.000000 0.020000 ( 0.022138)
break: Flatulent.version : 0.0.4
a.rb:63: failed on attempt 1 (RuntimeError)
from /opt/local/lib/ruby/1.8/benchmark.rb:293:in measure' from /opt/local/lib/ruby/1.8/benchmark.rb:377:inreport’
from a.rb:63
from /opt/local/lib/ruby/1.8/benchmark.rb:177:in benchmark' from /opt/local/lib/ruby/1.8/benchmark.rb:207:inbm’
from a.rb:59

cfp:~ > cat a.rb
require ‘flatulent’
require ‘benchmark’
require ‘flatulent’

class Deflatulent
def initialize font=“big”
font = Text::Figlet::Font.new(File.join(Flatulent.fontdir,font
+".flf"))
typesetter = Text::Figlet::Typesetter.new font
letters = (‘A’…‘Z’).to_a + (‘1’…‘9’).to_a
@lines_array = letters.map{|letter| [letter,
gen_figlet_lines_array(typesetter[letter])] }
end

def deflatulent string
if string =~ /

(.*?)<
/ pre>/m
string = $1
[[/</?span>/,’’],[" “,” “],[”
","\n"],["<","<"],
[">",">"],[""",’"’],["&","&"]].each do |args|
string.gsub!(*args)
end
end
 width = string.index("\n")
 string.tr!("\n","")
 solution = []

 @lines_array.each do |(letter,(length,lines))|

   re = "(?="
   lines.each{|line| re << line << ".{#{width-length}}" }
   re << ")"

   string.scan(Regexp.new(re, Regexp::MULTILINE)) do
     solution[$~.begin(0) % width] = letter
   end
 end

 solution.join

end

private
def gen_figlet_lines_array string
lines = string.split("\n")
lines.shift while lines.first.strip.empty?
lines.pop while lines.last.strip.empty?

 lines.each{|e|e[0,1]=""} while lines.all?{|e|e[0,1]==' '}
 lines.each{|e|e[-1,1]=""} while lines.all?{|e|e[-1,1]==' '}

 [lines[0].length,lines.map{|e|e.split('').map{|q|(q == ' ' ?

‘.’ : Regexp.escape(q))}.join}]
end
end

defl = html = code = nil
pairs = Array.new(200)

GC.disable
i = 0

begin
Benchmark.bm(13) do |x|
i += 1
x.report(“generate:”) { flat = Flatulent.new; html = flat.form;
code = flat.string }
x.report(“setup:”) { defl = Deflatulent.new }
x.report(“break:”) { raise “failed on attempt #{ i }” unless
defl.deflatulent(html) == code }
x.report(“generate 200:”) { 200.times{|index| flat =
Flatulent.new; pairs[index] = [flat.form,flat.string] } }
x.report(“break 200:”) { pairs.map{|(html,code)| raise unless
defl.deflatulent(html) == code } }
end
ensure
puts “Flatulent.version : #{ Flatulent.version }”
end

nevertheless, i’m not for one second claiming flatulent is ready for
prime time. however, i will state that i think it’s quite a bit of
work if you use it in the intended way, which is for the html to make
an ajax call to get the flatulent source because this make said
source available only to javascript. no doubt someone could crack it
from there, but the latest version adds vertical and horizontal
offset to each char. my version is turning that source into a png.
anyhow, the attention is welcome - but next time send a patch! :wink:

-a


#8

On Jul 7, 2007, at 11:25 AM, Chris C. wrote:

Ara,
That is because you set defl and flat inside a block, without setting
the variables to nil before the block is executed, so they stay
existing for the actual decode stage.

??

defl = html = code = nil ### irrelevant

pairs = Array.new(200)

GC.disable
i = 0

begin
Benchmark.bm(13) do |x|
i += 1
defl = html = code = nil ### irrelevant
x.report(“generate:”) { flat = Flatulent.new; html = flat.form;
code = flat.string }
x.report(“setup:”) { defl = Deflatulent.new }
x.report(“break:”) { raise “failed on attempt #{ i }” unless
defl.deflatulent(html) == code }
x.report(“generate 200:”) { 200.times{|index| flat =
Flatulent.new; pairs[index] = [flat.form,flat.string] } }
x.report(“break 200:”) { pairs.map{|(html,code)| raise unless
defl.deflatulent(html) == code } }
end
ensure
puts “Flatulent.version : #{ Flatulent.version }”
end

it fails on the very first attempt:

cfp:~ > ruby a.rb
user system total real
generate: 0.140000 0.020000 0.170000 ( 0.179983)
setup: 0.020000 0.000000 0.020000 ( 0.022315)
break: Flatulent.version : 0.0.4
a.rb:64: failed on attempt 1 (RuntimeError)
from /opt/local/lib/ruby/1.8/benchmark.rb:293:in measure' from /opt/local/lib/ruby/1.8/benchmark.rb:377:inreport’
from a.rb:64
from /opt/local/lib/ruby/1.8/benchmark.rb:177:in benchmark' from /opt/local/lib/ruby/1.8/benchmark.rb:207:inbm’
from a.rb:59

cheers.

-a


#9

On 7/7/07, ara.t.howard removed_email_address@domain.invalid wrote:

get
setup: 0.020000 0.000000 0.020000 ( 0.022138)

 letters = ('A'..'Z').to_a + ('1'..'9').to_a
     string.gsub!(*args)
   lines.each{|line| re << line << ".{#{width-length}}" }

private
end
i += 1
ensure
offset to each char. my version is turning that source into a png.

Ara,
That is because you set defl and flat inside a block, without setting
the variables to nil before the block is executed, so they stay
existing for the actual decode stage.


#10

On 7 Jul., 19:17, “ara.t.howard” removed_email_address@domain.invalid wrote:

def deflatulent string
if string =~ /

(.*?)<
/ pre>/m ########## there is a space before pre
string = $1
[[/</?span>/,’’],[" “,” “],[”
","\n"],["<","<"],
[">",">"],[""",’"’],["&","&"]].each do |args|
string.gsub!(*args)
end
end

It seems that google groups added line breaks inside the regexp that
somehow
turned into spaces for you… try it without the space before pre…
if that
doesn’t work I can upload my code somewhere…


#11

On Jul 7, 2007, at 11:41 AM, removed_email_address@domain.invalid wrote:

     string.gsub!(*args)

my version doesn’t seem to have line breaks - here it is:

http://drawohara.tumblr.com/post/5164285

thanks for having a go - i’m not sure this can be made to work, but
i’m still very interested in an ImageMagick-less captcha system.

cheers

-a


#12

On Jul 7, 2007, at 11:42 AM, Chris C. wrote:

Huh, I guess I am blind then…

i wouldn’t say that :wink:

did i understand you correctly? i wasn’t sure i did…

cheers.

-a


#13

On 7 Jul., 19:52, “ara.t.howard” removed_email_address@domain.invalid wrote:

my version doesn’t seem to have line breaks - here it is:

http://drawohara.tumblr.com/post/5164285

That version still has an additional space in the html stripping
regexp. I think it was inserted by google groups when I pasted that
message (because it displays a line break here). Anyway here is a
version without any additional characters.
http://pastie.caboo.se/76886
To avoid this cracking method one could change at least one of the
characters of each letter. This would break simple regexp attacks. But
I still think this wouldn’t be too difficult to break. But even if
there is a simple way to crack a captcha it will stop most of the
spambots so I’m not saying this is something useless.


#14

On Jul 7, 2007, at 12:31 PM, removed_email_address@domain.invalid wrote:

That version still has an additional space in the html stripping
regexp. I think it was inserted by google groups when I pasted that
message (because it displays a line break here). Anyway here is a
version without any additional characters.
http://pastie.caboo.se/76886
To avoid this cracking method one could change at least one of the
characters of each letter. This would break simple regexp attacks. But
I still think this wouldn’t be too difficult to break. But even if
there is a simple way to crack a captcha it will stop most of the
spambots so I’m not saying this is something useless.

cool. i’ve updated here

http://drawohara.tumblr.com/post/5164285

but it’s still failing (yay!)

note the new output - sample at bottom of above post. it’s much harder.

fun stuff!

-a


#15

On 7 Jul., 20:43, “ara.t.howard” removed_email_address@domain.invalid wrote:

cool. i’ve updated here

http://drawohara.tumblr.com/post/5164285

but it’s still failing (yay!)

note the new output - sample at bottom of above post. it’s much harder.

Testing with an updated flatulent version is cheating :wink: … but I
love challenge, so here is an updated version successfully breaking
flatulent 0.0.4. 9 times faster than generating.
http://pastie.caboo.se/76923
This version should be able to break 0.0.3 and 0.0.4.


#16

On 7/7/07, ara.t.howard removed_email_address@domain.invalid wrote:

 x.report("generate:") { flat = Flatulent.new; html = flat.form;

puts “Flatulent.version : #{ Flatulent.version }”
setup: 0.020000 0.000000 0.020000 ( 0.022315)
cheers.

-a

Huh, I guess I am blind then…


#17

On Jul 7, 2007, at 3:28 PM, removed_email_address@domain.invalid wrote:

Testing with an updated flatulent version is cheating :wink: … but I
love challenge,

have a whack at this then

http://drawohara.tumblr.com/post/5126190

:wink:

so here is an updated version successfully breaking
flatulent 0.0.4. 9 times faster than generating.
http://pastie.caboo.se/76923
This version should be able to break 0.0.3 and 0.0.4.

awesome. i’ll post it on my blog later. guess i’ll have to make an
image after all ;-(

-a


#18

On 7/8/07, Patrick H. removed_email_address@domain.invalid wrote:

On 7/7/07, ara.t.howard removed_email_address@domain.invalid wrote:

awesome. i’ll post it on my blog later. guess i’ll have to make an
image after all ;-(

I think if you can kern the letters into each other, finding some way
to make them overlap, much of this approach (regex) will be defeated.
Couple that with using more fonts will increase the difficulty of
solving the problem.

i agree.
ara, i’ve seen your raptcha. how about something like that but in text
mode. The pixel would represent a character (that varies). it’s like
converting from bmp to ascii art. Your text captcha must be able to
display on text browsers, otherwise it has no use for me like most
other captchas. yes, i’m usually a text browser :wink:


#19

On Jul 8, 2007, at 8:07 AM, Patrick H. wrote:

I think if you can kern the letters into each other, finding some way
to make them overlap, much of this approach (regex) will be defeated.
Couple that with using more fonts will increase the difficulty of
solving the problem.

i’ve played with quite a few things, but not overlapping the the
letters. i’ll give it a shot.

thanks!

-a


#20

On 7/7/07, ara.t.howard removed_email_address@domain.invalid wrote:

awesome. i’ll post it on my blog later. guess i’ll have to make an
image after all ;-(

I think if you can kern the letters into each other, finding some way
to make them overlap, much of this approach (regex) will be defeated.
Couple that with using more fonts will increase the difficulty of
solving the problem.

pth