RFuzz is an HTTP client library that you can use in combination with a random junk generator to conduct fuzzing tests against any HTTP server (or web app). It's not limited to fuzzing, as the HTTP client can work as a Net/HTTP replacement, and you can easily just make regular requests.
Fuzzing is where you try to give a web application lots of randomly generated unexpected inputs in an attempt to break it and find new areas to write unit tests. It compliments other testing methods. See http://en.wikipedia.org/wiki/Fuzzing for additional information.
This is the library I'll be using in my RubyConf talk.
CHANGES
The 0.7 release fixes a bad bug in the request headers, fixes a rare chunked encoding error, and adds a new example that uses the fresh RFuzz::Browser class. Look at examples/cl_watcher.rb for a simple script that I'm using to watch apartment listings on craigslist.
This release *also supports win32 precompiled binaries*.
INSTALL
Everyone should be able to install it with:
sudo gem install rfuzz
Or on window just "gem install rfuzz". Windows people pick the win32 version as it's the one that's precompiled.
class RESTClient def initialize(host,port, base="") @host, @port = host, port @base = base @client = RFuzz::HttpClient.new(host,port) end
def target_uri(symbol) uri = @base + "/" + symbol.to_s.tr("_","/") end
def method_missing(symbol, *args) res = @client.get(target_uri(symbol), :query => (args[0] || {})) raise_error_if(res.http_status != "200", "Invalid Status #{res.http_status} from server #{@host}:#{@port}") return REXML::Document.new(res.http_body).root end
def raise_error_if(test, msg) raise RESTClientError.new(msg) if test end
end
This example just takes simple:
client.users_find :name => "joe"
And translates them to:
GET /users/find?name=joe
Requests on the fly and then returns an REXML docroot.
> Or on window just "gem install rfuzz". Windows people pick the win32 > version as it's the one that's precompiled.
I just ran 'gem install rfuzz' on my WinXP box, and was given the choice of installs. Selecting the first one returned a 404:
Select which gem to install for your platform (i386-mswin32) 1. rfuzz 0.7 (mswin32) 2. rfuzz 0.7 (ruby) 3. rfuzz 0.6 (ruby) 4. Cancel installation > 1 ERROR: While executing gem ... (OpenURI::HTTPError) 404 Not Found
I checked the rubyforge.org rfuzz download page, which lists the mswin32 gem, but clicking the link for a manual download also gives a 404
On Sat, Aug 05, 2006 at 06:40:39AM +0900, James Britt wrote: > >Or on window just "gem install rfuzz". Windows people pick the win32 > >version as it's the one that's precompiled.
> I just ran 'gem install rfuzz' on my WinXP box, and was given the choice > of installs. Selecting the first one returned a 404: [...]
> I checked the rubyforge.org rfuzz download page, which lists the mswin32 > gem, but clicking the link for a manual download also gives a 404
h = Mongrel::HttpServer.new('0.0.0.0', '8080') h.register('/', Mongrel::DirHandler.new('.')) h.run
def do_download(h, path) h.get(path) # quack! end
n = 1000
Benchmark.bmbm(20) do |b| u = URI.parse('http://localhost:8080/rfuzz_test.rb') busted = Net::HTTP.new(u.host, u.port) b.report('net/http'){for i in 0..n; do_download(busted, u.path); end} hotness = RFuzz::HttpClient.new(u.host, u.port) b.report('rfuzz'){for i in 0..n; do_download(hotness, u.path); end} end
On Sat, 2006-08-05 at 16:07 +0900, Alex Young wrote: > Zed Shaw wrote: > In case anybody's wondering... <snip> > Rehearsal ------------------------------------------------------- > net/http 7.720000 1.470000 9.190000 ( 9.195841) > rfuzz 3.040000 1.070000 4.110000 ( 4.119992) > --------------------------------------------- total: 13.300000sec
> user system total real > net/http 7.930000 1.370000 9.300000 ( 9.298725) > rfuzz 2.950000 1.130000 4.080000 ( 4.073578)
Yeah it should be faster just by virtue of having very little to it and using a C parser. Keep in mind that rfuzz--unlike mongrel--isn't really designed for speed but more for letting you easily hit web servers with any kind of request. Another thing some folks don't notice is that all of the HttpClient requests are "data driven". That means you can power RFuzz blindly from a YAML file full of strings and hashes.
Otherwise net/http has a bunch more features, but I'll slowly add them as I need them. I think SSL is next on the list.
> That doesn't include those speedups for net/http that went through the > list a couple of weeks back, though.
I'd also be curious for the same test run with the RFuzz call being done inside a Timeout block. RFuzz doesn't do any of that (bare metal), and I know Timeout blocks fire up an extra thread and eat up a bit more CPU. I'm betting that putting RFuzz inside Timeout makes it about the same speed at net/http.
>> user system total real >>net/http 7.930000 1.370000 9.300000 ( 9.298725) >>rfuzz 2.950000 1.130000 4.080000 ( 4.073578)
> Yeah it should be faster just by virtue of having very little to it and > using a C parser. Keep in mind that rfuzz--unlike mongrel--isn't really > designed for speed but more for letting you easily hit web servers with > any kind of request. Another thing some folks don't notice is that all > of the HttpClient requests are "data driven". That means you can power > RFuzz blindly from a YAML file full of strings and hashes.
> Otherwise net/http has a bunch more features, but I'll slowly add them > as I need them. I think SSL is next on the list.
>>That doesn't include those speedups for net/http that went through the >>list a couple of weeks back, though.
> I'd also be curious for the same test run with the RFuzz call being done > inside a Timeout block. RFuzz doesn't do any of that (bare metal), and > I know Timeout blocks fire up an extra thread and eat up a bit more CPU. > I'm betting that putting RFuzz inside Timeout makes it about the same > speed at net/http.
m = Mongrel::HttpServer.new('0.0.0.0', '8080') m.register('/', Mongrel::DirHandler.new('.')) m.run
def do_download(h, path) h.get(path) end
def do_timeout_download(h, path) timeout(30){ h.get(path) } end
n = 1000
Benchmark.bmbm(20) do |b| u = URI.parse('http://localhost:8080/rfuzz_test.rb') h = Net::HTTP.new(u.host, u.port) b.report('net/http'){ for i in 0..n; do_download(h, u.path); end } r = RFuzz::HttpClient.new(u.host, u.port) b.report('rfuzz'){ for i in 0..n; do_download(r, u.path); end } b.report('rfuzz+timeout'){ for i in 0..n; do_timeout_download(r, u.path); end } end Rehearsal ------------------------------------------------------- net/http 8.000000 1.380000 9.380000 ( 9.609907) rfuzz 3.110000 1.090000 4.200000 ( 4.213084) rfuzz+timeout 3.660000 1.270000 4.930000 ( 4.933268) --------------------------------------------- total: 18.510000sec
user system total real net/http 8.060000 1.480000 9.540000 ( 9.620967) rfuzz 3.090000 1.090000 4.180000 ( 5.725728) rfuzz+timeout 3.680000 1.230000 4.910000 ( 4.991043)
But then again, that's not what Net::HTTP's actually doing - it puts a timeout around every single rbuf_fill call, so by default it has to spawn a new thread for every 1K of data it intends to receive. I'm sure there's a damn good reason for that, but it's a reason I don't think I'd have spotted if I was writing it. That shouldn't be a problem on this test, though - the file it's downloading is only 715 bytes. I'll try the test with a bigger file and see what I can see.
Alex Young wrote: > Zed Shaw wrote: <snip> >> I'd also be curious for the same test run with the RFuzz call being done >> inside a Timeout block. RFuzz doesn't do any of that (bare metal), and >> I know Timeout blocks fire up an extra thread and eat up a bit more CPU. >> I'm betting that putting RFuzz inside Timeout makes it about the same >> speed at net/http.
> Not quite: <snip> > But then again, that's not what Net::HTTP's actually doing - it puts a > timeout around every single rbuf_fill call, so by default it has to > spawn a new thread for every 1K of data it intends to receive. I'm sure > there's a damn good reason for that, but it's a reason I don't think I'd > have spotted if I was writing it.
Ok, having actually stopped and thought about it for a second, I can see why they've done that.
> That shouldn't be a problem on this > test, though - the file it's downloading is only 715 bytes. I'll try > the test with a bigger file and see what I can see.