If you are looking for these devices for research purposes, the standard Shodan search queries usually involve looking for the HTTP title or the server name:
If you want a custom update script, use the shodan command-line interface.
Installation:
pip install shodan
shodan init YOUR_API_KEY
Automated Update Script (webcamxp_updater.sh):
#!/bin/bash
# Define the search query
QUERY='title:"WebcamXP 5"'
# Run the search and save to a timestamped file
shodan search --limit 100 --fields ip_str,port,http.title "$QUERY" > webcamxp_results_$(date +%Y%m%d_%H%M%S).txt
# Optional: diff with previous file to see changes
if [ -f webcamxp_latest.txt ]; then
echo "Changes since last update:"
diff webcamxp_latest.txt webcamxp_results_$(date +%Y%m%d_%H%M%S).txt
fi
# Symlink to "latest"
ln -sf webcamxp_results_$(date +%Y%m%d_%H%M%S).txt webcamxp_latest.txt
Set a Cron Job (update every 4 hours):
crontab -e
# Add line:
0 */4 * * * /home/user/webcamxp_updater.sh
To narrow down results to live, accessible feeds, combine filters:
title:"WebcamXP 5" http.status:200 -"404" country:US
CONFIG_FILE = "webcamxp_config.json" REPORT_FILE = "webcamxp_instances.csv" LOG_FILE = "webcamxp_updates.log"
class WebcamXPShodanSearcher: def init(self, api_key): self.api = shodan.Shodan(api_key) self.instances = []
def search_webcamxp(self, max_pages=3):
"""Search Shodan for WebcamXP 5 instances"""
queries = [
'"WebcamXP 5" "Server:"',
'title:"WebcamXP 5"',
'"WebcamXP 5" port:8080,8081',
]
all_results = []
for query in queries:
print(f"[*] Searching with query: query")
try:
# Paginate through results
for page in range(1, max_pages + 1):
results = self.api.search(query, page=page)
print(f" Found len(results['matches']) results on page page")
for match in results['matches']:
instance = self.parse_instance(match)
if instance and instance not in all_results:
all_results.append(instance)
if page >= results['total'] // 100:
break
except shodan.APIError as e:
print(f" Error: e")
continue
self.instances = all_results
return self.instances
def parse_instance(self, match):
"""Parse Shodan match into structured instance data"""
try:
# Extract webcam feed URL
ip = match.get('ip_str', '')
port = match.get('port', 80)
protocol = 'https' if match.get('ssl') else 'http'
base_url = f"protocol://ip:port"
# Common WebcamXP paths
possible_paths = ['/', '/view/viewer_index.shtml', '/cgi-bin/viewer/video.jpg']
feed_url = None
for path in possible_paths:
test_url = base_url + path
if self.check_url_accessible(test_url):
feed_url = test_url
break
instance = {
'ip': ip,
'port': port,
'url': base_url,
'feed_url': feed_url,
'timestamp': datetime.now().isoformat(),
'organization': match.get('org', 'Unknown'),
'location': f"match.get('city', 'Unknown'), match.get('country_name', 'Unknown')",
'server_header': match.get('http', {}).get('server', ''),
'title': match.get('http', {}).get('title', ''),
}
return instance
except Exception as e:
print(f" Parse error: e")
return None
def check_url_accessible(self, url, timeout=5):
"""Check if webcam feed URL is accessible"""
try:
response = requests.get(url, timeout=timeout, verify=False)
return response.status_code == 200
except:
return False
def generate_report(self):
"""Generate CSV report of found instances"""
if not self.instances:
print("[!] No instances found to report")
return
with open(REPORT_FILE, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['ip', 'port', 'url', 'feed_url', 'timestamp',
'organization', 'location', 'server_header', 'title']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(self.instances)
print(f"[+] Report saved to REPORT_FILE (len(self.instances) instances)")
def update_report_with_current_status(self):
"""Update existing report with current online status"""
if not os.path.exists(REPORT_FILE):
print("[!] No existing report to update")
return
# Read existing report
with open(REPORT_FILE, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
instances = list(reader)
# Check each instance
for instance in instances:
status = self.check_url_accessible(instance['url'])
instance['status_checked'] = datetime.now().isoformat()
instance['online'] = 'Yes' if status else 'No'
# Write updated report
with open(REPORT_FILE, 'w', newline='', encoding='utf-8') as f:
fieldnames = list(instances[0].keys())
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(instances)
print(f"[+] Report updated at datetime.now().isoformat()")
def main(): parser = argparse.ArgumentParser(description='WebcamXP 5 Shodan Search & Update Tool') parser.add_argument('--api-key', help='Shodan API key (or set SHODAN_API_KEY env var)') parser.add_argument('--search', action='store_true', help='Perform new search') parser.add_argument('--update', action='store_true', help='Update existing report') parser.add_argument('--max-pages', type=int, default=3, help='Max search pages')
args = parser.parse_args()
# Get API key
api_key = args.api_key or os.environ.get('SHODAN_API_KEY')
if not api_key:
print("[!] Please provide Shodan API key via --api-key or SHODAN_API_KEY env var")
return
searcher = WebcamXPShodanSearcher(api_key)
if args.search:
print("[*] Searching for WebcamXP 5 instances...")
instances = searcher.search_webcamxp(max_pages=args.max_pages)
print(f"[+] Found len(instances) unique instances")
searcher.generate_report()
if args.update:
print("[*] Updating existing report...")
searcher.update_report_with_current_status()
if not args.search and not args.update:
print("[!] Please specify --search or --update")
if name == "main": main()
# Initial search
python webcamxp_search.py --search --max-pages 5