Slide 1

Slide 1 text

Solid as Diamond Using Ruby in a web application penetration test Wednesday, September 18, 13

Slide 2

Slide 2 text

self.inspect • I do stuff: husband, proud father && martial artist • I break other people code for living (only when authorized) • I blog at: http://armoredcode.com • I’m on github too: https:// github.com/thesp0nge • I love twitter: @thesp0nge, @armoredcode 2 Wednesday, September 18, 13

Slide 3

Slide 3 text

talk.inspect • Owasp Top 10 2013 • Ruby code to... • Leverage a web application attack surface • Bruteforce authentication mechanism • Look for Cross site scripting 3 Wednesday, September 18, 13

Slide 4

Slide 4 text

Disclaimer 4 Attack only sites you’re authorized to Wednesday, September 18, 13

Slide 5

Slide 5 text

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, September 18, 13

Slide 6

Slide 6 text

It all starts with... 6 ... someone wants to publish a new web application on the Internet or on an Internal network, she gives me the url saying: “test it for security issues, please”... Wednesday, September 18, 13

Slide 7

Slide 7 text

Our target 7 Wednesday, September 18, 13

Slide 8

Slide 8 text

The Owasp Top 10 - 2013 8 • 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 https://www.owasp.org/index.php/Top_10_2013 Wednesday, September 18, 13

Slide 9

Slide 9 text

Leverage your attack surface 9 Wednesday, September 18, 13

Slide 10

Slide 10 text

Leverage your attack surface 10 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, September 18, 13

Slide 11

Slide 11 text

Fingerprint your target 11 • 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, phpinfo.php...) • The HTTP response field order (soon it will be implemented in the gengiscan gem) Wednesday, September 18, 13

Slide 12

Slide 12 text

Fingerprint your target 12 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, September 18, 13

Slide 13

Slide 13 text

Spot attack entrypoints 13 robots.txt to discover to fingerprint Wednesday, September 18, 13

Slide 14

Slide 14 text

Spot attack entrypoints 14 # 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, September 18, 13

Slide 15

Slide 15 text

Spot attack entrypoints 15 • 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, September 18, 13

Slide 16

Slide 16 text

Check transport layer security 16 $ 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, September 18, 13

Slide 17

Slide 17 text

Check transport layer security 17 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, September 18, 13

Slide 18

Slide 18 text

Check for the service door 18 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, September 18, 13

Slide 19

Slide 19 text

Demo 19 Wednesday, September 18, 13

Slide 20

Slide 20 text

Bruteforce authentication mechanism 20 Wednesday, September 18, 13

Slide 21

Slide 21 text

Am I vulnerable? 21 Wednesday, September 18, 13

Slide 22

Slide 22 text

Am I vulnerable? 22 Wednesday, September 18, 13

Slide 23

Slide 23 text

How do I break this? 23 1. Use an existing user to check the HTML

Wrong password for admin user

2. Place a canary string to anonymize the output

Wrong password for canary_username user

3. Submit the post and check if the response is the one expected with the canary substituted

Wrong password for tom user

Wednesday, September 18, 13

Slide 24

Slide 24 text

How do I break this? 24 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, September 18, 13

Slide 25

Slide 25 text

How do I break this? 25 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, September 18, 13

Slide 26

Slide 26 text

Demo 26 Wednesday, September 18, 13

Slide 27

Slide 27 text

Look for Cross Site Scripting (reflected) 27 Wednesday, September 18, 13

Slide 28

Slide 28 text

Look for Cross Site Scripting 28 Wednesday, September 18, 13

Slide 29

Slide 29 text

Look for Cross Site Scripting 29 Wednesday, September 18, 13

Slide 30

Slide 30 text

Look for Cross Site Scripting 30 • 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, September 18, 13

Slide 31

Slide 31 text

Look for Cross Site Scripting 31 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, September 18, 13

Slide 32

Slide 32 text

Look for Cross Site Scripting 32 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 = "alert('cross canary');" 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, September 18, 13

Slide 33

Slide 33 text

Demo 33 Wednesday, September 18, 13

Slide 34

Slide 34 text

What we learnt 34 • 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, September 18, 13

Slide 35

Slide 35 text

Some links before we leave 35 http://armoredcode.com/blog/categories/pentest-with-ruby/ https://github.com/codesake http://ronin-ruby.github.com/ https://github.com/rapid7/metasploit-framework http://www.owasp.org http://brakemanscanner.org/ Not mine, here because they’re cool http://www.youtube.com/user/armoredcodedotcom Wednesday, September 18, 13

Slide 36

Slide 36 text

Questions? 36 Wednesday, September 18, 13

Slide 37

Slide 37 text

Thank you! 37 Wednesday, September 18, 13