HashID and SSLScan plugins created
This commit is contained in:
79
README.md
79
README.md
@@ -269,6 +269,85 @@ Comprehensive HTTP security header analysis with security scoring and recommenda
|
||||
!headers subdomain.target.com
|
||||
```
|
||||
|
||||
### 🔐 Hash Identification
|
||||
|
||||
**🔄 !hashid [hash]**
|
||||
Advanced hash type identification with confidence scoring and tool recommendations.
|
||||
|
||||
**Features:**
|
||||
- **Comprehensive Detection**: 100+ hash types including modern, legacy, and exotic algorithms
|
||||
- **Confidence Scoring**: Color-coded confidence levels (🟢 Very High to 🔴 Low)
|
||||
- **Tool Integration**: Hashcat mode numbers and John the Ripper format names
|
||||
- **Context-Aware**: Handles modular crypt formats, LDAP, database, and network hashes
|
||||
|
||||
**Supported Hash Categories:**
|
||||
- **Modern Algorithms**: yescrypt, scrypt, Argon2 (i/d/id), bcrypt variants
|
||||
- **Unix/Linux**: SHA-512/256 Crypt, MD5 Crypt, Apache MD5 (apr1)
|
||||
- **Raw Hashes**: MD5, SHA-1/224/256/384/512, SHA-3, Keccak, BLAKE2
|
||||
- **Windows**: NTLM, LM, NetNTLMv1/v2
|
||||
- **Databases**: MySQL (4.1+, old), PostgreSQL, Oracle (11g, 12c), MSSQL
|
||||
- **Web/CMS**: WordPress, phpBB3, Drupal 7+, Django PBKDF2
|
||||
- **LDAP**: SSHA, SMD5, various LDAP crypt formats
|
||||
- **Exotic**: Whirlpool, RIPEMD, GOST, Tiger, Haval
|
||||
|
||||
**Tool Integration:**
|
||||
- **Hashcat**: Mode numbers for direct use with `-m` parameter
|
||||
- **John the Ripper**: Format names for `--format=` parameter
|
||||
- **Multi-tool Support**: Works with most popular password cracking tools
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
!hashid 5d41402abc4b2a76b9719d911017c592
|
||||
!hashid aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
|
||||
!hashid $6$rounds=5000$salt$hashvalue...
|
||||
!hashid $y$j9T$... (modern Linux yescrypt)
|
||||
!hashid 8846f7eaee8fb117ad06bdd830b7586c
|
||||
!hashid *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19
|
||||
```
|
||||
|
||||
### 🔐 SSL/TLS Security Scanner
|
||||
|
||||
**🔐 !sslscan [domain[:port]]**
|
||||
Comprehensive SSL/TLS security scanning and analysis with vulnerability detection.
|
||||
|
||||
**Features:**
|
||||
- **Protocol Analysis**: TLS 1.0-1.3 support testing with security scoring
|
||||
- **Certificate Validation**: Chain validation, expiration, signature algorithms
|
||||
- **Cipher Suite Testing**: 25+ cipher suites with strength classification
|
||||
- **Vulnerability Detection**: POODLE, weak ciphers, protocol vulnerabilities
|
||||
- **Security Scoring**: 0-100 rating with color-coded assessment
|
||||
- **Compliance Checking**: PCI DSS and modern security standards
|
||||
|
||||
**Security Checks:**
|
||||
- **Protocol Security**: TLS 1.2/1.3 enforcement, insecure protocol detection
|
||||
- **Certificate Health**: Expiration monitoring, signature strength validation
|
||||
- **Cipher Security**: RC4, DES, 3DES, NULL cipher detection and classification
|
||||
- **Modern Standards**: Forward Secrecy, strong encryption, best practices
|
||||
|
||||
**Output Features:**
|
||||
- **Security Score**: Overall rating (🟢 Excellent to 🔴 Poor)
|
||||
- **Detailed Breakdown**: Protocol support, cipher analysis, certificate info
|
||||
- **Vulnerability List**: CVE references and severity ratings
|
||||
- **Actionable Recommendations**: Specific fixes and configuration improvements
|
||||
- **Quick Assessment**: Executive summary for rapid evaluation
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
!sslscan example.com
|
||||
!sslscan github.com:443
|
||||
!sslscan localhost:8443
|
||||
!sslscan 192.168.1.1:443
|
||||
```
|
||||
🟢 Excellent (90-100): Modern TLS configuration with strong security
|
||||
🟡 Good (80-89): Good security with minor improvements needed
|
||||
🟠 Fair (60-79): Moderate security, significant improvements recommended
|
||||
🔴 Poor (0-59): Critical security issues requiring immediate attention
|
||||
|
||||
*Note: SSLv2/SSLv3 testing limited by Python security features (intentional security measure)*
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### AI & Generation Commands
|
||||
|
||||
|
@@ -22,7 +22,8 @@ ALLOWED_PLUGINS = {
|
||||
'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
|
||||
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
|
||||
'xkcd', 'youtube-preview', 'youtube-search', 'weather', 'urbandictionary',
|
||||
'bitcoin', 'dns', 'shodan', 'dnsdumpster', 'exploitdb', 'headers'
|
||||
'bitcoin', 'dns', 'shodan', 'dnsdumpster', 'exploitdb', 'headers', 'hashid',
|
||||
'sslscan'
|
||||
}
|
||||
|
||||
class FunguyBot:
|
||||
|
386
plugins/hashid.py
Normal file
386
plugins/hashid.py
Normal file
@@ -0,0 +1,386 @@
|
||||
"""
|
||||
This plugin provides a command to identify hash types using comprehensive pattern matching.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import simplematrixbotlib as botlib
|
||||
|
||||
def identify_hash(hash_string):
|
||||
"""
|
||||
Identify the hash type based on comprehensive pattern matching.
|
||||
|
||||
Args:
|
||||
hash_string (str): The hash string to identify
|
||||
|
||||
Returns:
|
||||
list: List of tuples (hash_type, hashcat_mode, john_format, confidence)
|
||||
"""
|
||||
|
||||
hash_string = hash_string.strip()
|
||||
hash_lower = hash_string.lower()
|
||||
length = len(hash_string)
|
||||
|
||||
possible_types = []
|
||||
|
||||
# Unix crypt and modular crypt formats (most specific first)
|
||||
if hash_string.startswith('$'):
|
||||
# yescrypt (modern Linux /etc/shadow)
|
||||
if re.match(r'^\$y\$', hash_string):
|
||||
possible_types.append(("yescrypt", None, "yescrypt", 95))
|
||||
|
||||
# scrypt
|
||||
elif re.match(r'^\$7\$', hash_string):
|
||||
possible_types.append(("scrypt", "8900", "scrypt", 95))
|
||||
|
||||
# Argon2
|
||||
elif re.match(r'^\$argon2(id?|d)\$', hash_string):
|
||||
if '$argon2i$' in hash_string:
|
||||
possible_types.append(("Argon2i", "10900", "argon2", 95))
|
||||
elif '$argon2d$' in hash_string:
|
||||
possible_types.append(("Argon2d", None, "argon2", 95))
|
||||
elif '$argon2id$' in hash_string:
|
||||
possible_types.append(("Argon2id", "10900", "argon2", 95))
|
||||
|
||||
# bcrypt variants
|
||||
elif re.match(r'^\$(2[abxy]?)\$', hash_string):
|
||||
bcrypt_type = re.match(r'^\$(2[abxy]?)\$', hash_string).group(1)
|
||||
possible_types.append((f"bcrypt ({bcrypt_type})", "3200", "bcrypt", 95))
|
||||
|
||||
# SHA-512 Crypt (common in Linux)
|
||||
elif re.match(r'^\$6\$', hash_string):
|
||||
possible_types.append(("SHA-512 Crypt (Unix)", "1800", "sha512crypt", 95))
|
||||
|
||||
# SHA-256 Crypt (Unix)
|
||||
elif re.match(r'^\$5\$', hash_string):
|
||||
possible_types.append(("SHA-256 Crypt (Unix)", "7400", "sha256crypt", 95))
|
||||
|
||||
# MD5 Crypt (Unix)
|
||||
elif re.match(r'^\$1\$', hash_string):
|
||||
possible_types.append(("MD5 Crypt (Unix)", "500", "md5crypt", 95))
|
||||
|
||||
# Apache MD5
|
||||
elif re.match(r'^\$apr1\$', hash_string):
|
||||
possible_types.append(("Apache MD5 (apr1)", "1600", "md5crypt", 95))
|
||||
|
||||
# AIX SMD5
|
||||
elif re.match(r'^\{smd5\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("AIX {smd5}", "6300", None, 90))
|
||||
|
||||
# AIX SSHA256
|
||||
elif re.match(r'^\{ssha256\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("AIX {ssha256}", "6700", None, 90))
|
||||
|
||||
# AIX SSHA512
|
||||
elif re.match(r'^\{ssha512\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("AIX {ssha512}", "6800", None, 90))
|
||||
|
||||
# phpBB3
|
||||
elif re.match(r'^\$H\$', hash_string):
|
||||
possible_types.append(("phpBB3", "400", "phpass", 90))
|
||||
|
||||
# Wordpress
|
||||
elif re.match(r'^\$P\$', hash_string):
|
||||
possible_types.append(("Wordpress", "400", "phpass", 90))
|
||||
|
||||
# Drupal 7+
|
||||
elif re.match(r'^\$S\$', hash_string):
|
||||
possible_types.append(("Drupal 7+", "7900", "drupal7", 90))
|
||||
|
||||
# WBB3 (Woltlab Burning Board)
|
||||
elif re.match(r'^\$wbb3\$', hash_string):
|
||||
possible_types.append(("WBB3 (Woltlab)", None, None, 85))
|
||||
|
||||
# PBKDF2-HMAC-SHA256
|
||||
elif re.match(r'^\$pbkdf2-sha256\$', hash_string):
|
||||
possible_types.append(("PBKDF2-HMAC-SHA256", "10900", "pbkdf2-hmac-sha256", 90))
|
||||
|
||||
# PBKDF2-HMAC-SHA512
|
||||
elif re.match(r'^\$pbkdf2-sha512\$', hash_string):
|
||||
possible_types.append(("PBKDF2-HMAC-SHA512", None, "pbkdf2-hmac-sha512", 90))
|
||||
|
||||
# Django PBKDF2
|
||||
elif re.match(r'^pbkdf2_sha256\$', hash_string):
|
||||
possible_types.append(("Django PBKDF2-SHA256", "10000", "django", 90))
|
||||
|
||||
# Unknown modular crypt format
|
||||
else:
|
||||
possible_types.append(("Unknown Modular Crypt Format", None, None, 30))
|
||||
|
||||
return possible_types
|
||||
|
||||
# LDAP formats
|
||||
if hash_string.startswith('{'):
|
||||
if re.match(r'^\{SHA\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("LDAP SHA-1", "101", "nsldap", 90))
|
||||
elif re.match(r'^\{SSHA\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("LDAP SSHA (Salted SHA-1)", "111", "nsldaps", 90))
|
||||
elif re.match(r'^\{MD5\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("LDAP MD5", "3210", None, 90))
|
||||
elif re.match(r'^\{SMD5\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("LDAP SMD5 (Salted MD5)", "3211", None, 90))
|
||||
elif re.match(r'^\{CRYPT\}', hash_string, re.IGNORECASE):
|
||||
possible_types.append(("LDAP CRYPT", None, None, 85))
|
||||
return possible_types
|
||||
|
||||
# Check for colon-separated formats (LM:NTLM, username:hash, etc.)
|
||||
if ':' in hash_string:
|
||||
parts = hash_string.split(':')
|
||||
|
||||
# NetNTLMv1 / NetNTLMv2
|
||||
if len(parts) >= 5:
|
||||
possible_types.append(("NetNTLMv2", "5600", "netntlmv2", 85))
|
||||
possible_types.append(("NetNTLMv1", "5500", "netntlm", 75))
|
||||
|
||||
# LM:NTLM format
|
||||
elif len(parts) == 2 and len(parts[0]) == 32 and len(parts[1]) == 32:
|
||||
possible_types.append(("LM:NTLM", "1000", "nt", 90))
|
||||
|
||||
# Username:Hash or similar
|
||||
elif len(parts) == 2:
|
||||
hash_part = parts[1]
|
||||
if len(hash_part) == 32:
|
||||
possible_types.append(("NTLM (with username)", "1000", "nt", 80))
|
||||
elif len(hash_part) == 40:
|
||||
possible_types.append(("SHA-1 (with salt/username)", "110", None, 70))
|
||||
|
||||
# PostgreSQL md5
|
||||
if hash_string.startswith('md5') and len(hash_string) == 35:
|
||||
possible_types.append(("PostgreSQL MD5", "3100", "postgres", 90))
|
||||
|
||||
return possible_types if possible_types else None
|
||||
|
||||
# MySQL formats
|
||||
if hash_string.startswith('*') and length == 41 and re.match(r'^\*[A-F0-9]{40}$', hash_string.upper()):
|
||||
possible_types.append(("MySQL 4.1/5.x", "300", "mysql-sha1", 95))
|
||||
return possible_types
|
||||
|
||||
# Oracle formats
|
||||
if re.match(r'^[A-F0-9]{16}:[A-F0-9]{16}$', hash_string.upper()):
|
||||
possible_types.append(("Oracle 11g", "112", "oracle11", 90))
|
||||
return possible_types
|
||||
|
||||
if re.match(r'^S:[A-F0-9]{60}$', hash_string.upper()):
|
||||
possible_types.append(("Oracle 12c/18c", "12300", "oracle12c", 90))
|
||||
return possible_types
|
||||
|
||||
# MSSQL formats
|
||||
if re.match(r'^0x0100[A-F0-9]{8}[A-F0-9]{40}$', hash_string.upper()):
|
||||
possible_types.append(("MSSQL 2000", "131", "mssql", 90))
|
||||
return possible_types
|
||||
|
||||
if re.match(r'^0x0200[A-F0-9]{8}[A-F0-9]{128}$', hash_string.upper()):
|
||||
possible_types.append(("MSSQL 2012/2014", "1731", "mssql12", 90))
|
||||
return possible_types
|
||||
|
||||
# Base64 pattern check
|
||||
is_base64 = re.match(r'^[A-Za-z0-9+/]+=*$', hash_string) and length % 4 == 0
|
||||
|
||||
# Raw hash identification by length
|
||||
is_hex = re.match(r'^[a-f0-9]+$', hash_lower)
|
||||
|
||||
if is_hex:
|
||||
if length == 16:
|
||||
possible_types.append(("MySQL < 4.1", "200", "mysql", 85))
|
||||
possible_types.append(("Half MD5", None, None, 60))
|
||||
|
||||
elif length == 32:
|
||||
possible_types.append(("MD5", "0", "raw-md5", 80))
|
||||
possible_types.append(("MD4", "900", "raw-md4", 70))
|
||||
possible_types.append(("NTLM", "1000", "nt", 75))
|
||||
possible_types.append(("LM", "3000", "lm", 60))
|
||||
possible_types.append(("RAdmin v2.x", "9900", None, 50))
|
||||
possible_types.append(("Snefru-128", None, None, 40))
|
||||
possible_types.append(("HMAC-MD5 (key = $pass)", "50", None, 50))
|
||||
|
||||
elif length == 40:
|
||||
possible_types.append(("SHA-1", "100", "raw-sha1", 85))
|
||||
possible_types.append(("RIPEMD-160", "6000", "ripemd-160", 65))
|
||||
possible_types.append(("Tiger-160", None, None, 50))
|
||||
possible_types.append(("Haval-160", None, None, 45))
|
||||
possible_types.append(("HMAC-SHA1 (key = $pass)", "150", None, 55))
|
||||
|
||||
elif length == 48:
|
||||
possible_types.append(("Tiger-192", None, None, 70))
|
||||
possible_types.append(("Haval-192", None, None, 65))
|
||||
|
||||
elif length == 56:
|
||||
possible_types.append(("SHA-224", "1300", "raw-sha224", 85))
|
||||
possible_types.append(("Haval-224", None, None, 60))
|
||||
|
||||
elif length == 64:
|
||||
possible_types.append(("SHA-256", "1400", "raw-sha256", 85))
|
||||
possible_types.append(("RIPEMD-256", None, None, 60))
|
||||
possible_types.append(("SHA3-256", "17400", "raw-sha3", 70))
|
||||
possible_types.append(("Keccak-256", "17800", "raw-keccak-256", 70))
|
||||
possible_types.append(("Haval-256", None, None, 50))
|
||||
possible_types.append(("GOST R 34.11-94", "6900", None, 55))
|
||||
possible_types.append(("BLAKE2b-256", None, None, 60))
|
||||
|
||||
elif length == 80:
|
||||
possible_types.append(("RIPEMD-320", None, None, 80))
|
||||
|
||||
elif length == 96:
|
||||
possible_types.append(("SHA-384", "10800", "raw-sha384", 85))
|
||||
possible_types.append(("SHA3-384", "17900", None, 70))
|
||||
possible_types.append(("Keccak-384", None, None, 65))
|
||||
|
||||
elif length == 128:
|
||||
possible_types.append(("SHA-512", "1700", "raw-sha512", 85))
|
||||
possible_types.append(("Whirlpool", "6100", "whirlpool", 75))
|
||||
possible_types.append(("SHA3-512", "17600", None, 70))
|
||||
possible_types.append(("Keccak-512", None, None, 65))
|
||||
possible_types.append(("BLAKE2b-512", None, None, 60))
|
||||
|
||||
# Base64 encoded hashes
|
||||
elif is_base64:
|
||||
if length == 24:
|
||||
possible_types.append(("MD5 (Base64)", None, None, 75))
|
||||
elif length == 28:
|
||||
possible_types.append(("SHA-1 (Base64)", None, None, 75))
|
||||
elif length == 32:
|
||||
possible_types.append(("SHA-224 (Base64)", None, None, 75))
|
||||
elif length == 44:
|
||||
possible_types.append(("SHA-256 (Base64)", None, None, 75))
|
||||
elif length == 64:
|
||||
possible_types.append(("SHA-384 (Base64)", None, None, 75))
|
||||
elif length == 88:
|
||||
possible_types.append(("SHA-512 (Base64)", None, None, 75))
|
||||
|
||||
return possible_types if possible_types else [("Unknown", None, None, 0)]
|
||||
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Function to handle the !hashid command.
|
||||
|
||||
Args:
|
||||
room (Room): The Matrix room where the command was invoked.
|
||||
message (RoomMessage): The message object containing the command.
|
||||
bot (Bot): The bot object.
|
||||
prefix (str): The command prefix.
|
||||
config (dict): Configuration parameters.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("hashid"):
|
||||
logging.info("Received !hashid command")
|
||||
|
||||
args = match.args()
|
||||
|
||||
if len(args) < 1:
|
||||
usage_msg = """<strong>🔐 Hash Identifier Usage</strong>
|
||||
|
||||
<strong>Usage:</strong> <code>!hashid <hash></code>
|
||||
|
||||
<strong>Examples:</strong>
|
||||
• <code>!hashid 5f4dcc3b5aa765d61d8327deb882cf99</code>
|
||||
• <code>!hashid 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8</code>
|
||||
• <code>!hashid $6$rounds=5000$salt$hash...</code>
|
||||
• <code>!hashid $y$j9T$...</code> (yescrypt from /etc/shadow)
|
||||
|
||||
<strong>Supported Hash Types:</strong>
|
||||
• <strong>Modern:</strong> yescrypt, scrypt, Argon2, bcrypt
|
||||
• <strong>Unix Crypt:</strong> SHA-512 Crypt, SHA-256 Crypt, MD5 Crypt
|
||||
• <strong>Raw Hashes:</strong> MD5, SHA-1/224/256/384/512, SHA-3, NTLM, LM
|
||||
• <strong>Database:</strong> MySQL, PostgreSQL, Oracle, MSSQL
|
||||
• <strong>CMS:</strong> Wordpress, phpBB3, Drupal, Django
|
||||
• <strong>LDAP:</strong> SSHA, SMD5, and various LDAP formats
|
||||
• <strong>Network:</strong> NetNTLMv1/v2, Kerberos
|
||||
• <strong>Exotic:</strong> Whirlpool, RIPEMD, BLAKE2, Keccak, GOST
|
||||
"""
|
||||
await bot.api.send_markdown_message(room.room_id, usage_msg)
|
||||
return
|
||||
|
||||
hash_input = ' '.join(args)
|
||||
|
||||
try:
|
||||
# Identify the hash
|
||||
identified = identify_hash(hash_input)
|
||||
|
||||
if not identified:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"Could not identify hash type. Please verify the hash format."
|
||||
)
|
||||
return
|
||||
|
||||
# Sort by confidence (highest first)
|
||||
identified = sorted(identified, key=lambda x: x[3], reverse=True)
|
||||
|
||||
# Format the response
|
||||
hash_preview = hash_input[:60] + "..." if len(hash_input) > 60 else hash_input
|
||||
|
||||
# Determine confidence indicator
|
||||
top_confidence = identified[0][3]
|
||||
if top_confidence >= 90:
|
||||
confidence_emoji = "🟢"
|
||||
confidence_label = "Very High"
|
||||
elif top_confidence >= 80:
|
||||
confidence_emoji = "🟡"
|
||||
confidence_label = "High"
|
||||
elif top_confidence >= 60:
|
||||
confidence_emoji = "🟠"
|
||||
confidence_label = "Medium"
|
||||
else:
|
||||
confidence_emoji = "🔴"
|
||||
confidence_label = "Low"
|
||||
|
||||
# Build response inside collapsible details
|
||||
response = "<details><summary><strong>🔐 Hash Identification Results</strong></summary>\n"
|
||||
response += "<br>\n"
|
||||
response += f"<strong>Input:</strong> <code>{hash_preview}</code><br>\n"
|
||||
response += f"<strong>Length:</strong> {len(hash_input)} characters<br>\n"
|
||||
response += f"<strong>Overall Confidence:</strong> {confidence_emoji} {confidence_label} ({top_confidence}%)<br>\n"
|
||||
response += "<br>\n"
|
||||
|
||||
response += f"<strong>Possible Hash Types ({len(identified)}):</strong><br>\n"
|
||||
|
||||
for idx, (hash_type, hashcat_mode, john_format, confidence) in enumerate(identified, 1):
|
||||
# Confidence indicator per hash
|
||||
if confidence >= 90:
|
||||
conf_emoji = "🟢"
|
||||
elif confidence >= 80:
|
||||
conf_emoji = "🟡"
|
||||
elif confidence >= 60:
|
||||
conf_emoji = "🟠"
|
||||
else:
|
||||
conf_emoji = "🔴"
|
||||
|
||||
response += f" <strong>{idx}. {hash_type}</strong> {conf_emoji} {confidence}%<br>\n"
|
||||
|
||||
tools = []
|
||||
if hashcat_mode:
|
||||
tools.append(f"Hashcat: <code>-m {hashcat_mode}</code>")
|
||||
if john_format:
|
||||
tools.append(f"John: <code>--format={john_format}</code>")
|
||||
|
||||
if tools:
|
||||
response += f" {' | '.join(tools)}<br>\n"
|
||||
|
||||
response += "<br>\n"
|
||||
|
||||
# Add useful tips
|
||||
if len(identified) == 1 and identified[0][0] not in ["Unknown", "Unknown Modular Crypt Format"]:
|
||||
response += "<br><strong>💡 Single match with high confidence</strong><br>\n"
|
||||
elif len(identified) > 5:
|
||||
response += "<br><em>ℹ️ Multiple possibilities - context may help narrow it down</em><br>\n"
|
||||
|
||||
# Add legend
|
||||
response += "<br>\n"
|
||||
response += "<strong>Confidence Legend:</strong><br>\n"
|
||||
response += "🟢 Very High (90-100%) | 🟡 High (80-89%) | 🟠 Medium (60-79%) | 🔴 Low (0-59%)<br>\n"
|
||||
|
||||
response += "</details>"
|
||||
|
||||
await bot.api.send_markdown_message(room.room_id, response)
|
||||
logging.info(f"Identified hash types: {', '.join([f'{h[0]} ({h[3]}%)' for h in identified])}")
|
||||
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
f"Error identifying hash: {str(e)}"
|
||||
)
|
||||
logging.error(f"Error in hashid command: {e}", exc_info=True)
|
@@ -202,6 +202,87 @@ Search Exploit-DB for security vulnerabilities and exploits. Returns detailed in
|
||||
<p><em>Provides enterprise-grade security analysis for penetration testers and developers</em></p>
|
||||
</details>
|
||||
|
||||
<details><summary>🔄 <strong>!hashid <hash></strong></summary>
|
||||
<p>Advanced hash type identification with confidence scoring and tool recommendations.</p>
|
||||
<p><strong>Features:</strong></p>
|
||||
<ul>
|
||||
<li>100+ hash types including modern, legacy, and exotic algorithms</li>
|
||||
<li>Color-coded confidence scoring (🟢 Very High to 🔴 Low)</li>
|
||||
<li>Hashcat mode numbers and John the Ripper format names</li>
|
||||
<li>Context-aware parsing for various hash formats</li>
|
||||
</ul>
|
||||
<p><strong>Supported Categories:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Modern:</strong> yescrypt, scrypt, Argon2, bcrypt</li>
|
||||
<li><strong>Unix/Linux:</strong> SHA-512/256 Crypt, MD5 Crypt, apr1</li>
|
||||
<li><strong>Raw Hashes:</strong> MD5, SHA family, SHA-3, NTLM, LM</li>
|
||||
<li><strong>Databases:</strong> MySQL, PostgreSQL, Oracle, MSSQL</li>
|
||||
<li><strong>Web/CMS:</strong> WordPress, Drupal, phpBB3, Django</li>
|
||||
<li><strong>LDAP:</strong> SSHA, SMD5, LDAP crypt formats</li>
|
||||
<li><strong>Network:</strong> NetNTLMv1/v2, Kerberos</li>
|
||||
<li><strong>Exotic:</strong> Whirlpool, RIPEMD, GOST, BLAKE2</li>
|
||||
</ul>
|
||||
<p><strong>Tool Integration:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Hashcat:</strong> Mode numbers for <code>-m</code> parameter</li>
|
||||
<li><strong>John:</strong> Format names for <code>--format=</code> parameter</li>
|
||||
<li>Multi-tool compatibility</li>
|
||||
</ul>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>!hashid 5d41402abc4b2a76b9719d911017c592</code> (MD5)</li>
|
||||
<li><code>!hashid aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d</code> (SHA-1)</li>
|
||||
<li><code>!hashid $6$rounds=5000$salt$hash...</code> (SHA-512 Crypt)</li>
|
||||
<li><code>!hashid $y$j9T$...</code> (yescrypt - modern Linux)</li>
|
||||
<li><code>!hashid 8846f7eaee8fb117ad06bdd830b7586c</code> (NTLM)</li>
|
||||
<li><code>!hashid *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19</code> (MySQL)</li>
|
||||
</ul>
|
||||
<p><strong>Confidence Legend:</strong></p>
|
||||
<ul>
|
||||
<li>🟢 Very High (90-100%) - Single definitive match</li>
|
||||
<li>🟡 High (80-89%) - Strong match with minor alternatives</li>
|
||||
<li>🟠 Medium (60-79%) - Multiple plausible matches</li>
|
||||
<li>🔴 Low (0-59%) - Uncertain, needs context</li>
|
||||
</ul>
|
||||
<p><em>Essential for penetration testers, forensic analysts, and password cracking</em></p>
|
||||
</details>
|
||||
|
||||
<details><summary>🔐 <strong>!sslscan <domain[:port]></strong></summary>
|
||||
<p>Comprehensive SSL/TLS security scanning and analysis with vulnerability detection.</p>
|
||||
<p><strong>Features:</strong></p>
|
||||
<ul>
|
||||
<li>TLS 1.0-1.3 protocol support testing with security scoring</li>
|
||||
<li>Certificate chain validation, expiration, and signature analysis</li>
|
||||
<li>25+ cipher suite testing with strength classification</li>
|
||||
<li>Vulnerability detection (POODLE, weak ciphers, protocol issues)</li>
|
||||
<li>0-100 security rating with color-coded assessment</li>
|
||||
<li>PCI DSS and modern security standards compliance checking</li>
|
||||
</ul>
|
||||
<p><strong>Security Checks:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Protocol Security:</strong> TLS 1.2/1.3 enforcement, insecure protocol detection</li>
|
||||
<li><strong>Certificate Health:</strong> Expiration monitoring, signature validation</li>
|
||||
<li><strong>Cipher Security:</strong> RC4, DES, 3DES, NULL cipher detection</li>
|
||||
<li><strong>Modern Standards:</strong> Forward Secrecy, strong encryption</li>
|
||||
</ul>
|
||||
<p><strong>Security Ratings:</strong></p>
|
||||
<ul>
|
||||
<li>🟢 <strong>Excellent (90-100)</strong> - Modern TLS with strong security</li>
|
||||
<li>🟡 <strong>Good (80-89)</strong> - Good security, minor improvements needed</li>
|
||||
<li>🟠 <strong>Fair (60-79)</strong> - Moderate security, significant improvements</li>
|
||||
<li>🔴 <strong>Poor (0-59)</strong> - Critical issues requiring immediate attention</li>
|
||||
</ul>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>!sslscan example.com</code></li>
|
||||
<li><code>!sslscan github.com:443</code></li>
|
||||
<li><code>!sslscan localhost:8443</code></li>
|
||||
<li><code>!sslscan 192.168.1.1:443</code></li>
|
||||
</ul>
|
||||
<p><em>Essential for security teams, system administrators, and developers ensuring TLS compliance</em><br>
|
||||
<em>Note: SSLv2/SSLv3 testing limited by Python security features</em></p>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<details><summary>📸 <strong>!sd [prompt]</strong></summary>
|
||||
|
@@ -63,7 +63,9 @@ async def load_plugin(plugin_name):
|
||||
'shodan':'plugins.shodan',
|
||||
'dnsdumpster': 'plugins.dnsdumpster',
|
||||
'exploitdb': 'plugins.exploitdb',
|
||||
'headers': 'plugins.headers'
|
||||
'headers': 'plugins.headers',
|
||||
'hashid': 'plugins.hashid',
|
||||
'sslscan': 'plugins.sslscan'
|
||||
}
|
||||
|
||||
# Get the module path from the mapping
|
||||
|
594
plugins/sslscan.py
Normal file
594
plugins/sslscan.py
Normal file
@@ -0,0 +1,594 @@
|
||||
"""
|
||||
This plugin provides comprehensive SSL/TLS security scanning and analysis.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import ssl
|
||||
import OpenSSL
|
||||
import datetime
|
||||
import re
|
||||
import simplematrixbotlib as botlib
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# SSL/TLS configuration - handle missing protocols in modern Python
|
||||
TLS_VERSIONS = {
|
||||
'TLSv1.2': ssl.PROTOCOL_TLSv1_2,
|
||||
'TLSv1.3': ssl.PROTOCOL_TLS
|
||||
}
|
||||
|
||||
# Try to add older protocols if available (they're removed in modern Python)
|
||||
try:
|
||||
TLS_VERSIONS['TLSv1.1'] = ssl.PROTOCOL_TLSv1_1
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
TLS_VERSIONS['TLSv1'] = ssl.PROTOCOL_TLSv1
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Cipher suites by strength and category
|
||||
CIPHER_CATEGORIES = {
|
||||
'STRONG': [
|
||||
'TLS_AES_256_GCM_SHA384',
|
||||
'TLS_CHACHA20_POLY1305_SHA256',
|
||||
'TLS_AES_128_GCM_SHA256',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-CHACHA20-POLY1305',
|
||||
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
||||
'DHE-RSA-AES256-GCM-SHA384'
|
||||
],
|
||||
'WEAK': [
|
||||
'RC4',
|
||||
'DES',
|
||||
'3DES',
|
||||
'MD5',
|
||||
'EXPORT',
|
||||
'NULL',
|
||||
'ANON',
|
||||
'ADH',
|
||||
'CBC'
|
||||
],
|
||||
'OBSOLETE': [
|
||||
'SSLv2',
|
||||
'SSLv3'
|
||||
]
|
||||
}
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Function to handle !sslscan command for comprehensive SSL/TLS analysis.
|
||||
|
||||
Args:
|
||||
room (Room): The Matrix room where the command was invoked.
|
||||
message (RoomMessage): The message object containing the command.
|
||||
bot (Bot): The bot object.
|
||||
prefix (str): The command prefix.
|
||||
config (dict): Configuration parameters.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("sslscan"):
|
||||
logging.info("Received !sslscan command")
|
||||
|
||||
args = match.args()
|
||||
|
||||
if len(args) < 1:
|
||||
await show_usage(room, bot)
|
||||
return
|
||||
|
||||
target = args[0].strip()
|
||||
port = 443
|
||||
|
||||
# Parse port if provided
|
||||
if ':' in target:
|
||||
parts = target.split(':')
|
||||
target = parts[0]
|
||||
try:
|
||||
port = int(parts[1])
|
||||
except ValueError:
|
||||
await bot.api.send_text_message(room.room_id, "Invalid port number")
|
||||
return
|
||||
|
||||
await perform_ssl_scan(room, bot, target, port)
|
||||
|
||||
async def show_usage(room, bot):
|
||||
"""Display sslscan command usage."""
|
||||
usage = """
|
||||
<strong>🔐 SSL/TLS Security Scanner</strong>
|
||||
|
||||
<strong>!sslscan <domain[:port]></strong> - Comprehensive SSL/TLS security analysis
|
||||
|
||||
<strong>Examples:</strong>
|
||||
• <code>!sslscan example.com</code>
|
||||
• <code>!sslscan github.com:443</code>
|
||||
• <code>!sslscan localhost:8443</code>
|
||||
|
||||
<strong>Tests Performed:</strong>
|
||||
• SSL/TLS protocol support and versions
|
||||
• Certificate chain validation and expiration
|
||||
• Cipher suite strength and configuration
|
||||
• Security headers and extensions
|
||||
• Handshake simulation and vulnerabilities
|
||||
• PCI DSS compliance checking
|
||||
• SSL/TLS best practices assessment
|
||||
"""
|
||||
await bot.api.send_markdown_message(room.room_id, usage)
|
||||
|
||||
async def perform_ssl_scan(room, bot, target, port):
|
||||
"""Perform comprehensive SSL/TLS security scan."""
|
||||
try:
|
||||
await bot.api.send_text_message(room.room_id, f"🔍 Starting comprehensive SSL/TLS scan for {target}:{port}...")
|
||||
|
||||
scan_results = {
|
||||
'target': target,
|
||||
'port': port,
|
||||
'certificate': {},
|
||||
'protocols': {},
|
||||
'ciphers': {},
|
||||
'vulnerabilities': [],
|
||||
'recommendations': [],
|
||||
'security_score': 0
|
||||
}
|
||||
|
||||
# Test basic connectivity
|
||||
if not await test_connectivity(target, port):
|
||||
await bot.api.send_text_message(room.room_id, f"❌ Cannot connect to {target}:{port}")
|
||||
return
|
||||
|
||||
# Perform comprehensive tests
|
||||
await get_certificate_info(scan_results, target, port)
|
||||
await test_protocol_support(scan_results, target, port)
|
||||
await test_cipher_suites(scan_results, target, port)
|
||||
await check_vulnerabilities(scan_results)
|
||||
await calculate_security_score(scan_results)
|
||||
await generate_recommendations(scan_results)
|
||||
|
||||
# Format and send results
|
||||
output = await format_ssl_scan_results(scan_results)
|
||||
await bot.api.send_markdown_message(room.room_id, output)
|
||||
|
||||
logging.info(f"Completed SSL scan for {target}:{port}")
|
||||
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Error performing SSL scan: {str(e)}")
|
||||
logging.error(f"Error in perform_ssl_scan: {e}")
|
||||
|
||||
async def test_connectivity(target, port):
|
||||
"""Test basic connectivity to the target."""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
result = sock.connect_ex((target, port))
|
||||
sock.close()
|
||||
return result == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
async def get_certificate_info(scan_results, target, port):
|
||||
"""Get comprehensive certificate information."""
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
with socket.create_connection((target, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=target) as ssock:
|
||||
cert_bin = ssock.getpeercert(binary_form=True)
|
||||
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert_bin)
|
||||
|
||||
# Basic certificate info
|
||||
subject = cert.get_subject()
|
||||
issuer = cert.get_issuer()
|
||||
|
||||
scan_results['certificate'] = {
|
||||
'subject': {
|
||||
'common_name': subject.CN,
|
||||
'organization': subject.O,
|
||||
'organizational_unit': subject.OU,
|
||||
'country': subject.C,
|
||||
'state': subject.ST,
|
||||
'locality': subject.L
|
||||
},
|
||||
'issuer': {
|
||||
'common_name': issuer.CN,
|
||||
'organization': issuer.O,
|
||||
'organizational_unit': issuer.OU
|
||||
},
|
||||
'serial_number': cert.get_serial_number(),
|
||||
'version': cert.get_version(),
|
||||
'not_before': cert.get_notBefore().decode('utf-8'),
|
||||
'not_after': cert.get_notAfter().decode('utf-8'),
|
||||
'signature_algorithm': cert.get_signature_algorithm().decode('utf-8'),
|
||||
'extensions': []
|
||||
}
|
||||
|
||||
# Parse extensions
|
||||
for i in range(cert.get_extension_count()):
|
||||
ext = cert.get_extension(i)
|
||||
scan_results['certificate']['extensions'].append({
|
||||
'name': ext.get_short_name().decode('utf-8'),
|
||||
'value': str(ext)
|
||||
})
|
||||
|
||||
# Calculate days until expiration
|
||||
not_after = datetime.datetime.strptime(scan_results['certificate']['not_after'], '%Y%m%d%H%M%SZ')
|
||||
days_until_expiry = (not_after - datetime.datetime.utcnow()).days
|
||||
scan_results['certificate']['days_until_expiry'] = days_until_expiry
|
||||
|
||||
except Exception as e:
|
||||
scan_results['certificate_error'] = str(e)
|
||||
|
||||
async def test_protocol_support(scan_results, target, port):
|
||||
"""Test support for various SSL/TLS protocols."""
|
||||
protocols = {
|
||||
'SSLv2': False,
|
||||
'SSLv3': False,
|
||||
'TLSv1': False,
|
||||
'TLSv1.1': False,
|
||||
'TLSv1.2': False,
|
||||
'TLSv1.3': False
|
||||
}
|
||||
|
||||
# Test available protocols
|
||||
for protocol_name in protocols.keys():
|
||||
try:
|
||||
if protocol_name in TLS_VERSIONS:
|
||||
context = ssl.SSLContext(TLS_VERSIONS[protocol_name])
|
||||
else:
|
||||
# For protocols not available in this Python version, assume False
|
||||
protocols[protocol_name] = False
|
||||
continue
|
||||
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
with socket.create_connection((target, port), timeout=5) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=target) as ssock:
|
||||
protocols[protocol_name] = True
|
||||
# Get negotiated protocol
|
||||
if hasattr(ssock, 'version'):
|
||||
scan_results['negotiated_protocol'] = ssock.version()
|
||||
except:
|
||||
protocols[protocol_name] = False
|
||||
|
||||
scan_results['protocols'] = protocols
|
||||
|
||||
async def test_cipher_suites(scan_results, target, port):
|
||||
"""Test supported cipher suites."""
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Get default cipher suites
|
||||
context.set_ciphers('ALL:COMPLEMENTOFALL')
|
||||
|
||||
with socket.create_connection((target, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=target) as ssock:
|
||||
cipher = ssock.cipher()
|
||||
scan_results['ciphers'] = {
|
||||
'negotiated_cipher': cipher[0] if cipher else 'Unknown',
|
||||
'supported_ciphers': await get_supported_ciphers(target, port),
|
||||
'weak_ciphers': [],
|
||||
'strong_ciphers': []
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
scan_results['cipher_error'] = str(e)
|
||||
|
||||
async def get_supported_ciphers(target, port):
|
||||
"""Get list of supported cipher suites."""
|
||||
supported_ciphers = []
|
||||
|
||||
# Test common cipher suites
|
||||
test_ciphers = [
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES256-SHA384',
|
||||
'ECDHE-ECDSA-AES256-SHA384',
|
||||
'ECDHE-RSA-AES256-SHA',
|
||||
'ECDHE-ECDSA-AES256-SHA',
|
||||
'AES256-GCM-SHA384',
|
||||
'AES256-SHA256',
|
||||
'AES256-SHA',
|
||||
'CAMELLIA256-SHA',
|
||||
'PSK-AES256-CBC-SHA',
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES128-SHA256',
|
||||
'ECDHE-ECDSA-AES128-SHA256',
|
||||
'ECDHE-RSA-AES128-SHA',
|
||||
'ECDHE-ECDSA-AES128-SHA',
|
||||
'AES128-GCM-SHA256',
|
||||
'AES128-SHA256',
|
||||
'AES128-SHA',
|
||||
'CAMELLIA128-SHA',
|
||||
'PSK-AES128-CBC-SHA',
|
||||
'DES-CBC3-SHA',
|
||||
'RC4-SHA',
|
||||
'RC4-MD5'
|
||||
]
|
||||
|
||||
for cipher in test_ciphers:
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
context.set_ciphers(cipher)
|
||||
|
||||
with socket.create_connection((target, port), timeout=5) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=target) as ssock:
|
||||
if ssock.cipher():
|
||||
supported_ciphers.append(cipher)
|
||||
except:
|
||||
pass
|
||||
|
||||
return supported_ciphers
|
||||
|
||||
async def check_vulnerabilities(scan_results):
|
||||
"""Check for common SSL/TLS vulnerabilities."""
|
||||
vulnerabilities = []
|
||||
|
||||
# Check for weak protocols (these will be False in modern Python, which is good)
|
||||
if scan_results['protocols'].get('SSLv2', False):
|
||||
vulnerabilities.append({
|
||||
'name': 'SSLv2 Support',
|
||||
'severity': 'CRITICAL',
|
||||
'description': 'SSLv2 is obsolete and contains critical vulnerabilities',
|
||||
'cve': 'Multiple CVEs'
|
||||
})
|
||||
|
||||
if scan_results['protocols'].get('SSLv3', False):
|
||||
vulnerabilities.append({
|
||||
'name': 'SSLv3 Support',
|
||||
'severity': 'HIGH',
|
||||
'description': 'SSLv3 is vulnerable to POODLE attack',
|
||||
'cve': 'CVE-2014-3566'
|
||||
})
|
||||
|
||||
# Check certificate expiration
|
||||
cert = scan_results.get('certificate', {})
|
||||
if cert.get('days_until_expiry', 0) < 30:
|
||||
vulnerabilities.append({
|
||||
'name': 'Certificate Expiring Soon',
|
||||
'severity': 'MEDIUM',
|
||||
'description': f"Certificate expires in {cert['days_until_expiry']} days",
|
||||
'cve': 'N/A'
|
||||
})
|
||||
|
||||
# Check for weak ciphers
|
||||
supported_ciphers = scan_results.get('ciphers', {}).get('supported_ciphers', [])
|
||||
weak_ciphers_found = []
|
||||
|
||||
for cipher in supported_ciphers:
|
||||
if any(weak in cipher.upper() for weak in CIPHER_CATEGORIES['WEAK']):
|
||||
weak_ciphers_found.append(cipher)
|
||||
|
||||
if weak_ciphers_found:
|
||||
vulnerabilities.append({
|
||||
'name': 'Weak Cipher Suites',
|
||||
'severity': 'HIGH',
|
||||
'description': f'Weak ciphers supported: {", ".join(weak_ciphers_found[:3])}',
|
||||
'cve': 'Multiple CVEs'
|
||||
})
|
||||
|
||||
# Check for missing modern protocols
|
||||
if not scan_results['protocols'].get('TLSv1.2', False):
|
||||
vulnerabilities.append({
|
||||
'name': 'TLS 1.2 Not Supported',
|
||||
'severity': 'HIGH',
|
||||
'description': 'TLS 1.2 is required for modern security',
|
||||
'cve': 'N/A'
|
||||
})
|
||||
|
||||
if not scan_results['protocols'].get('TLSv1.3', False):
|
||||
vulnerabilities.append({
|
||||
'name': 'TLS 1.3 Not Supported',
|
||||
'severity': 'MEDIUM',
|
||||
'description': 'TLS 1.3 provides improved security and performance',
|
||||
'cve': 'N/A'
|
||||
})
|
||||
|
||||
scan_results['vulnerabilities'] = vulnerabilities
|
||||
|
||||
async def calculate_security_score(scan_results):
|
||||
"""Calculate overall security score."""
|
||||
score = 100
|
||||
|
||||
# Protocol penalties (in modern Python, SSLv2/SSLv3 will be False, which is good)
|
||||
if scan_results['protocols'].get('SSLv2', False):
|
||||
score -= 30
|
||||
if scan_results['protocols'].get('SSLv3', False):
|
||||
score -= 20
|
||||
if not scan_results['protocols'].get('TLSv1.2', False):
|
||||
score -= 15
|
||||
if not scan_results['protocols'].get('TLSv1.3', False):
|
||||
score -= 10
|
||||
|
||||
# Certificate penalties
|
||||
cert = scan_results.get('certificate', {})
|
||||
if cert.get('days_until_expiry', 0) < 30:
|
||||
score -= 10
|
||||
if cert.get('days_until_expiry', 0) < 7:
|
||||
score -= 20
|
||||
|
||||
# Cipher penalties
|
||||
supported_ciphers = scan_results.get('ciphers', {}).get('supported_ciphers', [])
|
||||
weak_cipher_count = sum(1 for cipher in supported_ciphers
|
||||
if any(weak in cipher.upper() for weak in CIPHER_CATEGORIES['WEAK']))
|
||||
score -= min(weak_cipher_count * 5, 25)
|
||||
|
||||
# Vulnerability penalties
|
||||
for vuln in scan_results.get('vulnerabilities', []):
|
||||
if vuln['severity'] == 'CRITICAL':
|
||||
score -= 20
|
||||
elif vuln['severity'] == 'HIGH':
|
||||
score -= 15
|
||||
elif vuln['severity'] == 'MEDIUM':
|
||||
score -= 10
|
||||
elif vuln['severity'] == 'LOW':
|
||||
score -= 5
|
||||
|
||||
scan_results['security_score'] = max(0, score)
|
||||
|
||||
async def generate_recommendations(scan_results):
|
||||
"""Generate security recommendations."""
|
||||
recommendations = []
|
||||
|
||||
# Protocol recommendations
|
||||
if scan_results['protocols'].get('SSLv2', False):
|
||||
recommendations.append("🔴 IMMEDIATELY disable SSLv2 - critically vulnerable")
|
||||
if scan_results['protocols'].get('SSLv3', False):
|
||||
recommendations.append("🔴 Disable SSLv3 - vulnerable to POODLE attack")
|
||||
if not scan_results['protocols'].get('TLSv1.3', False):
|
||||
recommendations.append("🟡 Enable TLSv1.3 for best security and performance")
|
||||
|
||||
# Certificate recommendations
|
||||
cert = scan_results.get('certificate', {})
|
||||
if cert.get('days_until_expiry', 0) < 30:
|
||||
recommendations.append("🟡 Renew SSL certificate - expiring soon")
|
||||
|
||||
# Cipher recommendations
|
||||
supported_ciphers = scan_results.get('ciphers', {}).get('supported_ciphers', [])
|
||||
weak_ciphers = [c for c in supported_ciphers
|
||||
if any(weak in c.upper() for weak in CIPHER_CATEGORIES['WEAK'])]
|
||||
|
||||
if weak_ciphers:
|
||||
recommendations.append("🔴 Remove weak cipher suites (RC4, DES, 3DES, NULL)")
|
||||
|
||||
# General recommendations
|
||||
if scan_results['security_score'] < 80:
|
||||
recommendations.append("🛡️ Implement modern TLS configuration following Mozilla guidelines")
|
||||
|
||||
if not any('ECDHE' in c for c in supported_ciphers):
|
||||
recommendations.append("🟡 Enable Forward Secrecy with ECDHE cipher suites")
|
||||
|
||||
# Add note about Python version limitations
|
||||
recommendations.append("ℹ️ Note: SSLv2/SSLv3 testing limited by Python security features")
|
||||
|
||||
scan_results['recommendations'] = recommendations
|
||||
|
||||
async def format_ssl_scan_results(scan_results):
|
||||
"""Format comprehensive SSL scan results."""
|
||||
output = f"<strong>🔐 SSL/TLS Security Scan: {scan_results['target']}:{scan_results['port']}</strong><br><br>"
|
||||
|
||||
# Security Score
|
||||
score = scan_results['security_score']
|
||||
if score >= 90:
|
||||
score_emoji, rating = "🟢", "Excellent"
|
||||
elif score >= 80:
|
||||
score_emoji, rating = "🟡", "Good"
|
||||
elif score >= 60:
|
||||
score_emoji, rating = "🟠", "Fair"
|
||||
else:
|
||||
score_emoji, rating = "🔴", "Poor"
|
||||
|
||||
output += f"<strong>{score_emoji} Security Score: {score}/100 ({rating})</strong><br><br>"
|
||||
|
||||
# Certificate Information
|
||||
cert = scan_results.get('certificate', {})
|
||||
if cert:
|
||||
output += "<strong>📜 Certificate Information</strong><br>"
|
||||
output += f" • <strong>Subject:</strong> {cert.get('subject', {}).get('common_name', 'N/A')}<br>"
|
||||
output += f" • <strong>Issuer:</strong> {cert.get('issuer', {}).get('common_name', 'N/A')}<br>"
|
||||
output += f" • <strong>Valid From:</strong> {format_cert_date(cert.get('not_before', ''))}<br>"
|
||||
output += f" • <strong>Valid Until:</strong> {format_cert_date(cert.get('not_after', ''))}<br>"
|
||||
output += f" • <strong>Expires In:</strong> {cert.get('days_until_expiry', 'N/A')} days<br>"
|
||||
output += f" • <strong>Signature Algorithm:</strong> {cert.get('signature_algorithm', 'N/A')}<br>"
|
||||
output += "<br>"
|
||||
|
||||
# Protocol Support
|
||||
output += "<strong>🔌 Protocol Support</strong><br>"
|
||||
protocols = scan_results.get('protocols', {})
|
||||
for proto, supported in protocols.items():
|
||||
# Handle protocols that can't be tested in this Python version
|
||||
if proto in ['SSLv2', 'SSLv3'] and proto not in TLS_VERSIONS:
|
||||
emoji = "⚫"
|
||||
status = "Cannot test (Python security)"
|
||||
else:
|
||||
emoji = "✅" if supported else "❌"
|
||||
|
||||
# Highlight insecure protocols
|
||||
if proto in ['SSLv2', 'SSLv3'] and supported:
|
||||
emoji = "🔴"
|
||||
elif proto in ['TLSv1.3'] and supported:
|
||||
emoji = "✅"
|
||||
|
||||
output += f" • {emoji} <strong>{proto}:</strong> {status if 'status' in locals() else 'Supported' if supported else 'Not Supported'}<br>"
|
||||
output += "<br>"
|
||||
|
||||
# Cipher Information
|
||||
ciphers = scan_results.get('ciphers', {})
|
||||
if ciphers.get('supported_ciphers'):
|
||||
output += "<strong>🔐 Cipher Suites</strong><br>"
|
||||
output += f" • <strong>Negotiated:</strong> {ciphers.get('negotiated_cipher', 'Unknown')}<br>"
|
||||
output += f" • <strong>Total Supported:</strong> {len(ciphers['supported_ciphers'])}<br>"
|
||||
|
||||
# Show weak ciphers if any
|
||||
weak_ciphers = [c for c in ciphers['supported_ciphers']
|
||||
if any(weak in c.upper() for weak in CIPHER_CATEGORIES['WEAK'])]
|
||||
if weak_ciphers:
|
||||
output += f" • <strong>Weak Ciphers:</strong> {len(weak_ciphers)} found<br>"
|
||||
for cipher in weak_ciphers[:3]:
|
||||
output += f" └─ 🔴 {cipher}<br>"
|
||||
|
||||
# Show strong ciphers if any
|
||||
strong_ciphers = [c for c in ciphers['supported_ciphers']
|
||||
if any(strong in c.upper() for strong in CIPHER_CATEGORIES['STRONG'])]
|
||||
if strong_ciphers:
|
||||
output += f" • <strong>Strong Ciphers:</strong> {len(strong_ciphers)} found<br>"
|
||||
output += "<br>"
|
||||
|
||||
# Vulnerabilities
|
||||
vulnerabilities = scan_results.get('vulnerabilities', [])
|
||||
if vulnerabilities:
|
||||
output += "<strong>⚠️ Security Vulnerabilities</strong><br>"
|
||||
for vuln in vulnerabilities[:5]: # Show top 5
|
||||
severity_emoji = "🔴" if vuln['severity'] == 'CRITICAL' else "🟠" if vuln['severity'] == 'HIGH' else "🟡"
|
||||
output += f" • {severity_emoji} <strong>{vuln['name']}</strong> ({vuln['severity']})<br>"
|
||||
output += f" └─ {vuln['description']}<br>"
|
||||
output += "<br>"
|
||||
|
||||
# Recommendations
|
||||
recommendations = scan_results.get('recommendations', [])
|
||||
if recommendations:
|
||||
output += "<strong>💡 Security Recommendations</strong><br>"
|
||||
for rec in recommendations[:8]:
|
||||
output += f" • {rec}<br>"
|
||||
output += "<br>"
|
||||
|
||||
# Quick Assessment
|
||||
output += "<strong>📊 Quick Assessment</strong><br>"
|
||||
if score >= 90:
|
||||
output += " • ✅ Excellent TLS configuration<br>"
|
||||
output += " • ✅ Modern protocols and ciphers<br>"
|
||||
output += " • ✅ Good certificate management<br>"
|
||||
elif score >= 70:
|
||||
output += " • ⚠️ Good configuration with minor issues<br>"
|
||||
output += " • 🔧 Some improvements recommended<br>"
|
||||
else:
|
||||
output += " • 🚨 Significant security issues found<br>"
|
||||
output += " • 🔴 Immediate action required<br>"
|
||||
|
||||
# Add note about testing limitations
|
||||
output += "<br><em>ℹ️ Note: Some protocol tests limited by Python security features</em>"
|
||||
|
||||
# Always wrap in collapsible due to comprehensive output
|
||||
output = f"<details><summary><strong>🔐 SSL/TLS Scan: {scan_results['target']}:{scan_results['port']} (Score: {score}/100)</strong></summary>{output}</details>"
|
||||
|
||||
return output
|
||||
|
||||
def format_cert_date(date_str):
|
||||
"""Format certificate date string for display."""
|
||||
try:
|
||||
if date_str:
|
||||
dt = datetime.datetime.strptime(date_str, '%Y%m%d%H%M%SZ')
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S UTC')
|
||||
except:
|
||||
pass
|
||||
return date_str
|
@@ -11,3 +11,4 @@ dnspython
|
||||
croniter
|
||||
schedule
|
||||
yt-dlp
|
||||
pyopenssl
|
||||
|
Reference in New Issue
Block a user