[ANN] flatulent-0.0.3

A

ara.t.howard

(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
 
B

Brad Phelan

ara.t.howard said:
(the demo has been updated too)

Demo seems broken to me

http://fortytwo.merseine.nu:3000/flatulent

returns gibberish when viewed in firefox. However when I copy paste the
text I get
____
|___ \
__) | __ _ _____
__ __ |__ < ______ | / __ \
\ \ / / ___) | | ____| | |_ | |
\ \_/ / |____/ | | |__ _/ _| | | |_
\ / _|___ \ |_|__| |_
| | \_| ___)||) | \____/
|_| \|____/ | _ _ _
_/_ \_ \\ | /_
|_ _ \ _ | /
| \)| | | _


Wierd?????

_ _____ _
_______ _ | | __ \ _ ___
| ____| _ | |__) | |__ \
| |__ __ _ | | ___/ / | \ ) |
| __| | | | | | | /\ / /
| |____ | | _ | |_| | ( _ / /_
|______|\ _| | _| __ |____|
||) | | |____ | | |
__ |______|
| | __ |
_ _ / \ _
_ |_ //

I tried it a few times and got the same result.
Viewing in the web browser breaks but copy
and paste works well.
 
L

Lionel Bouton

Patrick Hurley wrote the following on 05.07.2007 14:51 :
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
 
A

ara.t.howard

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
 
J

jannis

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
 
A

ara.t.howard

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
 
C

Chris Carter

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

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.
 
A

ara.t.howard

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
 
J

jannis

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...
 
C

Chris Carter

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

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

ara.t.howard

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...

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
 
J

jannis

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.
 
A

ara.t.howard

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
 
A

ara.t.howard

P

Patrick Hurley

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
 
B

botp

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top