From a811fd8013716b4e0da48c4ee428500e3ef65770 Mon Sep 17 00:00:00 2001
From: Jill Kamperides <33240393+kampji@users.noreply.github.com>
Date: Sat, 24 Oct 2026 21:31:43 -0400
Subject: [PATCH 1/2] feat: add --nat-t option to probe IKE over UDP 4500
---
ikess.py | 36 ++++++++++++++++++++++++++++++++----
1 file changed, 32 insertions(+), 4 deletions(-)
diff --git a/ikess.py b/ikess.py
index 9d2b570..db3944a 100644
--- a/ikess.py
+++ b/ikess.py
@@ -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 = {
@@ -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)
@@ -686,6 +691,10 @@ def generate_html_report(results: Dict, filename: str):
scope_badge = 'Scope: --onlycustom' if confirmed_only \
else 'Scope: default'
+ if results.get("scan_info", {}).get("nat_t", False):
+ scope_badge += ' Transport: NAT-T (UDP 4500)'
+ else:
+ scope_badge += ' Transport: UDP 500'
host_cards = []
@@ -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
@@ -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:
@@ -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
@@ -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"
),
)
@@ -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)
@@ -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
@@ -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:
From b49f91aac339c5c9c613d8eb5541e9e36ec59dd3 Mon Sep 17 00:00:00 2001
From: Jill Kamperides <33240393+kampji@users.noreply.github.com>
Date: Sat, 24 Oct 2026 21:54:16 -0400
Subject: [PATCH 2/2] docs: document --nat-t option in README
---
README.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 19e5ebc..f04af32 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ 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
@@ -19,7 +20,7 @@ A Python3 Script for Auditing IKE VPN Servers
> 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)
@@ -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
@@ -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: