FriendsFeedMe application is build around RSS feeds and relations between people. As result we need to process a lot of OPML files. My first approach to this task, was build web frontend (Rails powered application) and some library to process data uploaded via frontend, and display results via web.
Processing was done in fixed intervals (script called from
cron). Why that way? Since it was simplest approach which allowed to automate whole process. But it has several drawbacks: user have to wait before he will see results, it is hard to illustrate task progress, so I had to rewrite this part in some point in time. The time has come.
Natural was to use DRb (Distributed Ruby), build independent server to process data taken from DB, and from Rails application notice that processing is needed (new OPML uploaded). Using DRb is pretty easy and I don’t want to talk about how to use it this time.
To DRb server You can connect via TCP or UNIX sockets. Since our setup is currently on one host I prefer socket version – opening another TCP port in listening state without real need is something I would avoid.
You can imagine my surprise, when after I have started DRb server connected to socket I discovered this:
$ netstat -an | grep LISTE tcp4 0 0 *.61841 *.* LISTEN [cut] $ telnet localhost 61841 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. [ENTER] [ENTER] :DRb::DRbConnError:bt[ "5/usr/local/lib/ruby/1.8/drb/drb.rb:573:in `load'"=/usr/local/lib/ruby/1.8/drb/drb.rb:612:in `recv_request'"=/usr/local/lib/ruby/1.8/drb/drb.rb:911:in `recv_request'"B/usr/local/lib/ruby/1.8/drb/drb.rb:1530:in `init_with_client'"?/usr/local/lib/ruby/1.8/drb/drb.rb:1542:in `setup_message'"9/usr/local/lib/ruby/1.8/drb/drb.rb:1494:in `perform'";/usr/local/lib/ruby/1.8/drb/drb.rb:1589:in `main_loop'"6/usr/local/lib/ruby/1.8/drb/drb.rb:1585:in `loop'";/usr/local/lib/ruby/1.8/drb/drb.rb:1585:in `main_loop'"7/usr/local/lib/ruby/1.8/drb/drb.rb:1581:in `start'";/usr/local/lib/ruby/1.8/drb/drb.rb:1581:in `main_loop'"5/usr/local/lib/ruby/1.8/drb/drb.rb:1430:in `run'"7/usr/local/lib/ruby/1.8/drb/drb.rb:1427:in `start'"5/usr/local/lib/ruby/1.8/drb/drb.rb:1427:in `run'"</usr/local/lib/ruby/1.8/drb/drb.rb:1347:in `initialize'"5/usr/local/lib/ruby/1.8/drb/drb.rb:1627:in `new'"?/usr/local/lib/ruby/1.8/drb/drb.rb:1627:in `start_service'"*./script/../config/../config/ffm.rb:6"^/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'"Q/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'"3./script/../config/../config/environment.rb:18"^/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27: `gem_original_require'"Q/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'"L/usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/runner.rb:39"^/usr/local/lib/ruby/site_ruby/1.8/rubyg `gem_original_require'"Q/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'"./script/runner:3: mesg" too large packet 1935959667Connection closed by foreign host.
I’m not sure whether it is exploitable by any means, so I started digging around this topic. It looks like every script which executes
DRb.start_service (even setup as client only!) opens new TCP server on some random, high port.
So there is solution for this – Ruby’s ACL – Access Control Lists. Of course it is very wise to block unwanted ports on network level, but not always it is easy to accomplish due to some restrictions casued by… well, human factor ;-) In such case You can block access to this port using mentioned ACLs. Example will be the best way to show it. This is content of my
~/.irbrc when I did a lot test to DRb enabled processing.
require 'drb' require 'drb/acl' require 'pp' acl = ACL.new( %w[deny all] ) DRb.install_acl(acl) DRb.start_service
Since irb works only as DRb client, I don’t need any TCP connections, so I can disable all of thems (
deny all). This code I also use in my server since it is one host setup with connection via UNIX socket (URI to connect to DRb server looks like
"drbunix://tmp/socketName"). It is possible to use some more sophisticated entries:
acl = ACL.new( %w[allow 127.0.0.1/32 allow 18.104.22.168/8 deny all] )
Will allow connect to DRb server via TCP from localhost (127.0.0.1), all host with IP addres with 87 in first octect and deny all other folks.
All examples were tested with
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-freebsd6]