Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solid as diamond by Paolo Perego

Solid as diamond by Paolo Perego

Watch the video here: https://vimeo.com/69242088

Railsberry

April 23, 2013
Tweet

More Decks by Railsberry

Other Decks in Technology

Transcript

  1. Solid as Diamond Using Ruby in a web application penetration

    test [email protected] - Railsberry 2013 - Krakow Wednesday, April 17, 13
  2. talk.inspect • Owasp Top 10 2013 • Ruby code to...

    • Leverage our attack surface • Bruteforce authentication mechanism • Look for Cross site scripting 3 Wednesday, April 17, 13
  3. Change your mindset. You’re an attacker now! 5 Your web

    application is a blackbox You’ve got only a URL as a starting point (optional) You may have a valid user, instead you have to register a user to the application Good luck! Wednesday, April 17, 13
  4. The Owasp Top 10 - 2013 6 • A1 –

    Injection • A2 – Broken Authentication and Session Management • A3 – Cross-Site Scripting (XSS) • A4 – Insecure Direct Object References • A5 – Security Misconfiguration • A6 – Sensitive Data Exposure • A7 – Missing Function Level Access Control • A8 – Cross-Site Request Forgery (CSRF) • A9 – Using Known Vulnerable Components • A10 – Unvalidated Redirects and Forwards Wednesday, April 17, 13
  5. It all starts with... 7 ... someone who wants to

    publish a new web application, she give us the url and she says “test it for security issues, please”... Wednesday, April 17, 13
  6. Leverage your attack surface 9 Spot attack entrypoints: (robots.txt and

    url discovery with bruteforce) Fingerprint your target Check transport layer security Check for the service door (backup files) Wednesday, April 17, 13
  7. Fingerprint your target 10 • Meta generator tag • Server

    HTTP response field • X-Powered-by HTTP response field • Popular pages with extension (login.do, index.jsp, main.asp, login.php...) • The HTTP response field order (actually not implemented in gengiscan gem) Wednesday, April 17, 13
  8. Fingerprint your target 11 def detect(url) uri = URI(url) begin

    res = Net::HTTP.get_response(uri) {:status=>:OK, :code=>res.code, :server=>res['Server'], :powered=>res['X-Powered-By'], :generator=>get_generator_signature(res)} rescue {:status=>:KO, :code=>nil, :server=>nil, :powered=>nil, :generator=>nil} end end def get_generator_signature(res) generator = "" doc=Nokogiri::HTML(res.body) doc.xpath("//meta[@name='generator']/@content").each do |value| generator = value.value end generator end $ gem install gengiscan $ gengiscan http://localhost:4567 {:status=>:OK, :code=>"404", :server=>"WEBrick/1.3.1 (Ruby/ 1.9.3/2012-04-20)", :powered=>nil, :generator=>""} Wednesday, April 17, 13
  9. Spot attack entrypoints 13 # TESTING: SPIDERS, ROBOTS, AND CRAWLERS

    (OWASP-IG-001) def self.robots(site) site = 'http://'+site unless site.start_with? 'http://' or site.start_with? 'https://' allow_list = [] disallow_list = [] begin res=Net::HTTP.get_response(URI(site+'/robots.txt')) return {:status=>:KO, :allow_list=>[], :disallow_list=>[], :error=>"robots.txt response code was #{res.code}"} if (res.code != "200") res.body.split("\n").each do |line| disallow_list << line.split(":")[1].strip.chomp if (line.downcase.start_with?('disallow')) allow_list << line.split(":")[1].strip.chomp if (line.downcase.start_with?('allow')) end rescue Exception => e return {:status=>:KO, :allow_list=>[], :disallow_list=>[], :error=>e.message} end {:status=>:OK, :allow_list=>allow_list, :disallow_list=>disallow_list, :error=>""} end $ gem install codesake_links $ links -r http://localhost:4567 Wednesday, April 17, 13
  10. Spot attack entrypoints 14 • Use a dictionary to discover

    URLs with bruteforce • Very intrusive attack... you’ll be busted, be aware $ gem install codesake_links $ links -b test_case_dir_wordlist.txt http://localhost:4567 Wednesday, April 17, 13
  11. Check transport layer security 15 $ gem install ciphersurfer $

    ciphersurfer www.gmail.com Evaluating secure communication with www.gmail.com:443 Overall evaluation : B (76.5) Protocol support : ooooooooooooooooooooooooooooooooooooooooooooooooooooooo (55) Key exchange : oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo (80) Cipher strength : oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo (90) Evaluate an SSL connection for: • protocols the server supports • cipher length • certificate key length Wednesday, April 17, 13
  12. Check transport layer security 16 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 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 Wednesday, April 17, 13
  13. Check for the service door 17 require 'anemone' require 'httpclient'

    h=HTTPClient.new() Anemone.crawl(ARGV[0]) do |anemone| anemone.on_every_page do |page| response = h.get(page.url) puts "Original: #{page.url}: #{response.code}" response = h.get(page.url.to_s.split(";")[0].concat(".bak")) puts "BAK: #{page.url.to_s.split(";")[0].concat(".bak")}: #{response.code}" response = h.get(page.url.to_s.split(";")[0].concat(".old")) puts "OLD: #{page.url.to_s.split(";")[0].concat(".old")}: #{response.code}" response = h.get(page.url.to_s.split(";")[0].concat("~")) puts "~: #{page.url.to_s.split(";")[0].concat("~")}: #{response.code}" end end Wednesday, April 17, 13
  14. How do I break this? 24 1. Use an existing

    user to check the HTML <p> Wrong password for admin user </p> 2. Place a canary string to anonymize the output <p> Wrong password for canary_username user </p> 3. Submit the post and check if the response is the one expected with the canary substituted <p> Wrong password for tom user </p> Wednesday, April 17, 13
  15. How do I break this? 25 def post(url, username, password)

    agent = Mechanize.new agent.user_agent_alias = 'Mac Safari' agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE username_set = false password_set = false page = agent.get(url) page.forms.each do |form| form.fields.each do |field| if field.name.downcase == 'username' or field.name.downcase== 'login' username_set = true field.value = username end if field.name.downcase == 'password' or field.name.downcase== 'pass' or field.name.downcase== 'pwd' password_set = true field.value = password end end return agent.submit(form) if username_set and password_set end return nil end Wednesday, April 17, 13
  16. How do I break this? 26 log("existing user #{username} used

    as canary") wrong_pwd = post(url, username, "caosintheground").body.gsub(username, 'canary_username') wrong_creds = post(url, "caostherapy", "caosintheground").body.gsub("caostherapy", "canary_username") if ! line.start_with?("#") sleep(@sleep_time) log("awake... probing with: #{line}") r= post(url, line, ".4nt4n1") found << line if r.body == wrong_pwd.gsub("canary_username", line) end Wednesday, April 17, 13
  17. Look for Cross Site Scripting 32 • In GETs •

    Submit the attack payload as parameter in the query string • Parse HTML and check if payload is in the script nodes • In POSTs • Get the page • Find the form(s) • Fill the form input values with attack payload • Submit the form • Parse HTML and check if payload is in the script nodes Wednesday, April 17, 13
  18. Look for Cross Site Scripting 33 attack_url = Cross::Url.new(url) Cross::Attack::XSS.each

    do |pattern| attack_url.params.each do |par| page = @agent.get(attack_url.fuzz(par[:name],pattern)) @agent.log.debug(page.body) if debug? scripts = page.search("//script") scripts.each do |sc| found = true if sc.children.text.include?("alert('cross canary')") @agent.log.debug(sc.children.text) if @options[:debug] end attack_url.reset end end Exploiting GETs... $ gem install cross $ cross -u http://localhost:4567/hello?name=paolo Wednesday, April 17, 13
  19. Look for Cross Site Scripting 34 begin page = @agent.get(url)

    rescue Mechanize::UnauthorizedError puts 'Authentication failed. Giving up.' return false rescue Mechanize::ResponseCodeError puts 'Server gave back 404. Giving up.' return false end puts "#{page.forms.size} form(s) found" if debug? page.forms.each do |f| f.fields.each do |ff| ff.value = "<script>alert('cross canary');</script>" end pp = @agent.submit(f) puts "#{pp.body}" if debug? scripts = pp.search("//script") scripts.each do |sc| found = true if sc.children.text == "alert('cross canary');" end end Exploiting POSTs... $ gem install cross $ cross http://localhost:4567/login Wednesday, April 17, 13
  20. What we learnt 37 • Don’t trust your users •

    “Security through obscurity” is EVIL • Testing for security issues is a mandatory step before deploy • HTTPS won’t safe from XSS or SQL Injections Wednesday, April 17, 13
  21. Some links before we leave 38 http://armoredcode.com/blog/categories/pentest-with-ruby/ https://github.com/codesake http://ronin-ruby.github.com/ https://github.com/rapid7/metasploit-framework

    https://gist.github.com/2935464 (gist for anemone crawling demo) http://www.owasp.org All my application security stuff will be opensource and available here: http://brakemanscanner.org/ Not mine, here because they’re cool Wednesday, April 17, 13