Add IP geolocation and subdomain enumeration plugins

- Added geo.py plugin for IP geolocation with ip-api.com and ipapi.co support
- Added subdomains.py plugin for subdomain enumeration using CertSpotter API
- Updated help.py with documentation for new plugins
- Updated README.md with documentation for new plugins
- Both plugins support domain/IP resolution and provide detailed information
This commit is contained in:
2026-04-28 23:58:17 -05:00
parent 634ae1c066
commit 2b92bd389e
4 changed files with 507 additions and 1 deletions
+53 -1
View File
@@ -240,7 +240,59 @@ Perform comprehensive WHOIS lookups for domains and IP addresses.
- Provides clear error messages for failed lookups - Provides clear error messages for failed lookups
- Handles rate limiting and WHOIS server unavailability - Handles rate limiting and WHOIS server unavailability
## ExploitDB Plugin ### 🔍 Subdomain Enumeration
**🔍 !subdomains [domain]**
Enumerate subdomains using SSL certificate transparency logs with the CertSpotter API.
**Features:**
- Discovers subdomains through SSL certificate transparency logs
- Uses the free CertSpotter API for enumeration
- No rate limiting or API key required
- Identifies subdomains through certificate SAN (Subject Alternative Name) enumeration
- No configuration required
**Examples:**
```bash
!subdomains example.com
!subdomains google.com
!subdomains github.com
```
**Output includes:**
- List of discovered subdomains from certificate transparency logs
- Formatted list with up to 20 subdomains shown
- Total count of discovered subdomains
### 🌐 IP Geolocation
**📍 !geo [ip/domain]**
Perform IP geolocation lookups with detailed geographic information.
**Features:**
- Uses ip-api.com as primary geolocation service with ipapi.co fallback
- Automatic domain to IP resolution
- Comprehensive geographic information
- No API key required for basic usage
**Examples:**
```bash
!geo 8.8.8.8
!geo example.com
!geo google.com
```
**Information provided:**
- Country and country code
- Region/State
- City
- Postal code
- Latitude/Longitude coordinates
- Timezone
- ISP/Organization
- Autonomous System Number (ASN)
### ExploitDB Plugin
A security plugin that searches Exploit-DB for vulnerabilities and exploits directly from Matrix. A security plugin that searches Exploit-DB for vulnerabilities and exploits directly from Matrix.
+269
View File
@@ -0,0 +1,269 @@
"""
This plugin provides IP geolocation functionality using free APIs.
It uses ip-api.com as the primary API with a fallback to ipapi.co.
"""
import logging
import aiohttp
import simplematrixbotlib as botlib
import socket
import re
async def is_valid_ip(ip):
"""
Check if the provided string is a valid IP address.
Args:
ip (str): The IP address to validate.
Returns:
bool: True if valid IP, False otherwise.
"""
try:
# Check for IPv4
socket.inet_pton(socket.AF_INET, ip)
return True
except socket.error:
try:
# Check for IPv6
socket.inet_pton(socket.AF_INET6, ip)
return True
except socket.error:
return False
def is_domain(domain):
"""
Check if the provided string is a domain name.
Args:
domain (str): The string to check.
Returns:
bool: True if it's a domain, False otherwise.
"""
domain_pattern = re.compile(
r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
)
return bool(domain_pattern.match(domain))
async def resolve_domain(domain):
"""
Resolve a domain name to an IP address.
Args:
domain (str): The domain to resolve.
Returns:
str: The resolved IP address or None.
"""
try:
return socket.gethostbyname(domain)
except socket.gaierror:
return None
async def query_ip_api_com(ip):
"""
Query ip-api.com for geolocation information.
Args:
ip (str): The IP address to geolocate.
Returns:
dict: Geolocation data or None if error.
"""
url = f"http://ip-api.com/json/{ip}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
return data
else:
logging.error(f"ip-api.com returned status {response.status}")
return None
except Exception as e:
logging.error(f"Error querying ip-api.com: {e}")
return None
async def query_ipapi_co(ip):
"""
Query ipapi.co for geolocation information (fallback).
Args:
ip (str): The IP address to geolocate.
Returns:
dict: Geolocation data or None if error.
"""
url = f"https://ipapi.co/{ip}/json/"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
return data
else:
logging.error(f"ipapi.co returned status {response.status}")
return None
except Exception as e:
logging.error(f"Error querying ipapi.co: {e}")
return None
async def query_geolocation(ip):
"""
Query geolocation information using primary and fallback APIs.
Args:
ip (str): The IP address to geolocate.
Returns:
dict: Geolocation data or None if error.
"""
# Try primary API first
data = await query_ip_api_com(ip)
# If primary API fails, try fallback API
if not data or data.get('status') == 'fail':
logging.info("Primary API failed, trying fallback API")
data = await query_ipapi_co(ip)
return data
async def format_geolocation_results(ip, data):
"""
Format geolocation results into a readable message.
Args:
ip (str): The queried IP address
data (dict): Geolocation data
Returns:
str: Formatted message
"""
if not data:
return f"🔍 No geolocation data found for {ip}."
# Check if data is from ip-api.com or ipapi.co and format accordingly
if 'status' in data and data.get('status') == 'fail':
return f"🔍 No geolocation data found for {ip}."
# Extract relevant information based on API used
if 'country' in data: # ip-api.com format
country = data.get('country', 'N/A')
country_code = data.get('countryCode', 'N/A')
region = data.get('regionName', data.get('region', 'N/A'))
city = data.get('city', 'N/A')
postal = data.get('zip', 'N/A')
latitude = data.get('lat', 'N/A')
longitude = data.get('lon', 'N/A')
timezone = data.get('timezone', 'N/A')
isp = data.get('isp', 'N/A')
org = data.get('org', 'N/A')
asn = data.get('as', 'N/A')
else: # ipapi.co format
country = data.get('country_name', data.get('country', 'N/A'))
country_code = data.get('country_code', data.get('countryCode', 'N/A'))
region = data.get('region', 'N/A')
city = data.get('city', 'N/A')
postal = data.get('postal', 'N/A')
latitude = data.get('latitude', 'N/A')
longitude = data.get('longitude', 'N/A')
timezone = data.get('timezone', 'N/A')
isp = data.get('org', 'N/A')
org = data.get('org', 'N/A')
asn = data.get('asn', 'N/A')
# Create collapsible content
content = f"<strong>🔍 IP Geolocation Results for {ip}</strong><br><br>"
content += f"<strong>Country:</strong> {country} ({country_code})<br>"
content += f"<strong>Region:</strong> {region}<br>"
content += f"<strong>City:</strong> {city}<br>"
content += f"<strong>Postal Code:</strong> {postal}<br>"
content += f"<strong>Coordinates:</strong> {latitude}, {longitude}<br>"
content += f"<strong>Timezone:</strong> {timezone}<br>"
content += f"<strong>ISP/Organization:</strong> {isp}<br>"
content += f"<strong>ASN:</strong> {asn}<br>"
# Wrap in details tag for Matrix compatibility
message = f"<details><summary><strong>🔍 Geolocation: {ip}</strong></summary>{content}</details>"
return message
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !geo command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("geo"):
args = match.args()
if len(args) < 1:
await bot.api.send_text_message(
room.room_id,
"Usage: !geo <ip_address/domain>\nExample: !geo 8.8.8.8\nExample: !geo example.com"
)
return
query = args[0].strip()
logging.info(f"Received !geo command for: {query}")
try:
# Determine if input is IP or domain
ip = query
if is_domain(query):
# Resolve domain to IP first
await bot.api.send_text_message(
room.room_id,
f"🔍 Resolving domain {query} to IP address..."
)
ip = await resolve_domain(query)
if not ip:
await bot.api.send_text_message(
room.room_id,
f"Failed to resolve domain {query} to IP address."
)
return
await bot.api.send_text_message(
room.room_id,
f"Domain {query} resolved to IP {ip}"
)
elif not await is_valid_ip(query):
await bot.api.send_text_message(
room.room_id,
f"Invalid IP address or domain format: {query}"
)
return
# Notify user that we're starting the lookup
await bot.api.send_text_message(
room.room_id,
f"🔍 Looking up geolocation for {ip}..."
)
# Query geolocation data with fallback
geo_data = await query_geolocation(ip)
# Format and send results
result_message = await format_geolocation_results(ip, geo_data)
await bot.api.send_markdown_message(room.room_id, result_message)
logging.info(f"Successfully sent geolocation results for {ip}")
except Exception as e:
await bot.api.send_text_message(
room.room_id,
f"An error occurred during geolocation lookup for {query}. Please try again later."
)
logging.error(f"Error in geo plugin for {query}: {e}", exc_info=True)
+58
View File
@@ -94,6 +94,64 @@ async def handle_command(room, message, bot, prefix, config):
<p><strong>Output includes:</strong> Domain/IP information, registrar, WHOIS server, creation/expiration dates, name servers, and contact details.</p> <p><strong>Output includes:</strong> Domain/IP information, registrar, WHOIS server, creation/expiration dates, name servers, and contact details.</p>
</details> </details>
<details><summary>🔍 <strong>!subdomains <domain></strong></summary>
<p>Enumerate subdomains using SSL certificate transparency logs. Discovers associated subdomains by querying the CertSpotter API for SSL certificates issued for a domain.</p>
<p><strong>Usage:</strong></p>
<ul>
<li><code>!subdomains <domain></code> - Enumerate subdomains for any domain</li>
<li><code>!subdomains example.com</code> - Example subdomain enumeration</li>
</ul>
<p><strong>Features:</strong></p>
<ul>
<li>Discovers subdomains through SSL certificate transparency logs</li>
<li>Uses the free CertSpotter API for enumeration</li>
<li>No rate limiting or API key required</li>
<li>Identifies subdomains through certificate SAN (Subject Alternative Name) enumeration</li>
</ul>
<p><strong>Examples:</strong></p>
<ul>
<li><code>!subdomains google.com</code></li>
<li><code>!subdomains github.com</code></li>
<li><code>!subdomains example.com</code></li>
</ul>
<p><em>Essential for reconnaissance and subdomain enumeration in penetration testing</em></p>
</details>
<details><summary>📍 <strong>!geo [ip/domain]</strong></summary>
<p>Perform IP geolocation lookups with detailed geographic information. Resolves domains to IP addresses and provides location data including country, region, city, coordinates, and ISP information.</p>
<p><strong>Usage:</strong></p>
<ul>
<li><code>!geo <ip_address></code> - Geolocate an IP address</li>
<li><code>!geo <domain></code> - Geolocate a domain (automatically resolves to IP)</li>
</ul>
<p><strong>Features:</strong></p>
<ul>
<li>Uses ip-api.com as primary geolocation service with ipapi.co fallback</li>
<li>Automatic domain to IP resolution</li>
<li>Comprehensive geographic information</li>
<li>No API key required for basic usage</li>
<li>Supports both IPv4 and IPv6 addresses</li>
</ul>
<p><strong>Examples:</strong></p>
<ul>
<li><code>!geo 8.8.8.8</code></li>
<li><code>!geo example.com</code></li>
<li><code>!geo google.com</code></li>
</ul>
<p><strong>Information provided:</strong></p>
<ul>
<li>Country and country code</li>
<li>Region/State</li>
<li>City</li>
<li>Postal code</li>
<li>Latitude/Longitude coordinates</li>
<li>Timezone</li>
<li>ISP/Organization</li>
<li>Autonomous System Number (ASN)</li>
</ul>
<p><em>Essential for network reconnaissance and IP investigation</em></p>
</details>
<details><summary>🔍 <strong>!shodan [command] [query]</strong></summary> <details><summary>🔍 <strong>!shodan [command] [query]</strong></summary>
<p>Shodan.io integration for security reconnaissance and threat intelligence.</p> <p>Shodan.io integration for security reconnaissance and threat intelligence.</p>
<p><strong>Commands:</strong></p> <p><strong>Commands:</strong></p>
+127
View File
@@ -0,0 +1,127 @@
"""
This plugin provides subdomain enumeration functionality using the CertSpotter API.
It queries SSL certificates issued for domains to discover associated subdomains.
"""
import logging
import aiohttp
import simplematrixbotlib as botlib
import json
import asyncio
async def query_certspotter(domain):
"""
Query CertSpotter API for subdomain enumeration.
Args:
domain (str): The domain to enumerate subdomains for
Returns:
list: List of discovered subdomains
"""
url = f"https://api.certspotter.com/v1/issuances?domain={domain}&include_subdomains=true&expand=dns_names"
subdomains = set()
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
for cert in data:
if 'dns_names' in cert:
for dns_name in cert['dns_names']:
if domain in dns_name:
subdomains.add(dns_name)
else:
logging.error(f"CertSpotter API returned status {response.status}")
return []
except Exception as e:
logging.error(f"Error querying CertSpotter API: {e}")
return []
return list(subdomains)
async def format_subdomain_results(domain, subdomains):
"""
Format subdomain results into a readable message.
Args:
domain (str): The queried domain
subdomains (list): List of discovered subdomains
Returns:
str: Formatted message
"""
if not subdomains:
return f"🔍 No subdomains found for {domain} using CertSpotter API."
# Sort and remove duplicates
subdomains = sorted(list(set(subdomains)))
count = len(subdomains)
# Create the subdomain list with proper HTML list formatting
subdomain_list = "<ul>"
for sub in subdomains:
subdomain_list += f"<li>{sub}</li>"
subdomain_list += "</ul>"
# Create collapsible content
content = f"<strong>🔍 Subdomain Enumeration Results for {domain}</strong><br><br>"
content += f"<strong>Total subdomains found:</strong> {count}<br><br>"
content += f"<strong>Discovered subdomains:</strong><br>{subdomain_list}"
# Wrap in details tag for Matrix compatibility
message = f"<details><summary><strong>🔍 Subdomain Enumeration: {domain} ({count} found)</strong></summary>{content}</details>"
return message
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !subdomains command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("subdomains"):
args = match.args()
if len(args) < 1:
await bot.api.send_text_message(
room.room_id,
"Usage: !subdomains <domain>\nExample: !subdomains example.com"
)
return
domain = args[0].strip()
logging.info(f"Received !subdomains command for: {domain}")
try:
# Notify user that we're starting the lookup
await bot.api.send_text_message(
room.room_id,
f"🔍 Enumerating subdomains for {domain} using CertSpotter API..."
)
# Query CertSpotter API
subdomains = await query_certspotter(domain)
# Format and send results
result_message = await format_subdomain_results(domain, subdomains)
await bot.api.send_markdown_message(room.room_id, result_message)
logging.info(f"Successfully sent subdomain enumeration results for {domain}")
except Exception as e:
await bot.api.send_text_message(
room.room_id,
f"An error occurred during subdomain enumeration for {domain}. Please try again later."
)
logging.error(f"Error in subdomains plugin for {domain}: {e}", exc_info=True)