Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ A Python3 Script for Auditing IKE VPN Servers
- Risk rates findings with summary and recommendations
- Support for fingerprinting via vendor ID (VID)
- Support for fingerprinting via backoff pattern (optional)
- Support for probing over NAT-T / UDP 4500 (optional)

## Usage

> [!CAUTION]
> This script requires the binary `ike-scan` and must be run as root

```bash
usage: ikess [-h] [--fullalgs] [--fingerprint] [--enc ENC] [--hash HASH] [--auth AUTH] [--group GROUP] [--onlycustom] targets [targets ...]
usage: ikess [-h] [--fullalgs] [--fingerprint] [--enc ENC] [--hash HASH] [--auth AUTH] [--group GROUP] [--onlycustom] [--nat-t] targets [targets ...]

ikess - IKE Security Scanner (Sequential Mode)

Expand Down Expand Up @@ -58,6 +59,9 @@ options:
Examples: --group G14,G16 or --dh MODP2048,MODP4096 or --group 14,16 (default: None)
--onlycustom Scan only the transforms built from your custom --enc/--hash/--auth/--group lists. Without this
flag, custom items are merged into the curated or expanded set. (default: False)
--nat-t, --natt Probe over NAT-T (RFC 3947): UDP 4500 source+dest with non-ESP marker (ike-scan --nat-t).
Applies to every ike-scan call. Report files get a '_natt4500' suffix. Note: many gateways
only answer on 4500 after NAT detection on 500, so no response here is not conclusive. (default: False)

Aliases you can use for --enc, --hash, --auth, --group:
ENC: DES=1, 3DES=5, AES=7/128, AES128=7/128, AES192=7/192, AES256=7/256
Expand All @@ -72,6 +76,7 @@ Examples:
sudo ./ikess.py 10.0.0.1 --enc DES,3DES --onlycustom
sudo ./ikess.py 10.0.0.1 --enc AES128,3DES,1,7/256 --hash SHA1,SHA256,1 --auth PSK,RSA --group G2,G14,16
sudo ./ikess.py 203.0.113.5 --enc AES256 --hash SHA256 --auth PSK --group MODP2048 --onlycustom
sudo ./ikess.py 203.0.113.5 --nat-t
```

You can also run via Docker:
Expand Down
36 changes: 32 additions & 4 deletions ikess.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
CUSTOM_AUTH: List[str] = []
CUSTOM_GROUP: List[str] = []
ONLYCUSTOM: bool = False
NATT: bool = False
# Extra flags prepended to every ike-scan invocation (e.g. --nat-t)
EXTRA_IKESCAN_ARGS: List[str] = []

# --------------------------- Vulnerability text ------------------------
FLAWS = {
Expand Down Expand Up @@ -259,6 +262,8 @@ def _implementation_sources(data: Dict[str, Any]) -> Dict[str, Optional[str]]:
return {"backoff": backoff, "vid": vid_choice, "primary": primary}

def run_command(cmd: List[str], timeout: int = 30) -> Tuple[str, str, int]:
if cmd and cmd[0] == "ike-scan" and EXTRA_IKESCAN_ARGS:
cmd = [cmd[0]] + EXTRA_IKESCAN_ARGS + cmd[1:]
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
return (p.stdout.strip(), p.stderr.strip(), p.returncode)
Expand Down Expand Up @@ -686,6 +691,10 @@ def generate_html_report(results: Dict, filename: str):

scope_badge = '<span class="badge text-bg-primary">Scope: --onlycustom</span>' if confirmed_only \
else '<span class="badge text-bg-light">Scope: default</span>'
if results.get("scan_info", {}).get("nat_t", False):
scope_badge += ' <span class="badge text-bg-warning">Transport: NAT-T (UDP 4500)</span>'
else:
scope_badge += ' <span class="badge text-bg-light">Transport: UDP 500</span>'

host_cards = []

Expand Down Expand Up @@ -1076,6 +1085,8 @@ def generate_reports(vpns: Dict[str, Dict[str, Any]], start_time: str, end_time:
"end_time": end_time,
"targets": list(vpns.keys()),
"confirmed_only": ONLYCUSTOM,
"nat_t": NATT,
"udp_port": 4500 if NATT else 500,
}

# ensure subfolder exists
Expand All @@ -1086,9 +1097,10 @@ def generate_reports(vpns: Dict[str, Dict[str, Any]], start_time: str, end_time:
ts = datetime.now().strftime("%Y%m%d-%H%M%S")

# build timestamped paths
xml_path = out_dir / f"{ts}_ikess_scan.xml"
json_path = out_dir / f"{ts}_ikess_scan.json"
html_path = out_dir / f"{ts}_ikess_scan.html"
suffix = "_natt4500" if NATT else ""
xml_path = out_dir / f"{ts}_ikess_scan{suffix}.xml"
json_path = out_dir / f"{ts}_ikess_scan{suffix}.json"
html_path = out_dir / f"{ts}_ikess_scan{suffix}.html"

generate_xml_report(results, str(xml_path))
with open(json_path, "w", encoding="utf-8") as f:
Expand Down Expand Up @@ -1144,7 +1156,7 @@ def scan_target(ip: str) -> Dict[str, Any]:
return vpn

def main() -> int:
global FULLALGS, FINGERPRINT, CUSTOM_ENC, CUSTOM_HASH, CUSTOM_AUTH, CUSTOM_GROUP, ONLYCUSTOM
global FULLALGS, FINGERPRINT, CUSTOM_ENC, CUSTOM_HASH, CUSTOM_AUTH, CUSTOM_GROUP, ONLYCUSTOM, NATT

class SmartFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter):
pass
Expand Down Expand Up @@ -1180,6 +1192,7 @@ class SmartFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHel
" sudo ./ikess.py 10.0.0.1 --enc DES,3DES --onlycustom\n"
" sudo ./ikess.py 10.0.0.1 --enc AES128,3DES,1,7/256 --hash SHA1,SHA256,1 --auth PSK,RSA --group G2,G14,16\n"
" sudo ./ikess.py 203.0.113.5 --enc AES256 --hash SHA256 --auth PSK --group MODP2048 --onlycustom\n"
" sudo ./ikess.py 203.0.113.5 --nat-t\n"
),
)

Expand Down Expand Up @@ -1245,6 +1258,17 @@ class SmartFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHel
),
)

parser.add_argument(
"--nat-t", "--natt",
dest="nat_t",
action="store_true",
help=(
"Probe over NAT-T (RFC 3947): UDP 4500 source+dest with non-ESP marker (ike-scan --nat-t).\n"
"Applies to every ike-scan call. Report files get a '_natt4500' suffix. Note: many gateways\n"
"only answer on 4500 after NAT detection on 500, so no response here is not conclusive."
),
)

args = parser.parse_args()

CUSTOM_ENC = _parse_list_arg(args.enc, ENC_ALIASES)
Expand All @@ -1254,6 +1278,9 @@ class SmartFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHel
ONLYCUSTOM = args.onlycustom
FULLALGS = args.fullalgs
FINGERPRINT = args.fingerprint
NATT = args.nat_t
if NATT:
EXTRA_IKESCAN_ARGS.append("--nat-t")

if not check_ike_dependency():
return 1
Expand All @@ -1262,6 +1289,7 @@ class SmartFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHel
start = datetime.now()
logger.info(f"Scan started at {start:%Y-%m-%d %H:%M:%S}")
logger.info(f"Targets: {', '.join(args.targets)}")
logger.info(f"Transport: {'NAT-T (UDP 4500, ike-scan --nat-t)' if NATT else 'UDP 500'}")

ips_to_scan: List[str] = []
for t in args.targets:
Expand Down