1#!/usr/bin/env python3
2"""
3λ€νΈμν¬ λ³΄μ μ€μΊλ (κ΅μ‘μ©)
4Network Security Scanner (Educational)
5
6ν¬νΈ μ€μΊλ, HTTP 보μ ν€λ κ²μ¬, SSL/TLS μ 보 μμ§, λ°°λ κ·ΈλλΉ,
7리ν¬νΈ μμ±μ ν¬ν¨νλ κ΅μ‘μ© λ€νΈμν¬ λ³΄μ μ€μΊλμ
λλ€.
8λͺ¨λ μ€μΊμ λ μ΄νΈ 리미ν
μ΄ μ μ©λ©λλ€.
9
10Implements an educational network security scanner with TCP port scanning,
11HTTP security header checking, SSL/TLS info gathering, banner grabbing,
12and report generation. Rate limiting is applied between all connections.
13
14============================================================================
15ETHICAL USE DISCLAIMER / μ€λ¦¬μ μ¬μ© κ³ μ§
16============================================================================
17
18This tool is provided STRICTLY for EDUCATIONAL purposes only.
19
20- ONLY scan systems you OWN or have EXPLICIT WRITTEN PERMISSION to test.
21- Unauthorized port scanning may violate laws (CFAA, Computer Misuse Act, etc.)
22- The authors assume NO LIABILITY for misuse of this tool.
23- When in doubt, scan localhost (127.0.0.1) only.
24
25μ΄ λꡬλ μ€μ§ κ΅μ‘ λͺ©μ μΌλ‘λ§ μ 곡λ©λλ€.
26- λ³ΈμΈμ΄ μμ νκ±°λ λͺ
μμ μλ©΄ νκ°λ₯Ό λ°μ μμ€ν
λ§ μ€μΊνμΈμ.
27- λ¬΄λ¨ ν¬νΈ μ€μΊλμ λ²λ₯ μλ°μ΄ λ μ μμ΅λλ€.
28============================================================================
29"""
30
31import argparse
32import json
33import socket
34import ssl
35import sys
36import time
37import re
38from datetime import datetime, timezone
39from urllib.parse import urlparse
40from http.client import HTTPSConnection, HTTPConnection
41
42
43# =============================================================================
44# Configuration (μ€μ )
45# =============================================================================
46
47DEFAULT_PORTS = [21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995,
48 3306, 5432, 6379, 8080, 8443, 27017]
49
50WELL_KNOWN_SERVICES = {
51 21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS",
52 80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS",
53 993: "IMAPS", 995: "POP3S", 3306: "MySQL", 5432: "PostgreSQL",
54 6379: "Redis", 8080: "HTTP-Alt", 8443: "HTTPS-Alt", 27017: "MongoDB",
55}
56
57# Security headers that should be present
58RECOMMENDED_HEADERS = {
59 "Strict-Transport-Security": {
60 "description": "Enforces HTTPS connections",
61 "severity": "HIGH",
62 },
63 "Content-Security-Policy": {
64 "description": "Prevents XSS and data injection attacks",
65 "severity": "HIGH",
66 },
67 "X-Content-Type-Options": {
68 "description": "Prevents MIME type sniffing",
69 "severity": "MEDIUM",
70 "expected": "nosniff",
71 },
72 "X-Frame-Options": {
73 "description": "Prevents clickjacking attacks",
74 "severity": "MEDIUM",
75 "expected_any": ["DENY", "SAMEORIGIN"],
76 },
77 "Referrer-Policy": {
78 "description": "Controls referrer information leakage",
79 "severity": "LOW",
80 },
81 "Permissions-Policy": {
82 "description": "Controls browser feature permissions",
83 "severity": "LOW",
84 },
85 "X-XSS-Protection": {
86 "description": "Legacy XSS filter (use CSP instead)",
87 "severity": "INFO",
88 "note": "Modern recommendation: set to '0' and rely on CSP",
89 },
90}
91
92
93# =============================================================================
94# 1. Port Scanner (ν¬νΈ μ€μΊλ)
95# =============================================================================
96
97class PortScanner:
98 """
99 TCP connect scanner for educational purposes.
100 Uses full TCP handshake (connect scan), not SYN scan.
101 """
102
103 def __init__(self, target: str, ports: list[int], timeout: float = 1.0,
104 delay: float = 0.1):
105 self.target = target
106 self.ports = ports
107 self.timeout = timeout
108 self.delay = delay # Rate limiting delay between connections
109 self.results: list[dict] = []
110
111 def scan(self) -> list[dict]:
112 """Scan all configured ports with rate limiting."""
113 print(f"\n Scanning {self.target} ({len(self.ports)} ports)...")
114 print(f" Timeout: {self.timeout}s | Delay: {self.delay}s between probes\n")
115
116 self.results = []
117 for i, port in enumerate(self.ports):
118 result = self._scan_port(port)
119 self.results.append(result)
120
121 if result["state"] == "open":
122 service = WELL_KNOWN_SERVICES.get(port, "unknown")
123 banner = result.get("banner", "")
124 banner_str = f" | Banner: {banner}" if banner else ""
125 print(f" Port {port:5d}/tcp OPEN ({service}){banner_str}")
126
127 # Rate limiting
128 if i < len(self.ports) - 1:
129 time.sleep(self.delay)
130
131 open_count = sum(1 for r in self.results if r["state"] == "open")
132 print(f"\n Scan complete: {open_count} open / {len(self.ports)} scanned")
133 return self.results
134
135 def _scan_port(self, port: int) -> dict:
136 """Attempt TCP connection to a single port."""
137 result = {
138 "port": port,
139 "service": WELL_KNOWN_SERVICES.get(port, "unknown"),
140 "state": "closed",
141 "banner": "",
142 }
143
144 try:
145 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
146 sock.settimeout(self.timeout)
147 error = sock.connect_ex((self.target, port))
148
149 if error == 0:
150 result["state"] = "open"
151 # Attempt banner grab
152 result["banner"] = self._grab_banner(sock, port)
153 sock.close()
154 except socket.timeout:
155 result["state"] = "filtered"
156 except OSError:
157 result["state"] = "closed"
158
159 return result
160
161 def _grab_banner(self, sock: socket.socket, port: int) -> str:
162 """Attempt to read a service banner from an open port."""
163 try:
164 # Some services send banner on connect, others need a nudge
165 if port in (80, 8080):
166 # HTTP: send a minimal request
167 sock.sendall(b"HEAD / HTTP/1.0\r\nHost: localhost\r\n\r\n")
168 elif port in (25, 110, 143):
169 pass # SMTP/POP3/IMAP send banner automatically
170
171 sock.settimeout(0.5)
172 banner = sock.recv(256)
173 # Clean up banner: remove non-printable chars
174 cleaned = re.sub(r'[^\x20-\x7e]', '', banner.decode("utf-8", errors="replace"))
175 return cleaned.strip()[:100]
176 except (socket.timeout, OSError, UnicodeDecodeError):
177 return ""
178
179
180# =============================================================================
181# 2. HTTP Security Header Checker (HTTP 보μ ν€λ κ²μ¬)
182# =============================================================================
183
184class HeaderChecker:
185 """Check HTTP response headers against security best practices."""
186
187 def __init__(self, target: str):
188 self.target = target
189 self.response_headers: dict = {}
190 self.findings: list[dict] = []
191
192 def check(self) -> list[dict]:
193 """Fetch headers and analyze them."""
194 print(f"\n Checking HTTP security headers for: {self.target}")
195
196 self.response_headers = self._fetch_headers()
197 if not self.response_headers:
198 print(" ERROR: Could not connect to target")
199 return []
200
201 print(f"\n Response Headers Received:")
202 for key, value in sorted(self.response_headers.items()):
203 print(f" {key}: {value[:60]}")
204
205 self.findings = self._analyze_headers()
206
207 print(f"\n Security Header Analysis:")
208 for finding in self.findings:
209 symbol = "OK" if finding["status"] == "present" else "!!"
210 print(f" [{symbol}] [{finding['severity']:6s}] {finding['header']}")
211 print(f" {finding['message']}")
212
213 present = sum(1 for f in self.findings if f["status"] == "present")
214 total = len(self.findings)
215 print(f"\n Score: {present}/{total} recommended headers present")
216 return self.findings
217
218 def _fetch_headers(self) -> dict:
219 """Fetch HTTP(S) headers from target."""
220 try:
221 parsed = urlparse(self.target if "://" in self.target else f"https://{self.target}")
222 host = parsed.hostname or self.target
223 port = parsed.port
224 path = parsed.path or "/"
225
226 if parsed.scheme == "https" or (port and port == 443):
227 conn = HTTPSConnection(host, port=port or 443, timeout=5)
228 else:
229 conn = HTTPConnection(host, port=port or 80, timeout=5)
230
231 conn.request("HEAD", path)
232 response = conn.getresponse()
233 headers = dict(response.getheaders())
234 conn.close()
235 return headers
236 except Exception as e:
237 print(f" Connection error: {e}")
238 return {}
239
240 def _analyze_headers(self) -> list[dict]:
241 """Compare response headers against recommendations."""
242 findings = []
243 # Normalize header keys to title case for comparison
244 norm_headers = {k.lower(): v for k, v in self.response_headers.items()}
245
246 for header, info in RECOMMENDED_HEADERS.items():
247 header_lower = header.lower()
248 finding = {
249 "header": header,
250 "severity": info["severity"],
251 "description": info["description"],
252 }
253
254 if header_lower in norm_headers:
255 value = norm_headers[header_lower]
256 finding["status"] = "present"
257 finding["value"] = value
258 finding["message"] = f"Present: {value[:50]}"
259
260 # Check expected value
261 if "expected" in info and info["expected"].lower() != value.lower():
262 finding["message"] += f" (recommended: {info['expected']})"
263 if "expected_any" in info:
264 if not any(v.lower() in value.lower() for v in info["expected_any"]):
265 finding["message"] += f" (recommended: {' or '.join(info['expected_any'])})"
266 else:
267 finding["status"] = "missing"
268 finding["message"] = f"MISSING - {info['description']}"
269
270 findings.append(finding)
271
272 # Check for information disclosure headers
273 disclosure_headers = ["server", "x-powered-by", "x-aspnet-version"]
274 for h in disclosure_headers:
275 if h in norm_headers:
276 findings.append({
277 "header": h.title(),
278 "severity": "LOW",
279 "status": "warning",
280 "value": norm_headers[h],
281 "message": f"Information disclosure: {norm_headers[h]} (consider removing)",
282 "description": "Reveals server technology information",
283 })
284
285 return findings
286
287
288# =============================================================================
289# 3. SSL/TLS Info Gatherer (SSL/TLS μ 보 μμ§)
290# =============================================================================
291
292class TLSChecker:
293 """Gather SSL/TLS certificate and protocol information."""
294
295 def __init__(self, target: str, port: int = 443):
296 self.target = target
297 self.port = port
298 self.cert_info: dict = {}
299
300 def check(self) -> dict:
301 """Connect and gather TLS information."""
302 print(f"\n Checking SSL/TLS for: {self.target}:{self.port}")
303
304 try:
305 context = ssl.create_default_context()
306 with socket.create_connection((self.target, self.port), timeout=5) as sock:
307 with context.wrap_socket(sock, server_hostname=self.target) as ssock:
308 self.cert_info = self._extract_info(ssock)
309 self._print_info()
310 return self.cert_info
311 except ssl.SSLCertVerificationError as e:
312 print(f" SSL Certificate Error: {e}")
313 self.cert_info = {"error": str(e), "verified": False}
314 except (socket.timeout, OSError) as e:
315 print(f" Connection Error: {e}")
316 self.cert_info = {"error": str(e)}
317
318 return self.cert_info
319
320 def _extract_info(self, ssock: ssl.SSLSocket) -> dict:
321 """Extract TLS session and certificate details."""
322 cert = ssock.getpeercert()
323 cipher = ssock.cipher()
324
325 info = {
326 "protocol": ssock.version(),
327 "cipher_suite": cipher[0] if cipher else "unknown",
328 "cipher_bits": cipher[2] if cipher else 0,
329 "verified": True,
330 "subject": dict(x[0] for x in cert.get("subject", ())),
331 "issuer": dict(x[0] for x in cert.get("issuer", ())),
332 "serial": cert.get("serialNumber", ""),
333 "not_before": cert.get("notBefore", ""),
334 "not_after": cert.get("notAfter", ""),
335 "san": [entry[1] for entry in cert.get("subjectAltName", ())],
336 }
337
338 # Check expiry
339 if info["not_after"]:
340 try:
341 expiry = datetime.strptime(info["not_after"], "%b %d %H:%M:%S %Y %Z")
342 days_left = (expiry - datetime.utcnow()).days
343 info["days_until_expiry"] = days_left
344 info["expired"] = days_left < 0
345 except ValueError:
346 pass
347
348 return info
349
350 def _print_info(self):
351 """Print TLS information in a readable format."""
352 info = self.cert_info
353 print(f"\n TLS Connection:")
354 print(f" Protocol: {info.get('protocol', 'N/A')}")
355 print(f" Cipher: {info.get('cipher_suite', 'N/A')}")
356 print(f" Key bits: {info.get('cipher_bits', 'N/A')}")
357 print(f" Verified: {info.get('verified', False)}")
358
359 print(f"\n Certificate:")
360 subject = info.get("subject", {})
361 print(f" Subject: {subject.get('commonName', 'N/A')}")
362 issuer = info.get("issuer", {})
363 print(f" Issuer: {issuer.get('organizationName', 'N/A')}")
364 print(f" Valid from: {info.get('not_before', 'N/A')}")
365 print(f" Valid until: {info.get('not_after', 'N/A')}")
366
367 days = info.get("days_until_expiry")
368 if days is not None:
369 status = "EXPIRED" if days < 0 else f"{days} days remaining"
370 if 0 < days <= 30:
371 status += " (WARNING: expiring soon)"
372 print(f" Expiry: {status}")
373
374 san = info.get("san", [])
375 if san:
376 print(f" SAN entries: {', '.join(san[:5])}")
377 if len(san) > 5:
378 print(f" ... and {len(san) - 5} more")
379
380
381# =============================================================================
382# 4. Report Generator (리ν¬νΈ μμ±κΈ°)
383# =============================================================================
384
385class ReportGenerator:
386 """Generate a structured security scan report."""
387
388 def __init__(self, target: str):
389 self.target = target
390 self.timestamp = datetime.now(timezone.utc).isoformat()
391 self.sections: list[dict] = []
392
393 def add_port_scan(self, results: list[dict]):
394 self.sections.append({"type": "port_scan", "data": results})
395
396 def add_header_check(self, findings: list[dict]):
397 self.sections.append({"type": "header_check", "data": findings})
398
399 def add_tls_check(self, info: dict):
400 self.sections.append({"type": "tls_check", "data": info})
401
402 def generate_text(self) -> str:
403 """Generate a plain-text report."""
404 lines = [
405 "=" * 70,
406 " SECURITY SCAN REPORT",
407 "=" * 70,
408 f" Target: {self.target}",
409 f" Timestamp: {self.timestamp}",
410 f" Sections: {len(self.sections)}",
411 "",
412 ]
413
414 for section in self.sections:
415 if section["type"] == "port_scan":
416 lines.append("-" * 70)
417 lines.append(" PORT SCAN RESULTS")
418 lines.append("-" * 70)
419 open_ports = [r for r in section["data"] if r["state"] == "open"]
420 for r in open_ports:
421 banner = f" Banner: {r['banner']}" if r["banner"] else ""
422 lines.append(f" {r['port']:5d}/tcp {r['state']:8s} {r['service']}{banner}")
423 lines.append(f"\n Open: {len(open_ports)} / Scanned: {len(section['data'])}")
424 lines.append("")
425
426 elif section["type"] == "header_check":
427 lines.append("-" * 70)
428 lines.append(" HTTP SECURITY HEADERS")
429 lines.append("-" * 70)
430 for f in section["data"]:
431 symbol = "OK" if f["status"] == "present" else "!!"
432 lines.append(f" [{symbol}] {f['header']:35s} {f['message'][:50]}")
433 lines.append("")
434
435 elif section["type"] == "tls_check":
436 lines.append("-" * 70)
437 lines.append(" TLS/SSL INFORMATION")
438 lines.append("-" * 70)
439 data = section["data"]
440 if "error" in data:
441 lines.append(f" Error: {data['error']}")
442 else:
443 lines.append(f" Protocol: {data.get('protocol', 'N/A')}")
444 lines.append(f" Cipher: {data.get('cipher_suite', 'N/A')}")
445 subj = data.get('subject', {})
446 lines.append(f" Subject: {subj.get('commonName', 'N/A')}")
447 days = data.get('days_until_expiry')
448 if days is not None:
449 lines.append(f" Expiry: {days} days remaining")
450 lines.append("")
451
452 lines.append("=" * 70)
453 lines.append(" END OF REPORT")
454 lines.append("=" * 70)
455
456 return "\n".join(lines)
457
458 def generate_json(self) -> str:
459 """Generate a JSON report."""
460 report = {
461 "target": self.target,
462 "timestamp": self.timestamp,
463 "sections": self.sections,
464 }
465 return json.dumps(report, indent=2, default=str)
466
467
468# =============================================================================
469# 5. CLI Interface (CLI μΈν°νμ΄μ€)
470# =============================================================================
471
472def parse_port_range(port_str: str) -> list[int]:
473 """Parse port specification: '80', '80,443', '1-1024', 'common'."""
474 if port_str.lower() == "common":
475 return DEFAULT_PORTS
476
477 ports = set()
478 for part in port_str.split(","):
479 part = part.strip()
480 if "-" in part:
481 start, end = part.split("-", 1)
482 start, end = int(start), int(end)
483 if 1 <= start <= end <= 65535:
484 ports.update(range(start, end + 1))
485 else:
486 p = int(part)
487 if 1 <= p <= 65535:
488 ports.add(p)
489 return sorted(ports)
490
491
492def run_scan(args):
493 """Execute the scan based on CLI arguments."""
494 target = args.target
495 report = ReportGenerator(target)
496
497 # Resolve hostname
498 try:
499 ip = socket.gethostbyname(target)
500 print(f"\n Target: {target} ({ip})")
501 except socket.gaierror:
502 print(f"\n ERROR: Cannot resolve hostname: {target}")
503 return
504
505 # Port scan
506 if not args.skip_ports:
507 ports = parse_port_range(args.ports)
508 scanner = PortScanner(target, ports, timeout=args.timeout, delay=args.delay)
509 results = scanner.scan()
510 report.add_port_scan(results)
511
512 # HTTP header check
513 if not args.skip_headers:
514 checker = HeaderChecker(target)
515 findings = checker.check()
516 if findings:
517 report.add_header_check(findings)
518
519 # TLS check
520 if not args.skip_tls:
521 tls = TLSChecker(target, port=args.tls_port)
522 info = tls.check()
523 if info:
524 report.add_tls_check(info)
525
526 # Generate report
527 if args.output:
528 if args.format == "json":
529 report_text = report.generate_json()
530 else:
531 report_text = report.generate_text()
532
533 with open(args.output, "w") as f:
534 f.write(report_text)
535 print(f"\n Report saved to: {args.output}")
536 else:
537 print("\n" + report.generate_text())
538
539
540def run_demo():
541 """Run a localhost-only demo when no arguments are provided."""
542 print("=" * 60)
543 print(" Network Security Scanner - Demo Mode")
544 print(" λ€νΈμν¬ λ³΄μ μ€μΊλ - λ°λͺ¨ λͺ¨λ")
545 print("=" * 60)
546
547 print("""
548 ETHICAL USE DISCLAIMER:
549 This tool is for EDUCATIONAL purposes only.
550 Only scan systems you own or have written permission to test.
551 Unauthorized scanning may violate applicable laws.
552""")
553
554 # Demo 1: Port scanner on localhost (most ports will be closed)
555 print("-" * 60)
556 print(" Demo 1: Port Scan (localhost, limited ports)")
557 print("-" * 60)
558
559 scanner = PortScanner(
560 "127.0.0.1",
561 [22, 80, 443, 3000, 5000, 5050, 8080, 8443],
562 timeout=0.3,
563 delay=0.05,
564 )
565 port_results = scanner.scan()
566
567 # Demo 2: Show header check logic (without real connection)
568 print("\n" + "-" * 60)
569 print(" Demo 2: HTTP Security Header Analysis (simulated)")
570 print("-" * 60)
571
572 # Simulate response headers from a typical website
573 simulated_headers = {
574 "Content-Type": "text/html; charset=utf-8",
575 "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
576 "X-Content-Type-Options": "nosniff",
577 "X-Frame-Options": "SAMEORIGIN",
578 "Server": "nginx/1.24.0",
579 }
580
581 print(f"\n Simulated response headers:")
582 for k, v in simulated_headers.items():
583 print(f" {k}: {v}")
584
585 print(f"\n Analysis:")
586 norm = {k.lower(): v for k, v in simulated_headers.items()}
587 for header, info in RECOMMENDED_HEADERS.items():
588 h_lower = header.lower()
589 if h_lower in norm:
590 print(f" [OK] {header}: {norm[h_lower][:50]}")
591 else:
592 print(f" [!!] {header}: MISSING - {info['description']}")
593
594 # Check info disclosure
595 if "server" in norm:
596 print(f" [!!] Server header reveals: {norm['server']} (consider removing)")
597
598 # Demo 3: Show TLS check info
599 print(f"\n" + "-" * 60)
600 print(" Demo 3: SSL/TLS Check (simulated)")
601 print("-" * 60)
602
603 simulated_tls = {
604 "protocol": "TLSv1.3",
605 "cipher_suite": "TLS_AES_256_GCM_SHA384",
606 "cipher_bits": 256,
607 "verified": True,
608 "subject": {"commonName": "example.com"},
609 "issuer": {"organizationName": "Let's Encrypt"},
610 "days_until_expiry": 45,
611 }
612
613 print(f"\n Simulated TLS info:")
614 print(f" Protocol: {simulated_tls['protocol']}")
615 print(f" Cipher: {simulated_tls['cipher_suite']}")
616 print(f" Key bits: {simulated_tls['cipher_bits']}")
617 print(f" Subject: {simulated_tls['subject']['commonName']}")
618 print(f" Issuer: {simulated_tls['issuer']['organizationName']}")
619 print(f" Expiry: {simulated_tls['days_until_expiry']} days remaining")
620
621 # Demo 4: Report generation
622 print(f"\n" + "-" * 60)
623 print(" Demo 4: Report Generation")
624 print("-" * 60)
625
626 report = ReportGenerator("127.0.0.1")
627 report.add_port_scan(port_results)
628 print(f"\n{report.generate_text()}")
629
630 # CLI usage info
631 print("\n" + "-" * 60)
632 print(" CLI Usage Examples:")
633 print("-" * 60)
634 print("""
635 # Scan localhost (safe for testing)
636 python scanner.py 127.0.0.1
637
638 # Scan common ports on your own server
639 python scanner.py myserver.com --ports common
640
641 # Scan specific port range with custom timeout
642 python scanner.py myserver.com --ports 80,443,8080 --timeout 2.0
643
644 # Full scan with JSON report output
645 python scanner.py myserver.com --ports 1-1024 --output report.json --format json
646
647 # Skip port scan, only check headers and TLS
648 python scanner.py myserver.com --skip-ports
649
650 # Skip TLS check
651 python scanner.py myserver.com --skip-tls
652""")
653
654
655def build_parser() -> argparse.ArgumentParser:
656 """Build the CLI argument parser."""
657 parser = argparse.ArgumentParser(
658 description="Educational Network Security Scanner",
659 epilog="DISCLAIMER: Only scan systems you own or have permission to test.",
660 )
661 parser.add_argument(
662 "target", nargs="?", default=None,
663 help="Target hostname or IP address (default: run demo)",
664 )
665 parser.add_argument(
666 "--ports", "-p", default="common",
667 help="Ports to scan: '80,443', '1-1024', or 'common' (default: common)",
668 )
669 parser.add_argument(
670 "--timeout", "-t", type=float, default=1.0,
671 help="Connection timeout in seconds (default: 1.0)",
672 )
673 parser.add_argument(
674 "--delay", "-d", type=float, default=0.1,
675 help="Delay between port probes in seconds (default: 0.1)",
676 )
677 parser.add_argument(
678 "--tls-port", type=int, default=443,
679 help="Port for TLS check (default: 443)",
680 )
681 parser.add_argument(
682 "--skip-ports", action="store_true",
683 help="Skip port scanning",
684 )
685 parser.add_argument(
686 "--skip-headers", action="store_true",
687 help="Skip HTTP header check",
688 )
689 parser.add_argument(
690 "--skip-tls", action="store_true",
691 help="Skip TLS/SSL check",
692 )
693 parser.add_argument(
694 "--output", "-o",
695 help="Output report to file",
696 )
697 parser.add_argument(
698 "--format", "-f", choices=["text", "json"], default="text",
699 help="Report format (default: text)",
700 )
701 return parser
702
703
704# =============================================================================
705# Main
706# =============================================================================
707
708if __name__ == "__main__":
709 parser = build_parser()
710 args = parser.parse_args()
711
712 print("\n" + "=" * 60)
713 print(" Network Security Scanner (Educational)")
714 print(" λ€νΈμν¬ λ³΄μ μ€μΊλ (κ΅μ‘μ©)")
715 print("=" * 60)
716
717 if args.target is None:
718 run_demo()
719 else:
720 print("""
721 DISCLAIMER: This tool is for educational purposes only.
722 Only scan systems you own or have written permission to test.
723""")
724 run_scan(args)
725
726 print("\n" + "=" * 60)
727 print(" Scan complete.")
728 print("=" * 60)