Upload files to "modules"
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
cloud_enrich.py — IP-to-cloud-provider enrichment for FLOCK_scan
|
||||
|
||||
Turns hardcoded FLOCK_CLOUD_IPS into dynamic enrichment:
|
||||
- WHOIS / ASN lookup via ip-api.com or ipinfo.io (free, no key)
|
||||
- Maps IP → { org, asn, country, region, cloud_provider }
|
||||
- Cloud provider detection from ASN + org name
|
||||
|
||||
Usage:
|
||||
from modules.cloud_enrich import enrich_ip, enrich_ip_batch
|
||||
info = enrich_ip("52.72.49.79")
|
||||
# → {"ip": "...", "org": "Amazon Technologies", "asn": "AS16509",
|
||||
# "country": "US", "region": "Virginia", "cloud": "AWS"}
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import socket
|
||||
|
||||
# ── Fallback ASN database (no network call) ────────────────────────
|
||||
# Maps ASN prefixes to known cloud providers.
|
||||
# Helps when ip-api.com is unavailable.
|
||||
|
||||
CLOUD_ASN_MAP = {
|
||||
# AWS
|
||||
"16509": "AWS", "14618": "AWS", "7224": "AWS",
|
||||
"8987": "AWS", "17493": "AWS", "39111": "AWS",
|
||||
"31763": "AWS", "38895": "AWS", "7018": "AWS",
|
||||
# GCP / Google Cloud
|
||||
"15169": "GCP", "36040": "GCP", "36384": "GCP",
|
||||
"41264": "GCP", "19448": "GCP", "26910": "GCP",
|
||||
# Azure / Microsoft
|
||||
"8075": "Azure", "12076": "Azure", "63314": "Azure",
|
||||
"13100": "Azure", "31898": "Azure", "13526": "Azure",
|
||||
# CloudFlare
|
||||
"13335": "CloudFlare", "209242": "CloudFlare",
|
||||
"14789": "CloudFlare", "203898": "CloudFlare",
|
||||
# DigitalOcean
|
||||
"14061": "DigitalOcean", "62567": "DigitalOcean",
|
||||
# OVH
|
||||
"16276": "OVH", "35540": "OVH",
|
||||
# Linode
|
||||
"63949": "Linode", "48270": "Linode",
|
||||
# Vultr
|
||||
"20473": "Vultr", "208722": "Vultr",
|
||||
# Hetzner
|
||||
"24940": "Hetzner", "213230": "Hetzner",
|
||||
# Oracle Cloud
|
||||
"31898": "Oracle", "395050": "Oracle",
|
||||
# Fastly
|
||||
"54113": "Fastly", "201737": "Fastly",
|
||||
# Akamai
|
||||
"16625": "Akamai", "12222": "Akamai", "21399": "Akamai",
|
||||
# Linode
|
||||
"63949": "Linode",
|
||||
# Scaleway
|
||||
"12876": "Scaleway",
|
||||
# UpCloud
|
||||
"202053": "UpCloud",
|
||||
}
|
||||
|
||||
CLOUD_ORG_KEYWORDS = [
|
||||
("amazon", "AWS"),
|
||||
("aws", "AWS"),
|
||||
("amazon technologies", "AWS"),
|
||||
("amazon data services", "AWS"),
|
||||
("amazon web services", "AWS"),
|
||||
("amazon.com", "AWS"),
|
||||
("elastic load balancing", "AWS"),
|
||||
("google cloud", "GCP"),
|
||||
("google compute", "GCP"),
|
||||
("gcp", "GCP"),
|
||||
("microsoft azure", "Azure"),
|
||||
("azure", "Azure"),
|
||||
("microsoft corporation", "Azure"),
|
||||
("cloudflare", "CloudFlare"),
|
||||
("digitalocean", "DigitalOcean"),
|
||||
("linode", "Linode"),
|
||||
("vultr", "Vultr"),
|
||||
("hetzner", "Hetzner"),
|
||||
("oracle cloud", "Oracle"),
|
||||
("oracle public cloud", "Oracle"),
|
||||
("ovh", "OVH"),
|
||||
("fastly", "Fastly"),
|
||||
("akamai", "Akamai"),
|
||||
("scaleway", "Scaleway"),
|
||||
("upcloud", "UpCloud"),
|
||||
]
|
||||
|
||||
|
||||
# ── ASN / WHOIS Lookup ──────────────────────────────────────────────
|
||||
|
||||
def _reverse_dns(ip, timeout=3):
|
||||
"""Try to PTR the IP — sometimes reveals cloud hostname directly."""
|
||||
try:
|
||||
name, _, _ = socket.gethostbyaddr(ip)
|
||||
return name
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def enrich_ip(ip, timeout=5):
|
||||
"""
|
||||
Look up IP enrichment data from ip-api.com (free, no API key).
|
||||
|
||||
Returns dict with:
|
||||
ip, org, asn, country, region, city, cloud, reverse_dns
|
||||
|
||||
Falls back gracefully if the HTTP lookup fails.
|
||||
"""
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
result = {
|
||||
"ip": ip,
|
||||
"org": None,
|
||||
"asn": None,
|
||||
"country": None,
|
||||
"region": None,
|
||||
"city": None,
|
||||
"cloud": None,
|
||||
"reverse_dns": None,
|
||||
}
|
||||
|
||||
# PTR first (fast, local)
|
||||
try:
|
||||
rdns = _reverse_dns(ip)
|
||||
result["reverse_dns"] = rdns
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ip-api.com — limited to 45 req/min from a single IP (free tier)
|
||||
try:
|
||||
r = requests.get(
|
||||
f"http://ip-api.com/json/{ip}",
|
||||
timeout=timeout,
|
||||
headers={"User-Agent": "FLOCK_scan/3.0"},
|
||||
)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
if data.get("status") == "success":
|
||||
result["org"] = data.get("org")
|
||||
result["asn"] = data.get("asn")
|
||||
result["country"] = data.get("country")
|
||||
result["region"] = data.get("regionName")
|
||||
result["city"] = data.get("city")
|
||||
result["isp"] = data.get("isp")
|
||||
|
||||
# Determine cloud provider
|
||||
org = (result["org"] or "").lower()
|
||||
asn = result.get("asn") or ""
|
||||
result["cloud"] = _detect_cloud_provider(org, asn)
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: cli-based whois
|
||||
try:
|
||||
import subprocess
|
||||
whois_out = subprocess.run(
|
||||
["whois", ip],
|
||||
capture_output=True, text=True, timeout=timeout
|
||||
).stdout.lower()
|
||||
for line in whois_out.split("\n"):
|
||||
if "orgname:" in line:
|
||||
result["org"] = line.split(":", 1)[1].strip()
|
||||
if "originas:" in line or "origin:" in line:
|
||||
asn = line.split(":", 1)[1].strip().lstrip("AS")
|
||||
result["asn"] = f"AS{asn}"
|
||||
if "netname:" in line:
|
||||
if not result.get("org"):
|
||||
result["org"] = line.split(":", 1)[1].strip()
|
||||
if "country:" in line and not result.get("country"):
|
||||
result["country"] = line.split(":", 1)[1].strip().upper()
|
||||
|
||||
org = (result.get("org") or "").lower()
|
||||
asn = result.get("asn", "").replace("AS", "")
|
||||
result["cloud"] = _detect_cloud_provider(org, asn)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _detect_cloud_provider(org, asn):
|
||||
"""Match org string + ASN against known cloud providers."""
|
||||
# ASN match first
|
||||
if asn and asn in CLOUD_ASN_MAP:
|
||||
return CLOUD_ASN_MAP[asn]
|
||||
|
||||
# Org keyword match
|
||||
for keyword, provider in CLOUD_ORG_KEYWORDS:
|
||||
if keyword in org.lower():
|
||||
return provider
|
||||
|
||||
# PTR-based: if we have a reverse DNS, check for cloud patterns
|
||||
return None
|
||||
|
||||
|
||||
def enrich_ip_batch(ips, timeout=10):
|
||||
"""
|
||||
Batch enrich multiple IPs.
|
||||
Handles ip-api.com's 45 req/min rate limit with simple sleep.
|
||||
Also uses batch endpoint for efficiency.
|
||||
|
||||
Returns dict of ip -> result.
|
||||
"""
|
||||
import requests
|
||||
import time
|
||||
|
||||
# Try batch endpoint first (ip-api.com supports up to 100 IPs)
|
||||
try:
|
||||
r = requests.post(
|
||||
"http://ip-api.com/batch",
|
||||
json=ips[:100], # max 100 per batch
|
||||
timeout=timeout,
|
||||
headers={"User-Agent": "FLOCK_scan/3.0"},
|
||||
)
|
||||
if r.status_code == 200:
|
||||
batch_data = r.json()
|
||||
results = {}
|
||||
for item in batch_data:
|
||||
ip = item.get("query")
|
||||
if not ip:
|
||||
continue
|
||||
org = item.get("org", "")
|
||||
asn = item.get("asn", "")
|
||||
results[ip] = {
|
||||
"ip": ip,
|
||||
"org": org,
|
||||
"asn": asn,
|
||||
"country": item.get("country"),
|
||||
"region": item.get("regionName"),
|
||||
"city": item.get("city"),
|
||||
"isp": item.get("isp"),
|
||||
"cloud": _detect_cloud_provider(
|
||||
(org or "").lower(),
|
||||
(asn or "").replace("AS", "")
|
||||
),
|
||||
"reverse_dns": _reverse_dns(ip),
|
||||
}
|
||||
return results
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: one by one
|
||||
results = {}
|
||||
for ip in ips:
|
||||
results[ip] = enrich_ip(ip, timeout=5)
|
||||
time.sleep(1.5) # rate limit: ~40/min
|
||||
return results
|
||||
|
||||
|
||||
# ── CLI test ─────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else "52.72.49.79"
|
||||
print(json.dumps(enrich_ip(target), indent=2, default=str))
|
||||
Reference in New Issue
Block a user