Auditing Default Credentials with a Custom Python Script
From time to time I need to audit a large number of devices for default credentials. I typically use Metasploits http_login module for this. You can pass it a file of rhosts with something like set rhosts file://root/hosts.txt
and have it check numerous systems quickly. However, a recent system wasn't playing well with this module for reasons I still don't understand. No matter what settings I tried, I kept getting No URI found that asks for HTTP authentication
. Fine, I'll go make my own toys.
Setting up Burp and proxies (potentially optional)
A lot of times when I'm looking for default credentials across numerous devices, I'm looking at crappy, old, and unpatched IoT "stuff". A lot of these, if they offer encryption at all, are using old versions of SSL (and I do mean SSL, as in v2 or v3), old cipher suites, self-signed/untrusted certificates, etc. In this case, I get the following error from Metasploit (ruby) OpenSSL::SSL::SSLError SSL_connect returned=1 errno=0 state=error: sslv3 alert handshake failure
. In the case of the python script we're getting to, I would get OpenSSL.SSL.Error: [('SSL routines', 'ssl_choose_client_version', 'unsupported protocol')]
.
The easiest solution is to force everything through Burp honeybadger, Burp honeybadger don't care. You can find more information on this here: https://www.th3r3p0.com/random/python-requests-and-burp-suite.html
Grab the Burp certificate from http://burp/ and convert it to a .pem format
openssl x509 -inform der -in cacert.der -out certificate.pem
Use the following environment variables to force everything through Burp and ignore Python warnings (adjust for your path and ports).
export REQUESTS_CA_BUNDLE="/root/Downloads/certificate.pem"
export HTTP_PROXY="http://127.0.0.1:8082"
export HTTPS_PROXY="http://127.0.0.1:8082"
export PYTHONWARNINGS="ignore:Unverified HTTPS request"
Undo later with
unset REQUESTS_CA_BUNDLE
unset HTTP_PROXY
unset HTTPS_PROXY
unset PYTHONWARNINGS
Building Our Request
We are going to need Burp anyway. Find a device you want to test and, while the proxy is running (you don't have to be actively intercepting something), make a valid login attempt. In Burp, go into Proxy -> HTTP history, and find this request. Right click on it and choose "Copy as curl command".
curl -i -s -k -X $'POST' \
-H $'Host: 192.168.2.3' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: https://192.168.2.3/logon.shtm' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 32' -H $'Connection: close' -H $'Upgrade-Insecure-Requests: 1' \
--data-binary $'UserName=administrator&PassWord=123456' \
$'https://192.168.2.3/~logon_controller'
Using https://curl.trillworks.com/# or https://github.com/NickCarneiro/curlconverter, convert this cURL command into a Python request:
import requests
headers = {
'$Host': '192.168.2.3',
'$User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0',
'$Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'$Accept-Language': 'en-US,en;q=0.5',
'$Accept-Encoding': 'gzip, deflate',
'$Referer': 'https://192.168.2.3/logon.shtm',
'$Content-Type': 'application/x-www-form-urlencoded',
'$Content-Length': '32',
'$Connection': 'close',
'$Upgrade-Insecure-Requests': '1',
}
data = '$UserName=administrator&PassWord=123456'
response = requests.post('http://$https://192.168.2.3/~logon_controller', headers=headers, data=data, verify=False)
Something a bit weird here. Note the $
before each header field and the data field, remove those. Also remove the http://$
at the beginning of the request.post function.
Wrap it all in a Python script
Things to do:
- Store a list of IP addresses in hosts.txt
- Get string values that can be used to detect successful or failed logins
- Set these in the if, elif, else statements at the bottom
- Replace the actual IP addresses in our request above with our ip variable from the hosts.txt file (3 places)
#!/usr/bin/python3
import requests
with open('hosts.txt') as file:
for ip in file:
ip = ip.strip()
headers = {
'Host': ip,
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Referer': 'https://'+ip+'/logon.shtm',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '32',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
}
data = 'UserName=administrator&PassWord=123456'
response = requests.post('https://'+ip'+/~logon_controller', headers=headers, data=data, verify=False)
if 'User already logged in' in str(response.content):
print(ip+" - Login Successful (already logged in)")
elif 'Welcome administrator' in str(response.content):
print(ip+" - Login Successful")
elif 'Incorrect username or password.' in str(response.content):
print(ip+" - Incorrect username or password")
else:
print(ip+" - Something wrong")
Edit: Missed the ip variables, corrected now.