11class NXCModule :
22 """
3- Enumerate SQL Server logins
4- Module by deathflamingo
3+ Enumerate SQL Server logins (SQL, Domain, Local users)
4+ Module by deathflamingo, modified by mpgn
55 """
66
77 name = "enum_logins"
8- description = "Enumerate SQL Server logins"
8+ description = "Enumerate SQL Server logins (SQL, Domain, Local users) "
99 supported_protocols = ["mssql" ]
1010 opsec_safe = True
1111 multiple_hosts = True
@@ -17,25 +17,69 @@ def __init__(self):
1717 def on_login (self , context , connection ):
1818 self .context = context
1919 self .mssql_conn = connection .conn
20+
2021 logins = self .get_logins ()
2122 if logins :
22- self .context .log .success ("Logins found:" )
23- for login in logins :
24- self .context .log .display (f" - { login } " )
23+ self .context .log .display ("Enumerated logins" )
24+ self .context .log .highlight (f"{ 'Login Name' :<35} { 'Type' :<15} { 'Status' } " )
25+ self .context .log .highlight (f"{ '----------' :<35} { '----' :<15} { '------' } " )
26+ for login_name , login_type , status in logins :
27+ self .context .log .highlight (f"{ login_name :<35} { login_type :<15} { status } " )
2528 else :
2629 self .context .log .fail ("No logins found." )
2730
31+ def get_domain_name (self ) -> str :
32+ query = "SELECT DEFAULT_DOMAIN() as domain_name;"
33+ try :
34+ res = self .mssql_conn .sql_query (query )
35+ if res and res [0 ].get ("domain_name" ):
36+ return res [0 ]["domain_name" ].upper ()
37+ return ""
38+ except Exception as e :
39+ self .context .log .debug (f"Error querying domain name: { e } " )
40+ return ""
41+
2842 def get_logins (self ) -> list :
29- """
30- Fetches a list of SQL Server logins.
43+ domain_name = self . get_domain_name ()
44+ domain_prefix = f" { domain_name } \\ " if domain_name else ""
3145
32- Returns
33- -------
34- list: List of login names.
46+ query = f"""
47+ SELECT
48+ name,
49+ type,
50+ type_desc,
51+ CASE type_desc
52+ WHEN 'SQL_LOGIN' THEN 'SQL User'
53+ WHEN 'WINDOWS_LOGIN' THEN
54+ CASE
55+ WHEN name LIKE '{ domain_prefix } %' THEN 'Domain User'
56+ WHEN name LIKE '%\\ %' THEN 'Local User'
57+ ELSE 'Local User'
58+ END
59+ WHEN 'WINDOWS_GROUP' THEN 'Windows Group'
60+ WHEN 'CERTIFICATE_MAPPED_LOGIN' THEN 'Certificate Login'
61+ WHEN 'ASYMMETRIC_KEY_MAPPED_LOGIN' THEN 'Asymmetric Key Login'
62+ ELSE type_desc
63+ END as login_type,
64+ is_disabled,
65+ create_date
66+ FROM sys.server_principals
67+ WHERE type IN ('S', 'U', 'G', 'C', 'K')
68+ AND name NOT LIKE '##%'
69+ ORDER BY login_type, name;
3570 """
36- query = "SELECT name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN';"
37- res = self .mssql_conn .sql_query (query )
38- return [login ["name" ] for login in res ] if res else []
71+ try :
72+ res = self .mssql_conn .sql_query (query )
73+ if res :
74+ logins = []
75+ for login in res :
76+ status = "DISABLED" if login .get ("is_disabled" ) else "ENABLED"
77+ logins .append ((login ["name" ], login ["login_type" ], status ))
78+ return logins
79+ return []
80+ except Exception as e :
81+ self .context .log .fail (f"Error querying logins: { e } " )
82+ return []
3983
4084 def options (self , context , module_options ):
4185 pass
0 commit comments