-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
RDP Web Login User Enumeration Auxiliary Module #14544
Conversation
Thanks for your pull request! Before this pull request can be merged, it must pass the checks of our automated linting tools. We use Rubocop and msftidy to ensure the quality of our code. This can be ran from the root directory of Metasploit:
You can automate most of these changes with the
Please update your branch after these have been made, and reach out if you have any problems. |
@k0pak4 Can also run |
I've fixed the outputs from running msftidy on the documentation and module, and additionally went through and cleaned up additional pylint warnings for PEP-8 compliance since this is a python module. Let me know if there's anything else I can do! |
Hi @k0pak4, this looks nifty! It looks like the endpoint you are hitting could take a password if we wanted it to. Do you think this could be converted to a full login scanner like https://github.com/rapid7/metasploit-framework/blob/master/lib/metasploit/framework/login_scanner/jenkins.rb ? Either way, we will want to capture the results of the user enumeration in the database. On the Ruby side, the report will need to look something like
module.report_valid_username(username, 'domain': domain) .
|
Thanks! Yeah, it can take a password in the request as well. I'll first work on reporting the username enumeration to the database, because then the module will at least be complete and usable for that purpose and then afterwards work on converting it to a full login scanner with password checks too. |
@acammack-r7 I was able to report valid usernames with my latest commit, but I don't think I'm adding on the domain correctly. It was my understanding I should add it to the realm field but when I do so it doesn't appear in the realm column of the creds output. Any thoughts on what I may be doing wrong to allow for |
@acammack-r7 I've now converted this to a full login scanner that can check passwords as well. I still am looking for assistance with the domain reporting, but it otherwise works as intended. I'll update the documentation as well early next week |
@acammack-r7 Alright documentation has been updated and the password feature is fully tested. Here's a screenshot of the usage if that helps with the review! |
Thanks for the contribution! I just ran through some basic testing on this using a Windows 2016 DC I have. I am not sure if I am sold on the behavior when there's no connectionI'm not super familiar with login scanners, but a couple things struck me as odd. At first, I did not have the right service installed on the target, and when I ran it anyway, I got:
If you cannot talk to the service, does that count as an invalid name? Is that normal for login scanners? I figure in the case of a TCP service, if no connection can be made, that's a separate error? After I got the service up and running, I ran it again, and got the result I was expecting with a single name
When I used a list, things got oddFor lack of anything else that appeared, I tossed the unix_users list; I figured that I knew `Administrator` was a known user, and that likely there would be some failed names in there, too. That said, all came back as valid users and were added to the database credential store. ``` msf6 auxiliary(scanner/http/rdp_web_login) > set username data/wordlists/unix_users.txt username => data/wordlists/unix_users.txt msf6 auxiliary(scanner/http/rdp_web_login) > run[] Running for 192.168.134.140...
|
@bwatters-r7 thanks for the feedback and for going through the testing! I also tested a 2016 DC so it's nice to see it works there as well. As for the three items:
|
@bwatters-r7 I did some more investigation into your problems with the long user list. I can recreate the issue when the password is blank but making it any string fixes it. I ran through this with the module and in BurpSuite and it appears that when authentication attempts with blank passwords are made, the timing attack is ineffective. I didn't notice it initially, because I have the password default to 'wrong'. When any string is put into the password field, the timing discrepancies appear again |
Let me try to give you some more info; I may have glossed over some things, and also I don't have the depth of knowledge on some aspects on this technique For the timeout case, it looks like if you get the response 302, the password and username are good (successful), if you get the response 200, the password is invalid, but the username is valid. We'd assume that a timeout was a separate issue? Therefore, I would propose to simply pass the exception through like https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/ssl/bleichenbacher_oracle.py#L154 I can't do too much about the domain reporting without doing a lot more digging. @acammack-r7 might be able to chime in quick, but otherwise, I'll start digging. |
Interesting, the module you referenced is using raw sockets though where I'm using the requests library. So the idea was that rather than wait for the response and then check that it is passed the user set threshold, that by using a requests timeout the module would be faster. In my initial testing, invalid authentication attempts were taking over 4 seconds, whereas valid usernames take around 200 milliseconds. Thus, I set up the auth to go like the following:
I could change it to the following:
|
I think I've started this reply a half dozen times. First, I appreciate the breakdown you gave above, it really helped me understand what was happening. Second I think we should give the user control over the timeout somehow. Different networks will behave differently, so I could see edge case where users should be able to control the timeout. Likely that would be more of an advanced option with the default values you have found work correctly.
I still owe you an answer on the domain thing, but that's unlikely to come for a couple days. |
Roger. I'll try to move the RDP web server to a different VM in the network so I can test. |
…articular user agents
@bwatters-r7 I haven't run into this SSL cert issue, no. I don't think my most recent set of changes should have caused this either, since I just introduced the user_agent variable in order to get around a new host I was targeting that only allowed certain user agents 🤔 |
When I drop back to b962f41, it works again without the cert error:
|
@bwatters-r7 I see where I went wrong, thanks for helping identify this. I had removed the |
Sweet! I was staring at those user strings, but I did not catch that.
|
Still curious about those weird print format statements. Also, did you ever get the answer on adding the domain to the datastore? |
I tried investigating the print format statements and couldn't find them anywhere in the library, which I found strange. It could just be my greps weren't good enough. And I haven't gotten the answer on the domain either. I felt like I added it right, but for some reason wasn't getting any errors when I added it but it still wasn't showing up in the db. I thought my changes to external.rb would do it, but it hasn't worked |
So I threw out a quick question to the team and got a PR back for the log issue: |
Oh wow that's great! Thanks! |
lib/msf/core/module/external.rb
Outdated
credential_data[:public_data] = data['domain'] | ||
credential_data[:public_type] = :realm |
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.
Instead of changing the credential type, I think this just needs to add a realm to the credential data:
credential_data[:public_data] = data['domain'] | |
credential_data[:public_type] = :realm | |
credential_data[:realm] = data['domain'] |
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.
I just tried this, but it doesn't seem to work either. I'm wondering if the create_credential
function called at the end isn't handling the realm after it's added or something or if I'm still passing it in incorrectly. I've tried adding it with :realm_key as well because i saw that as I was searching but it didn't seem to work either
Edit: I've verified I'm sending the 'domain' in the json:
{'domain': 'DUNN', 'address': '192.168.148.128', 'port': '443', 'protocol': 'tcp', 'service_name': 'RDWeb', 'username': 'k0pak4'}
but for some reason the realm isn't sticking even with the suggested change
lib/msf/core/module/external.rb
Outdated
@@ -152,6 +152,11 @@ def handle_credential_login(data, mod) | |||
credential_data[:private_type] = :password | |||
end | |||
|
|||
if data.has_key?(:domain) |
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.
My apologies, this will also need to be changed since all keys in data
are strings:
if data.has_key?(:domain) | |
if data.has_key?('domain') |
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.
So this isn't working either, (though it could be an issue). When I remove the conditional completely, and just blindly add it to the credential_data, it still isn't appearing in the creds database as a realm. I've tried this multiple ways, including:
credential_data.merge({realm: data['domain']})
credential_data[:realm] = data['domain']
So I'm not sure why it isn't getting added at this point 😕
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.
@acammack-r7 I got it! It turns out the fix was using :realm_key and :realm_value. I then added this is to password reporting, since it didn't exist there either. This should be good to go now!
@acammack-r7 @bwatters-r7 I'm finished with requested updates now with the domain reporting complete. I updated the documentation to reflect the module updates as well, and have rerun msftidy on the docs to verify compliance as well as running pylint on the created module. Let me know what else I can do to help get this ready for approval |
Hi there, @k0pak4 ! Thanks so much for the updates. |
@bwatters-r7 no worries! Keep yourself and the team safe! This will still be here afterwards 🙂 |
OK; back up and running. Now, I'm still not getting the timing. I've got the web_rdp running on a separate VM from the DC, and the timing just does not seem to be there: Output
|
@bwatters-r7 I'm trying to think of other things that might be causing the difference. Are any of the ones you're using supposed to be valid? I suspected the Administrator one likely was. Based on the output you input the domain yourself, does the domain discovery feature work on this host? |
@k0pak4 I know Administrator is valid. Also, no, apparently the domain discovery does not work, either?
|
Hey- found the issue. My DC migrated into another network, so the rdp server could not talk to it, and to get the domain autodetect working, I enabled the rpc over http feature.
|
Release NotesNew module |
The Microsoft RD Web login is vulnerable to the same type of authentication username enumeration vulnerability that is present for OWA (see owa scanner modules). By analyzing the time it takes for a failed response, the RDWeb interface can be used to quickly test the validity of a set of usernames. Additionally, this module can attempt to discover the target NTLM domain if you don't already know it.
Verification
msfconsole
use auxiliary/scanner/http/rdp_web_login
set rhost TARGET_IP
set username USER_OR_FILE
set domain DOMAIN
(Only if you don't want to test the domain discovery feature)Scenarios
Specific target output replaced with Ys so as not to disclose information
Version and OS
Tested against Microsoft IIS 10.0 and RDWeb 2019