Skip to content

Commit b158cf7

Browse files
committed
Cryptography Security Testing added
1 parent 7fd34af commit b158cf7

1 file changed

Lines changed: 339 additions & 3 deletions

File tree

Framework/Built_In_Automation/Security/nmap_scan.py

Lines changed: 339 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from pathlib import Path
99
import shutil
1010
from datetime import datetime, timedelta
11+
import socket
12+
import ssl
13+
import urllib.request
14+
import urllib.parse
1115

1216
def run_nmap(ip, output_dir=None):
1317
os.makedirs(output_dir, exist_ok=True)
@@ -160,7 +164,7 @@ def parse_nmap_output(xml_file):
160164

161165
return vulnerabilities, scan_info
162166

163-
def generate_html(vulnerabilities, scan_info, target_ip, output_dir=None):
167+
def generate_html(vulnerabilities, scan_info, target_ip, output_dir=None, crypto_data=None):
164168
"""Generate HTML report for security vulnerabilities."""
165169

166170
severity_counts = {
@@ -595,6 +599,8 @@ def generate_html(vulnerabilities, scan_info, target_ip, output_dir=None):
595599
</div>
596600
''' if not is_clean else ''}
597601
602+
{get_crypto_html_snippets(crypto_data) if crypto_data else ''}
603+
598604
<footer>
599605
<p>Generated by Zeuz Security Automation Framework &bull; {current_datetime}</p>
600606
</footer>
@@ -675,7 +681,11 @@ def generate_html(vulnerabilities, scan_info, target_ip, output_dir=None):
675681

676682
def nmap_scan_run(url, security_report_dir=None):
677683
ip_address = url
678-
security_report_dir.mkdir(parents=True, exist_ok=True)
684+
if hasattr(security_report_dir, 'mkdir'):
685+
security_report_dir.mkdir(parents=True, exist_ok=True)
686+
elif security_report_dir:
687+
os.makedirs(security_report_dir, exist_ok=True)
688+
679689
print(f"Saving all reports directly to: {security_report_dir}")
680690
print("Running Nmap scan. It may take a while...")
681691
xml_result, text_result = run_nmap(ip_address, security_report_dir)
@@ -686,11 +696,337 @@ def nmap_scan_run(url, security_report_dir=None):
686696
print("Parsing results...")
687697
vuln_data, scan_info = parse_nmap_output(xml_result)
688698

699+
print("Running Cryptography & Surface Level scan (Background)...")
700+
crypto_data = get_cryptography_data(ip_address)
701+
689702
print("Generating HTML report...")
690-
html_result = generate_html(vuln_data, scan_info, ip_address, security_report_dir)
703+
html_result = generate_html(vuln_data, scan_info, ip_address, security_report_dir, crypto_data)
691704

692705
return {
693706
"xml": xml_result,
694707
"txt": text_result,
708+
"html": html_result,
709+
"crypto": crypto_data
710+
}
711+
712+
def get_crypto_html_snippets(crypto_data):
713+
ssl_info = crypto_data.get('ssl', {})
714+
headers_info = crypto_data.get('headers', {})
715+
banner_info = crypto_data.get('banner', {})
716+
whois_info = crypto_data.get('whois', {})
717+
geo_info = crypto_data.get('geo', {})
718+
osint_info = crypto_data.get('osint', {})
719+
720+
def badge(value, expected=None, good='Good', is_tls=False):
721+
if is_tls:
722+
if 'Good' in value: return f'<span class="badge" style="background:var(--success); color:white;">{value}</span>'
723+
else: return f'<span class="badge" style="background:var(--danger); color:white;">{value}</span>'
724+
725+
if isinstance(value, str) and value.lower() in ['missing', 'invalid', 'hidden', 'unknown', 'true', 'false']:
726+
if value.lower() == 'true' and expected is False:
727+
return f'<span class="badge" style="background:var(--danger); color:white;">Yes</span>'
728+
elif value.lower() == 'false' and expected is False:
729+
return f'<span class="badge" style="background:var(--success); color:white;">No</span>'
730+
elif value.lower() == 'missing' or (value.lower() == 'true'):
731+
return f'<span class="badge" style="background:var(--warning); color:white;">{value}</span>'
732+
return f'<span class="badge badge-secondary" style="background:#e2e8f0; color:#475569;">{value}</span>'
733+
734+
if expected is not None:
735+
if value == expected:
736+
return f'<span class="badge" style="background:var(--success); color:white;">{good}</span>'
737+
else:
738+
if ssl_info.get('blocked'):
739+
return f'<span class="badge" style="background:var(--warning); color:white;">Domain Blocked</span>'
740+
return f'<span class="badge" style="background:var(--danger); color:white;">High Risk</span>'
741+
742+
return f'<span class="badge" style="background:var(--primary); color:white;">{value}</span>'
743+
744+
tls_versions_html = ""
745+
for tls_ver, strength in ssl_info.get("tls_versions", []):
746+
tls_versions_html += f"<div>{tls_ver}: {badge(strength, is_tls=True)}</div>"
747+
if not tls_versions_html:
748+
tls_versions_html = "<div><span class='badge badge-secondary' style='background:#e2e8f0; color:#475569;'>Unknown - Detection failed</span></div>"
749+
750+
ssl_error_html = f" <span style='color:var(--danger); font-size:13px; font-weight:500; margin-left:8px;'>({ssl_info.get('error', 'Validation Failed')})</span>" if not ssl_info.get('valid') and ssl_info.get('error') else ""
751+
752+
osint_html = ""
753+
if osint_info.get('subdomains'):
754+
osint_html = f"""
755+
<div class="card">
756+
<div class="section-title">OSINT & Web Footprint</div>
757+
<table>
758+
<tbody>
759+
<tr><td width="30%">Public Subdomains (Top 15 via crt.sh & HackerTarget)</td><td>{'<br>'.join(f"<span>{s}</span>" for s in osint_info['subdomains'])}</td></tr>
760+
</tbody>
761+
</table>
762+
</div>"""
763+
764+
return f"""
765+
<div class="card">
766+
<div class="section-title">SSL / TLS Configuration</div>
767+
<table>
768+
<tbody>
769+
<tr><td width="30%">Valid Certificate</td><td>{badge(ssl_info.get('valid', 'Unknown'), expected=True, good='Yes')}{ssl_error_html}</td></tr>
770+
<tr><td>Expired</td><td>{badge(str(ssl_info.get('expired', 'Unknown')), expected=False, good='No')}</td></tr>
771+
<tr><td>Self-Signed</td><td>{badge(str(ssl_info.get('self_signed', 'Unknown')), expected=False, good='No')}</td></tr>
772+
<tr><td>Wrong Hostname</td><td>{badge(str(ssl_info.get('wrong_hostname', 'Unknown')), expected=False, good='No')}</td></tr>
773+
<tr><td>Issuer</td><td><span class="badge badge-secondary" style="background:#e2e8f0; color:#475569;">{ssl_info.get('issuer', 'Unknown')}</span></td></tr>
774+
<tr><td>Expiry Date</td><td><span class="badge badge-secondary" style="background:#e2e8f0; color:#475569;">{ssl_info.get('not_after', 'Unknown')}</span></td></tr>
775+
<tr><td>Supported TLS Versions</td><td>{tls_versions_html}</td></tr>
776+
</tbody>
777+
</table>
778+
</div>
779+
780+
<div class="card">
781+
<div class="section-title">Security Headers</div>
782+
<table>
783+
<tbody>
784+
<tr><td width="30%">Strict-Transport-Security</td><td>{badge(headers_info.get('Strict-Transport-Security', 'Missing'))}</td></tr>
785+
<tr><td>Content-Security-Policy</td><td>{badge(headers_info.get('Content-Security-Policy', 'Missing'))}</td></tr>
786+
<tr><td>X-Content-Type-Options</td><td>{badge(headers_info.get('X-Content-Type-Options', 'Missing'))}</td></tr>
787+
</tbody>
788+
</table>
789+
</div>
790+
791+
<div class="card">
792+
<div class="section-title">Server Banner Information</div>
793+
<table>
794+
<tbody>
795+
<tr><td width="30%">Server</td><td><span class="badge" style="background:var(--primary); color:white;">{banner_info.get('Server', 'Hidden')}</span></td></tr>
796+
<tr><td>X-Powered-By</td><td><span class="badge" style="background:var(--warning); color:white;">{banner_info.get('X-Powered-By', 'Hidden')}</span></td></tr>
797+
</tbody>
798+
</table>
799+
</div>
800+
801+
<div class="card">
802+
<div class="section-title">WHOIS Information</div>
803+
<table>
804+
<tbody>
805+
<tr><td width="30%">Registrar</td><td><strong>{whois_info.get('Registrar', 'Unknown')}</strong></td></tr>
806+
<tr><td>Creation Date</td><td>{whois_info.get('Creation Date', 'Unknown')}</td></tr>
807+
<tr><td>Expiry Date</td><td>{whois_info.get('Expiry Date', 'Unknown')}</td></tr>
808+
</tbody>
809+
</table>
810+
</div>
811+
812+
<div class="card">
813+
<div class="section-title">IP Geolocation</div>
814+
<table>
815+
<tbody>
816+
<tr><td width="30%">IP Address</td><td><strong>{geo_info.get('IP', 'Unknown')}</strong></td></tr>
817+
<tr><td>Country</td><td>{geo_info.get('Country', 'Unknown')}</td></tr>
818+
<tr><td>Hosting Provider / ISP</td><td>{geo_info.get('Hosting Provider', 'Unknown')}</td></tr>
819+
</tbody>
820+
</table>
821+
</div>
822+
{osint_html}
823+
"""
824+
825+
def generate_crypto_html(report_data, target, output_dir=None):
826+
current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
827+
828+
domain = report_data.get('domain', target)
829+
830+
html_template = f"""
831+
<!DOCTYPE html>
832+
<html lang="en">
833+
<head>
834+
<meta charset="UTF-8">
835+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
836+
<title>Cryptography Scan Report - {domain}</title>
837+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
838+
<style>
839+
:root {{
840+
--primary: #2563eb;
841+
--secondary: #64748b;
842+
--success: #10b981;
843+
--warning: #f59e0b;
844+
--danger: #ef4444;
845+
--dark: #0f172a;
846+
--light: #f8fafc;
847+
--surface: #ffffff;
848+
--border: #e2e8f0;
849+
}}
850+
body {{ font-family: 'Inter', sans-serif; margin: 0; padding: 0; background-color: #f1f5f9; color: #334155; line-height: 1.6; }}
851+
.container {{ max-width: 1000px; margin: 0 auto; padding: 20px; }}
852+
.header {{ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); color: white; padding: 40px 0; margin-bottom: -60px; padding-bottom: 80px; }}
853+
.header-content {{ max-width: 1000px; margin: 0 auto; padding: 0 20px; display: flex; justify-content: space-between; align-items: center; }}
854+
.logo h1 {{ margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -0.5px; }}
855+
.logo p {{ margin: 5px 0 0; opacity: 0.8; font-size: 14px; }}
856+
.card {{ background: white; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); padding: 24px; margin-bottom: 24px; border: 1px solid var(--border); }}
857+
.section-title {{ font-size: 18px; font-weight: 600; color: var(--dark); margin-top: 0; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid var(--border); }}
858+
table {{ width: 100%; border-collapse: collapse; }}
859+
th {{ text-align: left; padding: 12px 16px; font-size: 12px; font-weight: 600; text-transform: uppercase; color: var(--secondary); background: #f8fafc; border-bottom: 1px solid var(--border); }}
860+
td {{ padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 14px; vertical-align: top; }}
861+
tr:last-child td {{ border-bottom: none; }}
862+
.badge {{ display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 600; color: white;}}
863+
.font-mono {{ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; }}
864+
footer {{ text-align: center; padding: 40px; color: var(--secondary); font-size: 13px; }}
865+
</style>
866+
</head>
867+
<body>
868+
<div class="header">
869+
<div class="header-content">
870+
<div class="logo">
871+
<h1>Cryptography Scan Report</h1>
872+
<p>Target: {domain}</p>
873+
</div>
874+
<div class="scan-meta">
875+
<div>{current_datetime}</div>
876+
</div>
877+
</div>
878+
</div>
879+
<div class="container">
880+
{get_crypto_html_snippets(report_data)}
881+
<footer>
882+
<p>Generated by Zeuz Security Automation Framework &bull; {current_datetime}</p>
883+
</footer>
884+
</div>
885+
</body>
886+
</html>
887+
"""
888+
889+
if output_dir:
890+
os.makedirs(output_dir, exist_ok=True)
891+
else:
892+
output_dir = '.'
893+
out_path = os.path.join(output_dir, f"crypto_report_{domain.replace('.', '_')}.html")
894+
with open(out_path, "w", encoding="utf-8") as f:
895+
f.write(html_template)
896+
print(f"Enhanced cryptography report saved: {out_path}")
897+
return out_path
898+
899+
900+
def cryptography_scan_run(url, security_report_dir=None):
901+
if hasattr(security_report_dir, 'mkdir'):
902+
security_report_dir.mkdir(parents=True, exist_ok=True)
903+
elif security_report_dir:
904+
os.makedirs(security_report_dir, exist_ok=True)
905+
906+
print(f"Running Cryptography scan for {url}...")
907+
908+
def get_cryptography_data(url):
909+
if not url.startswith("http"):
910+
url = "https://" + url
911+
912+
parsed = urllib.parse.urlparse(url)
913+
domain = parsed.netloc.split(':')[0]
914+
915+
report_data = {
916+
"domain": domain,
917+
"url": url,
918+
"ssl": {"valid": False, "expired": False, "self_signed": False, "wrong_hostname": False, "tls_versions": []},
919+
"headers": {},
920+
"banner": {},
921+
"whois": {},
922+
"geo": {}
923+
}
924+
# 1. SSL Certificate Info
925+
print(f"[{domain}] Fetching SSL/TLS Certificate information...", flush=True)
926+
try:
927+
context = ssl.create_default_context()
928+
try:
929+
with socket.create_connection((domain, 443), timeout=2) as sock:
930+
with context.wrap_socket(sock, server_hostname=domain) as ssock:
931+
cert = ssock.getpeercert()
932+
report_data['ssl']['expired'] = ssl.cert_time_to_seconds(cert['notAfter']) < time.time()
933+
934+
issuer = dict(x[0] for x in cert.get('issuer', []))
935+
subject = dict(x[0] for x in cert.get('subject', []))
936+
report_data['ssl']['self_signed'] = issuer == subject
937+
938+
report_data['ssl']['valid'] = True
939+
report_data['ssl']['not_after'] = cert['notAfter']
940+
report_data['ssl']['issuer'] = issuer.get('organizationName', issuer.get('commonName', 'Unknown'))
941+
942+
# Check TLS Version while socket is open
943+
ver = ssock.version()
944+
if ver:
945+
strength = 'Good' if ver in ['TLSv1.2', 'TLSv1.3'] else 'Weak'
946+
report_data['ssl']['tls_versions'] = [(ver, strength)]
947+
948+
except ssl.CertificateError as e:
949+
report_data['ssl']['wrong_hostname'] = True
950+
report_data['ssl']['valid'] = False
951+
report_data['ssl']['error'] = str(e)
952+
953+
# Reconnect without verification to get cert info anyway
954+
ctx_no_verify = ssl._create_unverified_context()
955+
with socket.create_connection((domain, 443), timeout=2) as sock:
956+
with ctx_no_verify.wrap_socket(sock, server_hostname=domain) as ssock:
957+
cert = ssock.getpeercert()
958+
if cert:
959+
report_data['ssl']['expired'] = ssl.cert_time_to_seconds(cert['notAfter']) < time.time()
960+
issuer = dict(x[0] for x in cert.get('issuer', []))
961+
report_data['ssl']['not_after'] = cert['notAfter']
962+
report_data['ssl']['issuer'] = issuer.get('organizationName', issuer.get('commonName', 'Unknown'))
963+
ver = ssock.version()
964+
if ver:
965+
strength = 'Good' if ver in ['TLSv1.2', 'TLSv1.3'] else 'Weak'
966+
report_data['ssl']['tls_versions'] = [(ver, strength)]
967+
except Exception as e:
968+
report_data['ssl']['valid'] = 'Error'
969+
report_data['ssl']['error'] = str(e)
970+
if '[Errno 54]' in str(e) or 'timed out' in str(e).lower() or 'Connection refused' in str(e):
971+
report_data['ssl']['blocked'] = True
972+
print(f"[{domain}] WARNING: Access Blocked/Reset by Domain Firewall/WAF.", flush=True)
973+
974+
# 2. HTTP Headers & Server Banner
975+
print(f"[{domain}] Fetching HTTP Security Headers...", flush=True)
976+
try:
977+
ctx = ssl._create_unverified_context()
978+
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'})
979+
with urllib.request.urlopen(req, context=ctx, timeout=2) as resp:
980+
headers = resp.headers
981+
except urllib.error.HTTPError as e:
982+
headers = e.headers
983+
except Exception as e:
984+
headers = {}
985+
report_data['headers']['error'] = str(e)
986+
987+
if headers:
988+
report_data['headers'] = {
989+
"Strict-Transport-Security": headers.get("Strict-Transport-Security", "Missing"),
990+
"Content-Security-Policy": headers.get("Content-Security-Policy", "Missing"),
991+
"X-Content-Type-Options": headers.get("X-Content-Type-Options", "Missing")
992+
}
993+
report_data['banner'] = {
994+
"Server": headers.get("Server", "Hidden"),
995+
"X-Powered-By": headers.get("X-Powered-By", headers.get("X-Powered-By-Plesk", "Hidden"))
996+
}
997+
998+
# 3. IP Geolocation ONLY
999+
print(f"[{domain}] Fetching IP Geolocation...", flush=True)
1000+
try:
1001+
ip = socket.gethostbyname(domain)
1002+
geo_req = urllib.request.Request(f"http://ip-api.com/json/{ip}")
1003+
with urllib.request.urlopen(geo_req, timeout=2) as geo_resp:
1004+
geo_data = json.loads(geo_resp.read().decode())
1005+
report_data['geo'] = {
1006+
"IP": ip,
1007+
"Country": geo_data.get("country", "Unknown"),
1008+
"Hosting Provider": geo_data.get("isp", geo_data.get("org", "Unknown"))
1009+
}
1010+
except Exception as e:
1011+
report_data['geo'] = {"IP": socket.gethostbyname(domain) if 'domain' in locals() else 'Unknown'}
1012+
1013+
return report_data
1014+
1015+
1016+
def cryptography_scan_run(url, security_report_dir=None):
1017+
if hasattr(security_report_dir, 'mkdir'):
1018+
security_report_dir.mkdir(parents=True, exist_ok=True)
1019+
elif security_report_dir:
1020+
os.makedirs(security_report_dir, exist_ok=True)
1021+
1022+
print(f"Running Cryptography scan for {url}...")
1023+
1024+
report_data = get_cryptography_data(url)
1025+
domain = report_data.get("domain", url)
1026+
1027+
html_result = generate_crypto_html(report_data, domain, security_report_dir)
1028+
print("Cryptography scan complete!")
1029+
return {
1030+
"report_data": report_data,
6951031
"html": html_result
6961032
}

0 commit comments

Comments
 (0)