Some security tips for ruby hackers: leveraging the attack surface. Part 1.

In the first episode I introduced the security checks I’d like to talk about at the talk I have to give next Friday.

Today we will talk about the code to automate this checks.

The attack surface

Discovering the attack surface it will be the first part of my talk. It’s about:

Category Owasp Testing guide reference Test name
Information Gathering OWASP-IG-001 Spiders, Robots and Crawlers
Information Gathering OWASP-IG-002 Search Engine Discovery/Reconnaissance
Information Gathering OWASP-IG-003 Identify application entry points
Information Gathering OWASP-IG-004 Testing for Web Application Fingerprint
Information Gathering OWASP-IG-005 Application Discovery
Information Gathering OWASP-IG-006 Analysis of Error Codes
Configuration Management Testing OWASP-CM-001 SSL/TLS Testing
Configuration Management Testing OWASP-CM-002 DB Listener Testing
Configuration Management Testing OWASP-CM-003 Infrastructure Configuration Management Testing
Configuration Management Testing OWASP-CM-004 Application Configuration Management Testing
Configuration Management Testing OWASP-CM-005 Testing for File Extensions Handling
Configuration Management Testing OWASP-CM-006 Old, backup and unreferenced files
Configuration Management Testing OWASP-CM-007 Infrastructure and Application Admin Interfaces
Configuration Management Testing OWASP-CM-008 Testing for HTTP Methods and XST

Let’s go.

OWASP-IG-001: Spiders, Robots and Crawlers

In April I wrote a post about using robots.txt as attack weapon. Do you remember it? No?!? Go back and read it.

Acting as a spider it is possible to discover how much your website is wide and to spot interesting entry points.

Robots.txt file is the first thing I test if I want to find more out of your site.

links rubygem is a piece of code I wrote to automate OWASP-IG-001 testing.

As you may see… the Net::HTTP is enough to play with this test.

``` ruby Links::Api.robots - testing for OWASP-IG-001

TESTING: SPIDERS, ROBOTS, AND CRAWLERS (OWASP-IG-001)

def self.robots(site, only_disallow=true)

if (! site.start_with? ‘http://’) and (! site.start_with? ‘https://’) site = ‘http://’+site end

list = [] begin res=Net::HTTP.get_response(URI(site+’/robots.txt’)) if (res.code != “200”) return [] end

res.body.split("\n").each do |line|
  if only_disallow
    if (line.downcase.start_with?('disallow'))
      list << line.split(":")[1].strip.chomp
    end
  else
    if (line.downcase.start_with?('allow') or line.downcase.start_with?('disallow'))
      list << line.split(":")[1].strip.chomp
    end
  end
end   rescue
return []   end

list end

In the bin/links ruby script we check if the link disallowed is accessible or not.
Discovering disallowed urls that are accessible is **important** if we're
wondering to discover service door and try to break-in

``` ruby what we can do with robots.txt content (again from links rubygem)
list.each do |l|
  if robots or bulk
    if ! l.start_with? '/'
      l = '/'+l.chomp
    end
    if ! target.start_with? 'http://' and ! target.start_with? 'https://'
      #defaulting to HTTP when no protocol has been supplied
      target = "http://"+target
    end

    print "#{target}#{l}:".color(:white)
    start = Time.now
    code = Links::Api.code(target+l, proxy)
    stop = Time.now
  else
    print "#{l}:".color(:white)
    start = Time.now
    code = Links::Api.code(l, proxy)
    stop = Time.now
  end
...

Crawling: the clean way

What about crawling a website? By crawling I mean retrieving all the possible urls starting from the homepage, extracting all the links in the HTML and recursive make a lot of requests.

But we’re lucky enough and there is something who make a great gem for us.

Using anemone rubygem, we have a clean DSL for crawling a website starting from the links extracted by the web pages we find.

``` ruby crawling a website using anemone require ‘anemone’

Anemone.crawl(“http://www.target.com/”) do |anemone| anemone.on_every_page do |page| puts page.url end end


### Crawling: the bruteforce way

Even before discovering [anemone](http://anemone.rubyforge.org/) rubygem, I
wrote the [enchant](https://github.com/thesp0nge/enchant) gem to discover links
by bruteforcing the url with words taken from dictionary.

Using a bruteforce approach can be useful if an important link is not in
robots.txt (and I do suggest not to do this) and it's likely not linked in any
of the public pages.

Enchant::Engine.get_list method is trivial, it take the words from a dictionary I borrow from [Owasp Zap](https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project) project.

``` ruby Enchant::Engine.get_list
def get_list

  if @wordlist.nil?
    if File.exists?('../../db/directory-list-2.3-small.txt')
      @wordlist='../../db/directory-list-2.3-small.txt'
    end
    if File.exists?('./db/directory-list-2.3-small.txt')
      @wordlist='./db/directory-list-2.3-small.txt'
    else
      @list = {}
    end

  end

  begin
    File.open(@wordlist, 'r') { |f|
      @list = f.readlines
    }
  rescue Errno::ENOENT
    puts "it seems the wordlist file is not present (#{@wordlist})".color(:red)
    @list = {}
  end
end

There is no real magic in the Enchant::Engine.scan method… just a bunch of get and check for error codes… I know, I won’t win the A.Turing awards for these pieces of code, but sometimes they saved me the day in real pentest.

``` ruby Enchant::Engine.scan main loop list.each do |path| pbar.inc if ! path.start_with? ‘#’ begin response = http.get(‘/’+path.chop) c = response.code.to_i refused = 0 if c == 200 @urls_open « path end if c == 401 @urls_private « path end if c >= 500 @urls_internal_error « path end rescue Errno::ECONNREFUSED refused += 1 if refused > 5 pbar.finish puts “received 5 connection refused. #{@host} went down”.color(:red) return @urls_open.count else puts “[WARNING] connection refused”.color(:yellow) sleep 2 * refused end

rescue Net::HTTPBadResponse
  refused = 0
  if @verbose
    puts "#{$!}".color(:red)
  end
rescue Errno::ETIMEDOUT
  refused = 0
  if @verbose
    puts "#{$!}".color(:red)
  end
end   end end ```

OWASP-IG-002: Search Engine Discovery/Reconnaissance

This task can be done easily with a browser. Just point it to google.com and use the ‘site:’ special keyword to search for all pages about a website indexed with google.

A sample query that enumerate all the stuff you can find related to armoredcode.com domain is: http://www.google.it/search?q=site:armoredcode.com

Of course you can use Net::HTTP also in this case, but Google is not happy to be called in an automated way without authentication and their api usage… so it’s easy not to automate the task at all :-)

OWASP-IG-004: Testing for Web Application Fingerprint

This is a 2 years old project, may be it would a great idea to write down a new and better fingerprinter, however wafp script can be used to try to detect the CMS version or a particular Application server serving our target.

OWASP-CM-001: SSL/TLS Testing

For SSL/TSL testing I use a rubygem I wrote a couple of months ago: ciphersurfer.

I blogged about ciphersurfer in my previous blog: here and here.

Maybe those two posts deserve a repost over armoredcode.com.

However the trick behind ciphersurfer is trying to make HTTPS calls, using standard Ruby networking APIs (against, no voodoo here).

``` ruby lib/ciphersurfer/scanner.rb def go context=OpenSSL::SSL::SSLContext.new(@proto) cipher_set = context.ciphers cipher_set.each do |cipher_name, cipher_version, bits, algorithm_bits|

request = Net::HTTP.new(@host, @port)
request.use_ssl = true
request.verify_mode = OpenSSL::SSL::VERIFY_NONE
request.ciphers= cipher_name
begin
  response = request.get("/")
  @ok_bits << bits
  @ok_ciphers << cipher_name
rescue OpenSSL::SSL::SSLError => e
  # Quietly discard SSLErrors, really I don't care if the cipher has
  # not been accepted
rescue 
  # Quietly discard all other errors... you must perform all error
  # chekcs in the calling program
end   end end ```

Here we don’t use httpclient helpers since I want to play with different ciphers at time.

That’s it. All the magic happens there. Now, let’s look like at the bin script to see how the scoring system has been used.

First of all, we must scan the target for all the protocols we support.

``` ruby bin/ciphersurfer protocol_version.each do |version| s = Ciphersurfer::Scanner.new({:host=>host, :port=>port, :proto=>version})

s.go if (s.ok_ciphers.size != 0) supported_protocols « version cipher_bits = cipher_bits | s.ok_bits ciphers = ciphers | s.ok_ciphers end

end



``` ruby bin/ciphersurfer
cert= Ciphersurfer::Scanner.cert(host, port)
if ! cert.nil?
  a=cert.public_key.to_text
  key_size=/Modulus \((\d+)/i.match(a)[1]
else
  puts "warning: the server didn't give us the certificate".color(:yellow)
  key_size=0
end

Note that we don’t make another GET here since we did it at the beginning of the engagement when we checked if the target was alive or not.

Now, let’s calculate the scores, all of them in a 0..100 range.

``` ruby bin/ciphersurfer proto_score= Ciphersurfer::Score.evaluate_protocols(supported_protocols) cipher_score= Ciphersurfer::Score.evaluate_ciphers(cipher_bits) key_score= Ciphersurfer::Score.evaluate_key(key_size.to_i) score= Ciphersurfer::Score.score(proto_score, key_score, cipher_score)


And then, some graphics to make the experience more appealing.

``` ruby bin/ciphersurfer
printf "%20s : %s (%s)\n", "Overall evaluation", Ciphersurfer::Score.evaluate(score), score.to_s
printf "%20s : ", "Protocol support" 
proto_score.to_i.times{print 'o'.color(score_to_color(proto_score))}
puts ' ('+proto_score.to_s+')'
printf "%20s : ",  "Key exchange" 
key_score.to_i.times{print 'o'.color(score_to_color(key_score))}
puts ' ('+key_score.to_s+')'
printf "%20s : ", "Cipher strength" 
cipher_score.to_i.times{print 'o'.color(score_to_color(cipher_score))}
puts ' ('+cipher_score.to_s+')'

Wrap up

This is the first episode about leveraging the attack surface of a web application and, as along I was writing it I realized a couple of things:

  • in the friday talk I’ll go completely out of time
  • there is a lot of things to say about using ruby and the Owasp testing guide that it’s worth making something bigger…
  • I have a lot of things yet to learn
  • I don’t have fancy pictures to put on my Friday slideshow

Reference

Please note that this post series is built using Owasp testing guide as skeleton.

Off topic

True to be told I’m nervous for #rubyday talk. The talks I gave since today were done at security conferences where I’m confortable.

Here I’m going to talk about code to people who do uunderstand and that they do write great code, and they are not afraid to show it.

Just a bit scared… I hope they like it.

comments powered by Disqus