@@ -33,7 +33,8 @@ def initialize
33
33
'Author' =>
34
34
[
35
35
'==[ Alligator Security Team ]==' ,
36
- 'Heyder Andrade <heyder[at]alligatorteam.org>'
36
+ 'Heyder Andrade <heyder[at]alligatorteam.org>' ,
37
+ 'nebulus'
37
38
] ,
38
39
'License' => MSF_LICENSE
39
40
)
@@ -45,184 +46,173 @@ def initialize
45
46
[
46
47
true , 'The file that contains a list of probable users accounts.' ,
47
48
File . join ( Msf ::Config . install_root , 'data' , 'wordlists' , 'unix_users.txt' )
48
- ]
49
- ) ] , self . class )
49
+ ] ) ,
50
+ OptBool . new ( 'UNIXONLY' , [ true , 'Skip Microsoft bannered servers when testing unix users' , true ] )
51
+ ] , self . class )
50
52
51
53
deregister_options ( 'MAILTO' , 'MAILFROM' )
52
54
end
53
55
54
- def target
55
- "#{ rhost } :#{ rport } "
56
- end
57
-
58
- def smtp_send ( data = nil , con = true )
56
+ def smtp_send ( data = nil )
59
57
begin
60
- @result = ''
61
- @coderesult = ''
62
- if ( con )
63
- @connected = false
64
- connect
65
- select ( nil , nil , nil , 0.4 )
66
- end
67
- @connected = true
58
+ result = ''
59
+ code = 0
68
60
sock . put ( "#{ data } " )
69
- @result = sock . get_once
70
- @coderesult = @result [ 0 ..2 ] if @result
61
+ result = sock . get_once
62
+ result . chomp! if ( result )
63
+ code = result [ 0 ..2 ] . to_i if result
64
+ return result , code
65
+ rescue Rex ::ConnectionError , Errno ::ECONNRESET , ::EOFError
66
+ return result , code
71
67
rescue ::Exception => e
72
- print_error ( "Error: #{ e } " )
73
- raise e
68
+ print_error ( "#{ rhost } : #{ rport } Error smtp_send: ' #{ e . class } ' ' #{ e } ' " )
69
+ return nil , 0
74
70
end
75
71
end
76
72
77
73
def run_host ( ip )
78
- @users_found = { }
79
- @mails_found = { }
74
+ users_found = { }
75
+ result = nil # temp for storing result of SMTP request
76
+ code = 0 # status code parsed from result
77
+ vrfy = true # if vrfy allowed
78
+ expn = true # if expn allowed
79
+ rcpt = true # if rcpt allowed and useful
80
+ usernames = extract_words ( datastore [ 'USER_FILE' ] )
81
+
80
82
cmd = 'HELO' + " " + "localhost" + "\r \n "
81
- smtp_send ( cmd , true )
82
- print_status ( banner )
83
- @domain = @result . split ( ) [ 1 ] . split ( "." ) [ 1 ..-1 ] . join ( "." )
84
- print_status ( "Domain Name: #{ @domain } " )
83
+ connect
84
+ result , code = smtp_send ( cmd )
85
85
86
- begin
87
- cmd = 'VRFY' + " " + "root" + "\r \n "
88
- smtp_send ( cmd , !@connected )
89
- if ( @result . match ( %r{Cannot} ) ) or ( @result . match ( %r{recognized} ) )
90
- vprint_status ( "VRFY command disabled" )
91
- elsif ( @result . match ( %r{restricted} ) )
92
- vprint_status ( "VRFY command restricted" )
93
- else
94
- vprint_status ( "VRFY command enabled" )
95
- vrfy_ok = true
96
- end
86
+ if ( not result )
87
+ print_error ( "#{ rhost } :#{ rport } Connection but no data...skipping" )
88
+ return
97
89
end
90
+ banner . chomp! if ( banner )
91
+ if ( banner =~ /microsoft/i and datastore [ 'UNIXONLY' ] )
92
+ print_status ( "#{ rhost } :#{ rport } Skipping microsoft (#{ banner } )" )
93
+ return
94
+ elsif ( banner )
95
+ print_status ( "#{ rhost } :#{ rport } Banner: #{ banner } " )
96
+ end
97
+
98
+ domain = result . split ( ) [ 1 ]
99
+ domain = 'localhost' if ( domain == '' or not domain or domain . downcase == 'hello' )
98
100
99
- begin
100
- if ( vrfy_ok )
101
- extract_words ( datastore [ 'USER_FILE' ] ) . each { |user |
102
- do_vrfy_enum ( user )
103
- }
104
- else
105
- do_mail_from ( )
106
- extract_words ( datastore [ 'USER_FILE' ] ) . each { |user |
107
- return finish_host ( ) if ( ( do_rcpt_enum ( user ) ) == :abort )
108
- }
109
- end
110
101
111
- if ( @users_found . empty? )
112
- print_status ( "#{ target } No users or e-mail addresses found." )
102
+ vprint_status ( "#{ ip } :#{ rport } Domain Name: #{ domain } " )
103
+
104
+ result , code = smtp_send ( "VRFY root\r \n " )
105
+ vrfy = ( code == 250 )
106
+ users_found = do_enum ( 'VRFY' , usernames ) if ( vrfy )
107
+
108
+ if ( users_found . empty? )
109
+ # VRFY failed, lets try EXPN
110
+ result , code = smtp_send ( "EXPN root\r \n " )
111
+ expn = ( code == 250 )
112
+ users_found = do_enum ( 'EXPN' , usernames ) if ( expn )
113
+ end
114
+
115
+ if ( users_found . empty? )
116
+ # EXPN/VRFY failed, drop back to RCPT TO
117
+ result , code = smtp_send ( "MAIL FROM: root\@ #{ domain } \r \n " )
118
+ if ( code == 250 )
119
+ user = Rex ::Text . rand_text_alpha ( 8 )
120
+ result , code = smtp_send ( "RCPT TO: #{ user } \@ #{ domain } \r \n " )
121
+ if ( code >= 250 and code <= 259 )
122
+ vprint_status ( "#{ rhost } :#{ rport } RCPT TO: Allowed for random user (#{ user } )...not reliable? #{ code } '#{ result } '" )
123
+ rcpt = false
124
+ else
125
+ smtp_send ( "RSET\r \n " )
126
+ users_found = do_rcpt_enum ( domain , usernames )
127
+ end
113
128
else
114
- vprint_status ( "#{ target } - SMTP - Trying to get valid e-mail addresses" )
115
- @users_found . keys . each { |mails |
116
- return finish_host ( ) if ( ( do_get_mails ( mails ) ) == :abort )
117
- }
118
- finish_host ( )
119
- disconnect
129
+ rcpt = false
120
130
end
121
131
end
122
- end
123
132
124
- def finish_host ( )
125
- if @users_found && !@users_found . empty?
126
- print_good ( "#{ target } Users found: #{ @users_found . keys . sort . join ( ", " ) } " )
127
- report_note (
128
- :host => rhost ,
129
- :port => rport ,
130
- :type => 'smtp.users' ,
131
- :data => { :users => @users_found . keys . join ( ", " ) }
132
- )
133
+ if ( not vrfy and not expn and not rcpt )
134
+ print_status ( "#{ rhost } :#{ rport } could not be enumerated (no EXPN, no VRFY, invalid RCPT)" )
135
+ return
133
136
end
137
+ finish_host ( users_found )
138
+ disconnect
139
+
140
+ rescue Rex ::ConnectionError , Errno ::ECONNRESET , Rex ::ConnectionTimeout , EOFError , Errno ::ENOPROTOOPT
141
+ rescue ::Exception => e
142
+ print_error ( "Error: #{ rhost } :#{ rport } '#{ e . class } ' '#{ e } '" )
143
+ end
134
144
135
- if ( @mails_found . nil? or @mails_found . empty? )
136
- print_status ( "#{ target } No e-mail addresses found." )
137
- else
138
- print_good ( "#{ target } E-mail addresses found: #{ @mails_found . keys . sort . join ( ", " ) } " )
145
+ def finish_host ( users_found )
146
+ if users_found and not users_found . empty?
147
+ print_good ( "#{ rhost } :#{ rport } Users found: #{ users_found . sort . join ( ", " ) } " )
139
148
report_note (
140
149
:host => rhost ,
141
150
:port => rport ,
142
- :type => 'smtp.mails ' ,
143
- :data => { :mails => @mails_found . keys . join ( ", " ) }
151
+ :type => 'smtp.users ' ,
152
+ :data => { :users => users_found . join ( ", " ) }
144
153
)
145
154
end
146
155
end
147
156
148
- def do_vrfy_enum ( user )
149
- cmd = 'VRFY' + " " + user + "\r \n "
150
- smtp_send ( cmd , !@connected )
151
- vprint_status ( "#{ target } - SMTP - Trying name: '#{ user } '" )
152
- case @coderesult . to_i
153
- when ( 250 ..259 )
154
- print_good "#{ target } - Found user: #{ user } "
155
- @users_found [ user ] = :reported
156
- mail = @result . scan ( %r{\< (.*)(@)(.*)\> } )
157
- unless ( mail . nil? || mail . empty? )
158
- @mails_found [ mail . to_s ] = :reported
159
- end
160
- end
157
+ def kiss_and_make_up ( cmd )
158
+ vprint_status ( "#{ rhost } :#{ rport } SMTP server annoyed...reconnecting and saying HELO again..." )
159
+ disconnect
160
+ connect
161
+ smtp_send ( "HELO localhost\r \n " )
162
+ result , code = smtp_send ( "#{ cmd } " )
163
+ result . chomp!
164
+ cmd . chomp!
165
+ vprint_status ( "#{ rhost } :#{ rport } - SMTP - Re-trying #{ cmd } received #{ code } '#{ result } '" )
166
+ return result , code
161
167
end
162
168
163
- def do_mail_from ( )
164
- vprint_status ( "Trying to use to RCPT TO command" )
165
- cmd = 'MAIL FROM:' + " root@" + @domain + " \r \n "
166
- smtp_send ( cmd , ! @connected )
167
- if ( @coderesult == '501' ) && @domain . split ( "." ) . count > 2
168
- print_error " #{ target } - MX domain failure for #{ @domain } , trying #{ @domain . split ( / \. / ) . slice ( - 2 , 2 ) . join ( "." ) } "
169
- cmd = 'MAIL FROM:' + " root@" + @domain . split ( / \. / ) . slice ( - 2 , 2 ) . join ( "." ) + " \r \n "
170
- smtp_send ( cmd , ! @connected )
171
- if ( @coderesult == '501' )
172
- print_error "#{ target } - MX domain failure for #{ @domain . split ( / \. / ) . slice ( - 2 , 2 ) . join ( "." ) } "
173
- return :abort
169
+ def do_enum ( cmd , usernames )
170
+
171
+ users = [ ]
172
+ usernames . each { | user |
173
+ next if user . downcase == 'root'
174
+ result , code = smtp_send ( " #{ cmd } #{ user } \r \n " )
175
+ vprint_status ( " #{ rhost } : #{ rport } - SMTP - Trying #{ cmd } #{ user } received #{ code } ' #{ result } '" )
176
+ result , code = kiss_and_make_up ( " #{ cmd } #{ user } \r \n " ) if ( code == 0 and result . to_s == '' )
177
+ if ( code == 250 )
178
+ vprint_status ( "#{ rhost } : #{ rport } - Found user: #{ user } " )
179
+ users . push ( user )
174
180
end
175
- elsif ( @coderesult == '501' )
176
- print_error "#{ target } - MX domain failure for #{ @domain } "
177
- return :abort
178
- end
181
+ }
182
+ return users
179
183
end
180
184
181
- def do_rcpt_enum ( user )
182
- cmd = 'RCPT TO:' + " " + user + "\r \n "
183
- smtp_send ( cmd , !@connected )
184
- vprint_status ( "#{ target } - SMTP - Trying name: '#{ user } '" )
185
- case @coderesult . to_i
186
- # 550 is User unknown, which obviously isn't fatal when trying to
187
- # enumerate users, so only abort on other 500-series errors. See #4031
188
- when ( 500 ..549 ) , ( 551 ..599 )
189
- print_error "#{ target } : #{ @result . strip if @result } "
190
- print_error "#{ target } : Enumeration not possible"
191
- return :abort
192
- when ( 250 ..259 )
193
- print_good "#{ target } - Found user: #{ user } "
194
- @users_found [ user ] = :reported
195
- mail = @result . scan ( %r{\< (.*)(@)(.*)\> } )
196
- unless ( mail . nil? || mail . empty? )
197
- @mails_found [ mail . to_s ] = :reported
198
- end
199
- end
200
- end
185
+ def do_rcpt_enum ( domain , usernames )
186
+ users = [ ]
187
+ usernames . each { |user |
188
+ next if user . downcase == 'root'
189
+ vprint_status ( "#{ rhost } :#{ rport } - SMTP - Trying MAIL FROM: root\@ #{ domain } / RCPT TO: #{ user } ..." )
190
+ result , code = smtp_send ( "MAIL FROM: root\@ #{ domain } \r \n " )
191
+ result , code = kiss_and_make_up ( "MAIL FROM: root\@ #{ domain } \r \n " ) if ( code == 0 and result . to_s == '' )
192
+
193
+ if ( code == 250 )
194
+ result , code = smtp_send ( "RCPT TO: #{ user } \@ #{ domain } \r \n " )
195
+ if ( code == 0 and result . to_s == '' )
196
+ kiss_and_make_up ( "MAIL FROM: root\@ #{ domain } \r \n " )
197
+ result , code = smtp_send ( "RCPT TO: #{ user } \@ #{ domain } \r \n " )
198
+ end
201
199
202
- def do_get_mails ( user )
203
- cmd = 'EXPN' + " " + user + "\r \n "
204
- smtp_send ( cmd , !@connected )
205
- if ( @coderesult == '502' )
206
- print_error "#{ target } - EXPN : #{ @result . strip if @result } "
207
- return :abort
208
- else
209
- unless ( @result . nil? || @result . empty? )
210
- mail = @result . scan ( %r{\< (.*)(@)(.*)\> } )
211
- unless ( mail . nil? || mail . empty? )
212
- print_good "#{ target } - Mail Found: #{ mail } "
213
- @mails_found [ mail . to_s ] = :reported
200
+ if ( code == 250 )
201
+ vprint_status ( "#{ rhost } :#{ rport } - Found user: #{ user } " )
202
+ users . push ( user )
214
203
end
204
+ else
205
+ vprint_status ( "#{ rhost } :#{ rport } MAIL FROM: #{ user } NOT allowed during brute...aborting ( '#{ code } ' '#{ result } ')" )
206
+ break
215
207
end
216
- end
208
+ smtp_send ( "RSET\r \n " )
209
+ }
210
+ return users
217
211
end
218
212
219
213
def extract_words ( wordfile )
220
214
return [ ] unless wordfile && File . readable? ( wordfile )
221
- begin
222
- words = File . open ( wordfile , "rb" ) { |f | f . read }
223
- rescue
224
- return
225
- end
215
+ words = File . open ( wordfile , "rb" ) { |f | f . read }
226
216
save_array = words . split ( /\r ?\n / )
227
217
return save_array
228
218
end
0 commit comments