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

Solid as Diamond: using ruby in a web application penetration test

Solid as Diamond: using ruby in a web application penetration test

For Railsberry 2013 I gave a talk on some ruby gems I wrote to automate some security tests I perform during my daytime job.

Enjoy it

Paolo Perego

April 23, 2013
Tweet

More Decks by Paolo Perego

Other Decks in Programming

Transcript

  1. Solid as Diamond
    Using Ruby in a web application penetration test
    [email protected] - Railsberry 2013 - Cracow
    Tuesday, April 23, 13

    View full-size slide

  2. 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
    Tuesday, April 23, 13

    View full-size slide

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

    View full-size slide

  4. Disclaimer
    4
    Attack only sites you’re authorized to
    Tuesday, April 23, 13

    View full-size slide

  5. 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!
    Tuesday, April 23, 13

    View full-size slide

  6. 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 and she says
    “test it for security issues, please”...
    Tuesday, April 23, 13

    View full-size slide

  7. Our target
    7
    Tuesday, April 23, 13

    View full-size slide

  8. 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
    (To be released later this spring)
    https://www.owasp.org/index.php/Top_10_2013
    Tuesday, April 23, 13

    View full-size slide

  9. Leverage your attack surface
    9
    Tuesday, April 23, 13

    View full-size slide

  10. 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)
    Tuesday, April 23, 13

    View full-size slide

  11. 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)
    Tuesday, April 23, 13

    View full-size slide

  12. 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=>""}
    Tuesday, April 23, 13

    View full-size slide

  13. Spot attack entrypoints
    13
    robots.txt to discover
    to fingerprint
    Tuesday, April 23, 13

    View full-size slide

  14. 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
    Tuesday, April 23, 13

    View full-size slide

  15. 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
    Tuesday, April 23, 13

    View full-size slide

  16. 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
    Tuesday, April 23, 13

    View full-size slide

  17. 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
    Tuesday, April 23, 13

    View full-size slide

  18. 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
    Tuesday, April 23, 13

    View full-size slide

  19. Demo
    19
    Tuesday, April 23, 13

    View full-size slide

  20. Demo - Information gathering
    20
    Tuesday, April 23, 13

    View full-size slide

  21. Demo - First XSS discovered
    21
    Tuesday, April 23, 13

    View full-size slide

  22. Bruteforce authentication
    mechanism
    22
    Tuesday, April 23, 13

    View full-size slide

  23. Am I vulnerable?
    23
    Tuesday, April 23, 13

    View full-size slide

  24. Am I vulnerable?
    24
    Tuesday, April 23, 13

    View full-size slide

  25. How do I break this?
    25
    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

    Tuesday, April 23, 13

    View full-size slide

  26. How do I break this?
    26
    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
    Tuesday, April 23, 13

    View full-size slide

  27. How do I break this?
    27
    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
    Tuesday, April 23, 13

    View full-size slide

  28. Demo
    28
    Tuesday, April 23, 13

    View full-size slide

  29. Bruteforce authentication mechanism
    29
    Tuesday, April 23, 13

    View full-size slide

  30. Look for Cross Site Scripting
    (reflected)
    30
    Tuesday, April 23, 13

    View full-size slide

  31. Look for Cross Site Scripting
    31
    Tuesday, April 23, 13

    View full-size slide

  32. Look for Cross Site Scripting
    32
    Tuesday, April 23, 13

    View full-size slide

  33. Look for Cross Site Scripting
    33
    • 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
    Tuesday, April 23, 13

    View full-size slide

  34. Look for Cross Site Scripting
    34
    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
    Tuesday, April 23, 13

    View full-size slide

  35. Look for Cross Site Scripting
    35
    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
    Tuesday, April 23, 13

    View full-size slide

  36. Demo
    36
    Tuesday, April 23, 13

    View full-size slide

  37. Look for Cross Site Scripting
    37
    Tuesday, April 23, 13

    View full-size slide

  38. What we learnt
    38
    • 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
    Tuesday, April 23, 13

    View full-size slide

  39. Some links before we leave
    39
    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
    Tuesday, April 23, 13

    View full-size slide

  40. Questions?
    40
    Tuesday, April 23, 13

    View full-size slide

  41. Thank you!
    41
    Tuesday, April 23, 13

    View full-size slide