DNSDumpster plugin added
This commit is contained in:
@@ -22,7 +22,7 @@ ALLOWED_PLUGINS = {
|
||||
'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
|
||||
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
|
||||
'xkcd', 'youtube-preview', 'youtube-search', 'weather', 'urbandictionary',
|
||||
'bitcoin', 'dns', 'shodan'
|
||||
'bitcoin', 'dns', 'shodan', 'dnsdumpster'
|
||||
}
|
||||
|
||||
class FunguyBot:
|
||||
|
270
plugins/dnsdumpster.py
Normal file
270
plugins/dnsdumpster.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
This plugin provides DNSDumpster.com integration for domain reconnaissance and DNS mapping.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import simplematrixbotlib as botlib
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(plugin_dir)
|
||||
dotenv_path = os.path.join(parent_dir, '.env')
|
||||
load_dotenv(dotenv_path)
|
||||
|
||||
DNSDUMPSTER_API_KEY = os.getenv("DNSDUMPSTER_KEY", "")
|
||||
DNSDUMPSTER_API_BASE = "https://api.dnsdumpster.com"
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Function to handle DNSDumpster commands.
|
||||
|
||||
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("dnsdumpster"):
|
||||
logging.info("Received !dnsdumpster command")
|
||||
|
||||
# Check if API key is configured
|
||||
if not DNSDUMPSTER_API_KEY:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"DNSDumpster API key not configured. Please set DNSDUMPSTER_KEY environment variable."
|
||||
)
|
||||
logging.error("DNSDumpster API key not configured")
|
||||
return
|
||||
|
||||
args = match.args()
|
||||
|
||||
if len(args) < 1:
|
||||
await show_usage(room, bot)
|
||||
return
|
||||
|
||||
# Check if it's a test command or domain lookup
|
||||
if args[0].lower() == "test":
|
||||
await test_dnsdumpster_connection(room, bot)
|
||||
else:
|
||||
# Treat the first argument as the domain
|
||||
domain = args[0].lower().strip()
|
||||
await dnsdumpster_domain_lookup(room, bot, domain)
|
||||
|
||||
async def show_usage(room, bot):
|
||||
"""Display DNSDumpster command usage."""
|
||||
usage = """
|
||||
<strong>🔍 DNSDumpster Commands:</strong>
|
||||
|
||||
<strong>!dnsdumpster <domain_name></strong> - Get comprehensive DNS reconnaissance for a domain
|
||||
<strong>!dnsdumpster test</strong> - Test API connection
|
||||
|
||||
<strong>Examples:</strong>
|
||||
• <code>!dnsdumpster google.com</code>
|
||||
• <code>!dnsdumpster github.com</code>
|
||||
• <code>!dnsdumpster example.com</code>
|
||||
|
||||
<strong>Rate Limit:</strong> 1 request per 2 seconds
|
||||
"""
|
||||
await bot.api.send_markdown_message(room.room_id, usage)
|
||||
|
||||
async def test_dnsdumpster_connection(room, bot):
|
||||
"""Test DNSDumpster API connection."""
|
||||
try:
|
||||
test_domain = "google.com" # Changed from example.com to google.com
|
||||
url = f"{DNSDUMPSTER_API_BASE}/domain/{test_domain}"
|
||||
headers = {
|
||||
"X-API-Key": DNSDUMPSTER_API_KEY
|
||||
}
|
||||
|
||||
logging.info(f"Testing DNSDumpster API with domain: {test_domain}")
|
||||
response = requests.get(url, headers=headers, timeout=15)
|
||||
|
||||
debug_info = f"<strong>🔧 DNSDumpster API Test</strong><br>"
|
||||
debug_info += f"<strong>Status Code:</strong> {response.status_code}<br>"
|
||||
debug_info += f"<strong>Test Domain:</strong> {test_domain}<br>"
|
||||
debug_info += f"<strong>Headers Used:</strong> X-API-Key<br>"
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
debug_info += "<strong>✅ SUCCESS - API is working!</strong><br>"
|
||||
debug_info += f"<strong>Response Keys:</strong> {list(data.keys())}<br>"
|
||||
|
||||
# Show some sample data
|
||||
if data.get('a'):
|
||||
debug_info += f"<strong>A Records Found:</strong> {len(data['a'])}<br>"
|
||||
if data.get('ns'):
|
||||
debug_info += f"<strong>NS Records Found:</strong> {len(data['ns'])}<br>"
|
||||
if data.get('total_a_recs'):
|
||||
debug_info += f"<strong>Total A Records:</strong> {data['total_a_recs']}<br>"
|
||||
|
||||
elif response.status_code == 400:
|
||||
debug_info += "<strong>❌ Bad Request - Check domain format</strong><br>"
|
||||
debug_info += f"<strong>Response:</strong> {response.text[:200]}<br>"
|
||||
elif response.status_code == 401:
|
||||
debug_info += "<strong>❌ Unauthorized - Invalid API key</strong><br>"
|
||||
elif response.status_code == 429:
|
||||
debug_info += "<strong>⚠️ Rate Limit Exceeded - Wait 2 seconds</strong><br>"
|
||||
else:
|
||||
debug_info += f"<strong>❌ Error:</strong> {response.status_code} - {response.text[:200]}<br>"
|
||||
|
||||
await bot.api.send_markdown_message(room.room_id, debug_info)
|
||||
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Test failed: {str(e)}")
|
||||
|
||||
async def dnsdumpster_domain_lookup(room, bot, domain):
|
||||
"""Get comprehensive DNS reconnaissance for a domain."""
|
||||
try:
|
||||
url = f"{DNSDUMPSTER_API_BASE}/domain/{domain}"
|
||||
headers = {
|
||||
"X-API-Key": DNSDUMPSTER_API_KEY
|
||||
}
|
||||
|
||||
logging.info(f"Fetching DNSDumpster data for domain: {domain}")
|
||||
|
||||
# Send initial processing message
|
||||
await bot.api.send_text_message(room.room_id, f"🔍 Processing DNS reconnaissance for {domain}...")
|
||||
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
|
||||
if response.status_code == 400:
|
||||
await bot.api.send_text_message(room.room_id, f"Bad request - check domain format: {domain}")
|
||||
return
|
||||
elif response.status_code == 401:
|
||||
await bot.api.send_text_message(room.room_id, "Invalid DNSDumpster API key")
|
||||
return
|
||||
elif response.status_code == 403:
|
||||
await bot.api.send_text_message(room.room_id, "Access denied - check API key permissions")
|
||||
return
|
||||
elif response.status_code == 429:
|
||||
await bot.api.send_text_message(room.room_id, "Rate limit exceeded - wait 2 seconds between requests")
|
||||
return
|
||||
elif response.status_code != 200:
|
||||
await bot.api.send_text_message(room.room_id, f"DNSDumpster API error: {response.status_code} - {response.text[:100]}")
|
||||
return
|
||||
|
||||
data = response.json()
|
||||
logging.info(f"DNSDumpster response keys: {list(data.keys())}")
|
||||
|
||||
# Format the comprehensive DNS report
|
||||
output = await format_dnsdumpster_report(domain, data)
|
||||
|
||||
await bot.api.send_markdown_message(room.room_id, output)
|
||||
logging.info(f"Sent DNSDumpster data for {domain}")
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
await bot.api.send_text_message(room.room_id, "DNSDumpster API request timed out")
|
||||
logging.error("DNSDumpster API timeout")
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Error fetching DNSDumpster data: {str(e)}")
|
||||
logging.error(f"Error in dnsdumpster_domain_lookup: {e}")
|
||||
|
||||
async def format_dnsdumpster_report(domain, data):
|
||||
"""Format DNSDumpster JSON response into a readable report."""
|
||||
output = f"<strong>🔍 DNSDumpster Report: {domain}</strong><br><br>"
|
||||
|
||||
# Summary statistics
|
||||
if data.get('total_a_recs'):
|
||||
output += f"<strong>📊 Summary</strong><br>"
|
||||
output += f" • <strong>Total A Records:</strong> {data['total_a_recs']}<br>"
|
||||
|
||||
# A Records - Show ALL records
|
||||
if data.get('a') and data['a']:
|
||||
output += f"<br><strong>📍 A Records (IPv4) - {len(data['a'])} found</strong><br>"
|
||||
for record in data['a']: # Show ALL A records
|
||||
host = record.get('host', 'N/A')
|
||||
ips = record.get('ips', [])
|
||||
|
||||
output += f" • <strong>{host}</strong><br>"
|
||||
for ip_info in ips: # Show ALL IPs per host
|
||||
ip = ip_info.get('ip', 'N/A')
|
||||
country = ip_info.get('country', 'Unknown')
|
||||
asn_name = ip_info.get('asn_name', 'Unknown')
|
||||
|
||||
output += f" └─ {ip} ({country})<br>"
|
||||
output += f" └─ {asn_name}<br>"
|
||||
|
||||
# Show banner information if available
|
||||
banners = ip_info.get('banners', {})
|
||||
if banners.get('http') or banners.get('https'):
|
||||
output += f" └─ <em>Web Services:</em> "
|
||||
services = []
|
||||
if banners.get('http'):
|
||||
services.append("HTTP")
|
||||
if banners.get('https'):
|
||||
services.append("HTTPS")
|
||||
output += f"{', '.join(services)}<br>"
|
||||
|
||||
# NS Records - Show ALL records
|
||||
if data.get('ns') and data['ns']:
|
||||
output += f"<br><strong>🔗 NS Records (Name Servers) - {len(data['ns'])} found</strong><br>"
|
||||
for record in data['ns']: # Show ALL NS records
|
||||
host = record.get('host', 'N/A')
|
||||
ips = record.get('ips', [])
|
||||
|
||||
output += f" • <strong>{host}</strong><br>"
|
||||
for ip_info in ips: # Show ALL IPs
|
||||
ip = ip_info.get('ip', 'N/A')
|
||||
country = ip_info.get('country', 'Unknown')
|
||||
output += f" └─ {ip} ({country})<br>"
|
||||
|
||||
# MX Records - Show ALL records
|
||||
if data.get('mx') and data['mx']:
|
||||
output += f"<br><strong>📧 MX Records (Mail Servers) - {len(data['mx'])} found</strong><br>"
|
||||
for record in data['mx']: # Show ALL MX records
|
||||
host = record.get('host', 'N/A')
|
||||
ips = record.get('ips', [])
|
||||
|
||||
output += f" • <strong>{host}</strong><br>"
|
||||
for ip_info in ips: # Show ALL IPs
|
||||
ip = ip_info.get('ip', 'N/A')
|
||||
country = ip_info.get('country', 'Unknown')
|
||||
output += f" └─ {ip} ({country})<br>"
|
||||
|
||||
# CNAME Records - Show ALL records
|
||||
if data.get('cname') and data['cname']:
|
||||
output += f"<br><strong>🔀 CNAME Records - {len(data['cname'])} found</strong><br>"
|
||||
for record in data['cname']: # Show ALL CNAME records
|
||||
host = record.get('host', 'N/A')
|
||||
target = record.get('target', 'N/A')
|
||||
output += f" • {host} → {target}<br>"
|
||||
|
||||
# TXT Records - Show ALL records
|
||||
if data.get('txt') and data['txt']:
|
||||
output += f"<br><strong>📄 TXT Records - {len(data['txt'])} found</strong><br>"
|
||||
for txt in data['txt']: # Show ALL TXT records
|
||||
# Truncate very long TXT records but show more content
|
||||
if len(txt) > 200:
|
||||
txt = txt[:200] + "..."
|
||||
output += f" • {txt}<br>"
|
||||
|
||||
# Additional record types that might be present - Show ALL records
|
||||
other_records = ['aaaa', 'srv', 'soa', 'ptr']
|
||||
for record_type in other_records:
|
||||
if data.get(record_type) and data[record_type]:
|
||||
output += f"<br><strong>🔧 {record_type.upper()} Records - {len(data[record_type])} found</strong><br>"
|
||||
for record in data[record_type]: # Show ALL records
|
||||
if isinstance(record, dict):
|
||||
# Format dictionary records nicely
|
||||
record_str = ", ".join([f"{k}: {v}" for k, v in record.items()])
|
||||
if len(record_str) > 150:
|
||||
record_str = record_str[:150] + "..."
|
||||
output += f" • {record_str}<br>"
|
||||
else:
|
||||
output += f" • {record}<br>"
|
||||
|
||||
# Add rate limit reminder
|
||||
output += "<br><em>💡 Rate Limit: 1 request per 2 seconds</em>"
|
||||
|
||||
# Always wrap in collapsible details since we're showing all results
|
||||
output = f"<details><summary><strong>🔍 DNSDumpster Report: {domain} (Click to expand)</strong></summary>{output}</details>"
|
||||
|
||||
return output
|
@@ -99,6 +99,33 @@ async def handle_command(room, message, bot, prefix, config):
|
||||
<p><em>Requires SHODAN_KEY environment variable</em></p>
|
||||
</details>
|
||||
|
||||
<details><summary>🌐 <strong>!dnsdumpster [domain]</strong></summary>
|
||||
<p>Comprehensive DNS reconnaissance and attack surface mapping using DNSDumpster.com API.</p>
|
||||
<p><strong>Commands:</strong></p>
|
||||
<ul>
|
||||
<li><code>!dnsdumpster <domain></code> - Complete DNS reconnaissance for any domain</li>
|
||||
<li><code>!dnsdumpster test</code> - Test API connection and key validity</li>
|
||||
</ul>
|
||||
<p><strong>Features:</strong></p>
|
||||
<ul>
|
||||
<li>A Records - All IPv4 addresses with geographic and ASN information</li>
|
||||
<li>NS Records - Complete name server information with IP locations</li>
|
||||
<li>MX Records - All mail servers with geographic data</li>
|
||||
<li>CNAME Records - Full alias chain mappings</li>
|
||||
<li>TXT Records - All text records including SPF, DKIM, verification</li>
|
||||
<li>Additional Records - AAAA, SRV, SOA, PTR records when available</li>
|
||||
<li>Web Services - HTTP/HTTPS service detection with banner information</li>
|
||||
</ul>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>!dnsdumpster google.com</code></li>
|
||||
<li><code>!dnsdumpster github.com</code></li>
|
||||
<li><code>!dnsdumpster example.com</code></li>
|
||||
</ul>
|
||||
<p><em>Requires DNSDUMPSTER_KEY environment variable</em><br>
|
||||
<em>Rate Limit: 1 request per 2 seconds</em></p>
|
||||
</details>
|
||||
|
||||
<details><summary>📸 <strong>!sd [prompt]</strong></summary>
|
||||
<p>Generates images using self-hosted Stable Diffusion. Supports options: --steps, --cfg, --h, --w, --neg, --sampler. Uses queuing system to handle multiple requests. See available options using just '!sd'.</p>
|
||||
</details>
|
||||
|
@@ -60,7 +60,8 @@ async def load_plugin(plugin_name):
|
||||
'urbandictionary': 'plugins.urbandictionary',
|
||||
'bitcoin':'plugins.bitcoin',
|
||||
'dns':'plugins.dns',
|
||||
'shodan':'plugins.shodan'
|
||||
'shodan':'plugins.shodan',
|
||||
'dnsdumpster': 'plugins.dnsdumpster'
|
||||
}
|
||||
|
||||
# Get the module path from the mapping
|
||||
|
Reference in New Issue
Block a user