├── util
└── cms_lister
│ └── list.txt
├── README.md
├── md5_lookup.rb
└── cms_lister.rb
/util/cms_lister/list.txt:
--------------------------------------------------------------------------------
1 | readme.txt
2 | readme.html
3 | readme.php
4 | robots.txt
5 | .htaccess
6 | changelog.txt
7 | license.txt
8 | install.txt
9 | help.php
10 | wp-admin/
11 | administrator/
12 | administration/
13 | wp-login.php
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | + Metasploit modules:
2 | + md5_lookup.rb - reverse provided MD5 hashes by lookup in online databases
3 | + cms_lister.rb - list remote directory by comparision with local directory (or paths list), useful i.e. for CMS recon
4 | + util - samples and additional files for appropriate modules
5 | + cms_lister
6 | + list.txt - sample list of common paths for cms_lister
7 |
8 | md_lookup.rb
9 | -
10 | /*
11 | this module has been ported as a part of metasploit-framework:
12 | https://github.com/rapid7/metasploit-framework/blob/master/tools/password/md5_lookup.rb
13 |
- thanks to sinn3r: [](http://www.twitter.com/_sinn3r)
14 | */
15 | 
16 | cms_lister.rb
17 | -
18 | options:
19 | 
20 |
21 | running:
22 | 
23 |
--------------------------------------------------------------------------------
/md5_lookup.rb:
--------------------------------------------------------------------------------
1 | #
2 | # This module requires Metasploit: http//metasploit.com/download
3 | # Current source: https://github.com/rapid7/metasploit-framework
4 | #
5 | # MD5 lookup
6 | #
7 | # Author: hasherezade (http://hasherezade.net)
8 | # Source: https://github.com/hasherezade/metasploit_modules/blob/master/md5_lookup.rb
9 | #
10 |
11 | require 'msf/core'
12 |
13 | class Metasploit3 < Msf::Auxiliary
14 | include Msf::Exploit::Remote::HttpClient
15 |
16 | def initialize(info={})
17 | super(update_info(info,
18 | 'Name' => "Md5 lookup",
19 | 'Description' => %q{
20 | This auxiliary module attempts to reverse provided MD5 hashes by lookup in online databases.
21 | },
22 | 'License' => MSF_LICENSE,
23 | 'Author' =>
24 | [
25 | 'hAsh (http://hasherezade.net)' # Metasploit module
26 | ]
27 | ))
28 |
29 | register_options(
30 | [
31 | OptPath.new('PATH', [true, 'File with md5 hashes']),
32 | ])
33 | register_advanced_options(
34 | [
35 | OptPort.new('RPORT',[true, 'Port of the lookup service', 80]),
36 | OptString.new('RHOST',[true, 'Host used for lookup', 'md5cracker.org']),
37 | OptString.new('TARGETURI', [true, 'URI of the lookup service', '/api/api.cracker.php']),
38 | OptString.new('DATABASES',[true, 'Comma separated list of MD5 online databases',
39 | "authsecu, i337.net, md5.my-addr.com, md5.net, md5crack, md5cracker.org, md5decryption.com, md5online.net, md5pass, netmd5crack, tmto"
40 | ]),
41 | ])
42 | deregister_options('VHOST', 'Proxies')
43 | end
44 |
45 | # utils
46 | def read_file()
47 | path = datastore['PATH']
48 | print_status("Trying to read file: #{path}")
49 |
50 | if not File::exist?(path)
51 | print_error("No such file")
52 | return nil
53 | elsif not File::file?(path)
54 | print_error("Invalid path")
55 | return nil
56 | end
57 | hash_counter = 0
58 | hashes = Set.new
59 | my_file = File::new(path, mode="r")
60 | my_file.each {|line|
61 | hash = fetchMd5(line)
62 | if hash
63 | hashes << hash
64 | hash_counter += 1
65 | end
66 | }
67 | my_file.close
68 | print_status("Found hashes: #{hash_counter}, unique: #{hashes.length}")
69 | return hashes
70 | end
71 |
72 | def get_string_between(my_string, start_at, end_at)
73 | my_string = "#{my_string}"
74 |
75 | ini = my_string.index(start_at)
76 | return nil if ini == 0
77 |
78 | ini += start_at.length
79 | length = my_string.index(end_at, ini).to_i - ini
80 | my_string[ini,length]
81 | end
82 |
83 | def fetchMd5(my_string)
84 | if my_string =~ /([0-9a-fA-F]{32})/
85 | return $1
86 | end
87 | return nil
88 | end
89 |
90 | def md5search(hash, database)
91 | hash = fetchMd5(hash)
92 | if not hash
93 | return nil
94 | end
95 |
96 | port = datastore['RPORT']
97 | old_ssl = autoenable_ssl(port)
98 | res = send_request_cgi({
99 | 'method' => 'GET',
100 | 'uri' => normalize_uri(target_uri.path),
101 | 'vars_get' => { "database" => database, "hash" => hash }
102 | })
103 | datastore['SSL'] = old_ssl
104 |
105 | if not res or res.code != 200 or res.body.empty?
106 | print_error("#{url}, db: #{database} - returned invalid response")
107 | return nil
108 | end
109 | # "status":true, "result":"123",
110 | if res.body =~ /\>404 Not Found\
111 | return nil
112 | end
113 |
114 | if res.body =~ /true/
115 | tag1 = "result\":\""
116 | tag2 = "\","
117 | password = get_string_between(res.body, tag1, tag2)
118 | return password
119 | end
120 | return nil
121 | end
122 |
123 | def fetch_db_names(names_string)
124 | values = names_string.split(",").map(&:strip)
125 | if values
126 | return values.to_set()
127 | end
128 | return nil
129 | end
130 |
131 |
132 | def md5crack(hash, md5_databases)
133 | for db in md5_databases
134 | pass = md5search(hash, db)
135 | if pass
136 | return pass
137 | end
138 | end
139 | return nil
140 | end
141 |
142 | def crack_hashes(hashes, md5_databases)
143 | return nil if not hashes
144 | cracked = 0
145 | for chunk in hashes
146 | pass = md5crack(chunk, md5_databases)
147 | if pass
148 | print_good("#{chunk} : #{pass}")
149 | cracked += 1
150 | else
151 | print_error("#{chunk}")
152 | end
153 | end
154 | print_status("Found passwords: #{cracked} out of #{hashes.length}")
155 | end
156 |
157 | def autoenable_ssl(port)
158 | old_ssl = datastore['SSL']
159 | if (port == URI::HTTP::DEFAULT_PORT)
160 | datastore['SSL'] = false
161 | elsif (port == URI::HTTPS::DEFAULT_PORT)
162 | datastore['SSL'] = true
163 | end
164 | return old_ssl
165 | end
166 |
167 | # MSF API:
168 |
169 | def run
170 | hashes = read_file()
171 | if not hashes or hashes.length() == 0
172 | print_error("No hashes supplied")
173 | return nil
174 | end
175 | md5_dbs = fetch_db_names(datastore['DATABASES'])
176 | if not md5_dbs or md5_dbs.length() == 0
177 | print_error("No databases supplied!")
178 | return nil
179 | end
180 | if datastore['VERBOSE']
181 | print_status("Loaded: #{md5_dbs.length()} databases")
182 | end
183 | print_status("Attempting to reverse hashes...")
184 | crack_hashes(hashes, md5_dbs)
185 | end
186 |
187 | end
188 |
189 |
--------------------------------------------------------------------------------
/cms_lister.rb:
--------------------------------------------------------------------------------
1 | #
2 | # This module requires Metasploit: http//metasploit.com/download
3 | # Current source: https://github.com/rapid7/metasploit-framework
4 | #
5 | # CMS File Lister
6 | #
7 | # Author: hasherezade (http://hasherezade.net)
8 | # Source: https://github.com/hasherezade/metasploit_modules
9 | #
10 |
11 | require 'msf/core'
12 |
13 | class Metasploit3 < Msf::Auxiliary
14 | include Msf::Exploit::Remote::HttpClient
15 |
16 | MAX_REDIR = 5
17 |
18 | def initialize(info={})
19 | super(update_info(info,
20 | 'Name' => "CMS File Lister",
21 | 'Description' => %q{
22 | This auxiliary module attempts to list remote directory by comparision with local directory (or paths list)
23 | },
24 | 'License' => MSF_LICENSE,
25 | 'Author' =>
26 | [
27 | 'hAsh (http://hasherezade.net)' # Metasploit module
28 | ]
29 | ))
30 |
31 | register_options(
32 | [
33 | OptPath.new('CMS_DIR',[false, 'Directory with CMS']),
34 | OptPath.new('PATH', [false, 'File with list of files to search']),
35 | OptString.new('TARGETURI', [true, 'URI of the lookup service', '/']),
36 | OptRegexp.new('NOT_FOUND', [true, '404 pattern', '/Not Found/']),
37 | OptInt.new('REDIR_DEPTH', [true, "Redirection depth, max = #{MAX_REDIR}", 2]),
38 | OptRegexp.new('EXCLUDE_NAMES', [true, 'Skip files with names matching the pattern','\.(?:php|js|css|png|jpg|gif)$']),
39 | OptBool.new('DISPLAY_ERR', [true, 'Display error codes (if different than 404)', 'false']),
40 | OptBool.new('COMPARE_FILES', [true, "Compare remote file with local (Out: [+] - same; [-] - different; [?] - not compared)",'true'])
41 | ])
42 | deregister_options('VHOST', 'Proxies')
43 | end
44 |
45 | # utils
46 | def read_dir(dir)
47 | return nil if not dir or dir.length == 0
48 |
49 | if not File::directory?(dir)
50 | print_error("Not a directory: #{dir}")
51 | return nil
52 | end
53 |
54 | files = Set.new
55 |
56 | Dir[ File.join(dir, '**', '*') ].reject { |p| File.directory? p
57 | full_path = "#{p}"
58 | dir_path = "#{dir}"
59 | suffix = full_path[dir_path.length, full_path.length - dir_path.length]
60 | if File::directory?(full_path)
61 | suffix += '/'
62 | end
63 | files << suffix
64 | }
65 | return files
66 | end
67 |
68 | def read_file(path)
69 | if not File::exist?(path)
70 | print_error("No such file: #{path}")
71 | return nil
72 | elsif not File::file?(path)
73 | print_error("Invalid path: #{path}")
74 | return nil
75 | end
76 |
77 | paths = Set.new
78 | my_file = File::new(path, mode="r")
79 | my_file.each {|line|
80 | line = line.strip
81 | if line
82 | paths << line
83 | end
84 | }
85 | my_file.close
86 | print_status("Loaded #{paths.length} paths") if datastore['VERBOSE']
87 | return paths
88 | end
89 |
90 | def get_url(path, is_ssl)
91 | proto = "http"
92 | proto = "https" if is_ssl
93 |
94 | url = "#{proto}:/" + normalize_uri("#{datastore['RHOST']}//#{datastore['TARGETURI']}//#{path}")
95 | end
96 |
97 | def compare_content(path, res_body)
98 | dir = datastore['CMS_DIR']
99 | return -1 if not dir
100 |
101 | path = "#{dir}/#{path}"
102 |
103 | if not File::exist?(path)
104 | return -1
105 | elsif File::directory?(path)
106 | return 1
107 | elsif not File::file?(path)
108 | return -1
109 | end
110 | contents = File.read(path)
111 | return 1 if res_body == contents
112 | return 0
113 | end
114 |
115 | def cmpres_to_str(is_same)
116 | verified = '[?]'
117 | if is_same == 1
118 | verified = '[+]'
119 | elsif is_same == 0
120 | verified = '[-]'
121 | end
122 | return verified
123 | end
124 |
125 | def path_search(path, url, port, redir_depth = 0)
126 | return nil if not url
127 |
128 | print_status("GET: #{url}") if (datastore['VERBOSE'])
129 | autoenable_ssl(port)
130 | res = send_request_cgi({
131 | 'method' => 'GET',
132 | 'uri' => url,
133 | 'rhost' => datastore['RHOST'],
134 | 'rport' => port
135 | })
136 |
137 | if not res
138 | print_error("#{url}")
139 | return false
140 | end
141 |
142 | if res.code == 404 or res.body =~ datastore['NOT FOUND']
143 | print_error("Not found: #{res.code}") if datastore['VERBOSE'] and datastore['DISPLAY_ERR']
144 | return false
145 | end
146 |
147 | if res.code == 200
148 | is_same = -1
149 | if datastore['COMPARE_FILES']
150 | is_same = compare_content(path, res.body)
151 | end
152 |
153 | verified = cmpres_to_str(is_same)
154 | print_good("#{verified} #{url}")
155 | return true
156 | end
157 |
158 | if (res.code == 301 or res.code == 302) and res.redirection
159 | if (redir_depth < MAX_REDIR and redir_depth < datastore['REDIR_DEPTH'])
160 | redir_uri = res.redirection
161 | is_ssl = false
162 | if redir_uri.scheme == "https"
163 | port = URI::HTTPS::DEFAULT_PORT
164 | is_ssl = true
165 | end
166 | url = get_url(redir_uri.path, is_ssl)
167 | print_status("Redirection to: #{url} : ERR: #{res.code}") if datastore['VERBOSE']
168 | path_search(path, url, port, redir_depth + 1)
169 | end
170 | end
171 |
172 | print_error("#{url} : #{res.code}") if datastore['DISPLAY_ERR']
173 | return false
174 | end
175 |
176 | def skip_type(path)
177 | if path =~ datastore['EXCLUDE_NAMES']
178 | return true
179 | end
180 | return false
181 | end
182 |
183 | def search_paths(paths)
184 | return nil if not paths
185 |
186 | found = 0
187 | for chunk in paths
188 | if skip_type(chunk)
189 | next
190 | end
191 | url = get_url(chunk, datastore['SSL'])
192 | pass = path_search(chunk, url, datastore['RPORT'])
193 | if pass
194 | found += 1
195 | end
196 | end
197 | print_status("Found paths: #{found} out of #{paths.length}")
198 | end
199 |
200 | def autoenable_ssl(port)
201 | old_ssl = datastore['SSL']
202 | if (port == URI::HTTP::DEFAULT_PORT)
203 | datastore['SSL'] = false
204 | elsif (port == URI::HTTPS::DEFAULT_PORT)
205 | datastore['SSL'] = true
206 | end
207 | return old_ssl
208 | end
209 |
210 | # MSF API:
211 |
212 | def run
213 | paths = nil
214 | verbose = datastore['VERBOSE']
215 | print_status("Verbose mode: #{verbose}")
216 |
217 | if datastore['PATH'] and datastore['PATH'].length > 0
218 | fname = datastore['PATH']
219 | print_status("Searching by list, PATH = #{fname}")
220 | paths = read_file(fname)
221 | print_status("Attempting to search paths...")
222 | search_paths(paths)
223 | else
224 | print_error('PATH not set!')
225 | end
226 |
227 | if datastore['CMS_DIR'] and datastore['CMS_DIR'].length > 0
228 | cms = datastore['CMS_DIR']
229 | print_status("Searching by CMS_DIR = #{cms}")
230 | paths = read_dir(cms)
231 | search_paths(paths)
232 | else
233 | print_error('CMS_DIR not set!')
234 | end
235 |
236 | end
237 |
238 | end
239 |
240 |
--------------------------------------------------------------------------------