Forum: Ruby flatulent-0.0.3

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-05 08:42
(Received via mailing list)
(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
2c51fec8183a5d21c4e11b430beabb47?d=identicon&s=25 Patrick Hurley (Guest)
on 2007-07-05 14:54
(Received via mailing list)
On 7/5/07, ara.t.howard <ara.t.howard@gmail.com> 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 :-)
pth
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2007-07-05 15:15
(Received via mailing list)
On Jul 5, 2007, at 7:51 AM, Patrick Hurley wrote:

> On 7/5/07, ara.t.howard <ara.t.howard@gmail.com> 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 :-)

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

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

Yikes!

James Edward Gray II
Bef7ff8a0537495a1876ffebdc9f8e51?d=identicon&s=25 Lionel Bouton (Guest)
on 2007-07-05 15:17
(Received via mailing list)
Patrick Hurley wrote the following on 05.07.2007 14:51 :
> On 7/5/07, ara.t.howard <ara.t.howard@gmail.com> 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 :-)
> 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 :

<span><span><span><span><span><span><span>&nbsp;</span></span></span></span></span></span></span>

?! Is it to accomodate rendering bugs?

There are &nbsp; in a <pre> with style='...,white-space:pre,... '. Seems
the author *really* wants to be sure that spaces can not be rendered
with newlines...

Lionel
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-05 17:06
(Received via mailing list)
On Jul 5, 2007, at 6:51 AM, Patrick Hurley wrote:

> I can only guess 1 in 5 of the "images" -- that is one way to cut down
> on spammers :-)
> pth


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

-a
C61aed46b48311ca90c2d4e9a3b10757?d=identicon&s=25 jannis@harderweb.de (Guest)
on 2007-07-06 20:31
(Received via mailing list)
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 id='[a-zA-Z0-9_-]+_element' style='.*?'>(.*?)<\/
pre>/m
      string = $1

      [[/<\/?span>/,''],["&nbsp;"," "],["<br>","\n"],["&lt;","<"],
["&gt;",">"],["&quot;",'"'],["&amp;","&"]].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
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-07 19:18
(Received via mailing list)
On Jul 6, 2007, at 12:29 PM, jannis@harderweb.de 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:in `report'
         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:in `bm'
         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 id='[a-zA-Z0-9_-]+_element' style='.*?'>(.*?)<
\/ pre>/m
       string = $1
       [[/<\/?span>/,''],["&nbsp;"," "],["<br>","\n"],["&lt;","<"],
["&gt;",">"],["&quot;",'"'],["&amp;","&"]].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!  ;-)

-a
96931bfe0c2948f47a98e15ae52e5637?d=identicon&s=25 Chris Carter (cdcarter)
on 2007-07-07 19:26
(Received via mailing list)
On 7/7/07, ara.t.howard <ara.t.howard@gmail.com> 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.
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-07 19:36
(Received via mailing list)
On Jul 7, 2007, at 11:25 AM, Chris Carter 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:in `report'
         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:in `bm'
         from a.rb:59


cheers.


-a
C61aed46b48311ca90c2d4e9a3b10757?d=identicon&s=25 jannis@harderweb.de (Guest)
on 2007-07-07 19:42
(Received via mailing list)
On 7 Jul., 19:17, "ara.t.howard" <ara.t.how...@gmail.com> wrote:
>    def deflatulent string
>      if string =~ /<pre id='[a-zA-Z0-9_-]+_element' style='.*?'>(.*?)<
> \/ pre>/m   ########## there is a space before pre
>        string = $1
>        [[/<\/?span>/,''],["&nbsp;"," "],["<br>","\n"],["&lt;","<"],
> ["&gt;",">"],["&quot;",'"'],["&amp;","&"]].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...
96931bfe0c2948f47a98e15ae52e5637?d=identicon&s=25 Chris Carter (cdcarter)
on 2007-07-07 19:45
(Received via mailing list)
On 7/7/07, ara.t.howard <ara.t.howard@gmail.com> 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...
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-07 19:54
(Received via mailing list)
On Jul 7, 2007, at 11:41 AM, jannis@harderweb.de 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
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-07 19:56
(Received via mailing list)
On Jul 7, 2007, at 11:42 AM, Chris Carter wrote:

> Huh, I guess I am blind then...

i wouldn't say that ;-)

did i understand you correctly?  i wasn't sure i did...

cheers.

-a
C61aed46b48311ca90c2d4e9a3b10757?d=identicon&s=25 jannis@harderweb.de (Guest)
on 2007-07-07 20:32
(Received via mailing list)
On 7 Jul., 19:52, "ara.t.howard" <ara.t.how...@gmail.com> 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.
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-07 20:45
(Received via mailing list)
On Jul 7, 2007, at 12:31 PM, jannis@harderweb.de 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
C61aed46b48311ca90c2d4e9a3b10757?d=identicon&s=25 jannis@harderweb.de (Guest)
on 2007-07-07 23:29
(Received via mailing list)
On 7 Jul., 20:43, "ara.t.howard" <ara.t.how...@gmail.com> 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 ;) .... 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.
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-08 00:05
(Received via mailing list)
On Jul 7, 2007, at 3:28 PM, jannis@harderweb.de wrote:

>
> Testing with an updated flatulent version is cheating ;) .... but I
> love challenge,

have a whack at this then

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

;-)


> 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
2c51fec8183a5d21c4e11b430beabb47?d=identicon&s=25 Patrick Hurley (Guest)
on 2007-07-08 16:08
(Received via mailing list)
On 7/7/07, ara.t.howard <ara.t.howard@gmail.com> 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
797ef431a5e1295b56c08e1db4c8d2df?d=identicon&s=25 botp (Guest)
on 2007-07-08 17:01
(Received via mailing list)
On 7/8/07, Patrick Hurley <phurley@gmail.com> wrote:
> On 7/7/07, ara.t.howard <ara.t.howard@gmail.com> 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 ;)
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-09 16:41
(Received via mailing list)
On Jul 8, 2007, at 8:07 AM, Patrick Hurley 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
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 ara.t.howard (Guest)
on 2007-07-09 16:42
(Received via mailing list)
On Jul 8, 2007, at 9:00 AM, botp wrote:

> 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 ;)


i like that idea but at a loss as to how to do it without requiring a
complete image library, which was one of the goals of flatulent,
besides being funny, in the first place.

cheers.

-a
This topic is locked and can not be replied to.