A DevOps engineer told me they found their company's unpatched Jenkins server on Shodan — exposed to the entire internet. They patched it within an hour.
Shodan is a search engine for internet-connected devices. It indexes servers, webcams, IoT devices, databases — anything with a public IP. And it has a free API.
Getting Started
Sign up at shodan.io for a free API key. Free tier:
- 100 query credits/month
- 100 scan credits/month
- No filters in search (paid feature)
1. Search for Exposed Services
import requests
API_KEY = 'your_shodan_api_key' # Free from shodan.io
def shodan_search(query, page=1):
resp = requests.get(
'https://api.shodan.io/shodan/host/search',
params={'key': API_KEY, 'query': query, 'page': page}
).json()
print(f"Total results: {resp.get('total', 0)}")
for match in resp.get('matches', [])[:5]:
print(f" {match['ip_str']:<16} port:{match['port']:<6} {match.get('org','')[:30]}")
return resp
# Find exposed MongoDB instances
shodan_search('product:mongodb')
# Find Apache servers in a specific country
shodan_search('apache country:US')
2. Check Your Own Infrastructure
def check_ip(ip):
"""Check what Shodan knows about an IP address."""
resp = requests.get(
f'https://api.shodan.io/shodan/host/{ip}',
params={'key': API_KEY}
)
if resp.status_code != 200:
return {'error': resp.json().get('error', 'Unknown')}
data = resp.json()
return {
'ip': ip,
'org': data.get('org', 'Unknown'),
'os': data.get('os', 'Unknown'),
'ports': data.get('ports', []),
'vulns': data.get('vulns', []),
'hostnames': data.get('hostnames', []),
'country': data.get('country_name', 'Unknown')
}
# Check what's exposed on your server
info = check_ip('8.8.8.8') # Google DNS as example
print(f"Open ports: {info['ports']}")
print(f"Known vulns: {info['vulns']}")
3. Monitor for New Exposures
def check_org_exposure(org_name):
"""Find all devices associated with an organization."""
resp = requests.get(
'https://api.shodan.io/shodan/host/search',
params={'key': API_KEY, 'query': f'org:"{org_name}"'}
).json()
total = resp.get('total', 0)
services = {}
for match in resp.get('matches', []):
product = match.get('product', 'unknown')
services[product] = services.get(product, 0) + 1
print(f"\n{org_name}: {total} exposed devices")
for service, count in sorted(services.items(), key=lambda x: -x[1])[:10]:
print(f" {service}: {count}")
return {'total': total, 'services': services}
4. Find Vulnerable Services
def find_vulnerable(vuln_id):
"""Search for services with a specific CVE."""
resp = requests.get(
'https://api.shodan.io/shodan/host/search',
params={'key': API_KEY, 'query': f'vuln:{vuln_id}'}
).json()
print(f"Devices with {vuln_id}: {resp.get('total', 0)}")
for m in resp.get('matches', [])[:5]:
print(f" {m['ip_str']:<16} {m.get('org','')[:25]:<26} port:{m['port']}")
# Check for Log4Shell
find_vulnerable('CVE-2021-44228')
5. Quick Stats (No Credits Used)
Some endpoints don't use query credits:
def my_ip():
"""Get your public IP (free, no credits)."""
return requests.get(
'https://api.shodan.io/tools/myip',
params={'key': API_KEY}
).text
def api_info():
"""Check your remaining credits."""
return requests.get(
'https://api.shodan.io/api-info',
params={'key': API_KEY}
).json()
print(f"My IP: {my_ip()}")
print(f"Credits left: {api_info()['query_credits']}")
Ethical Use
Shodan shows what's already publicly visible. But:
- Never access systems you don't own
- Use it to audit YOUR infrastructure
- Report vulnerabilities you find responsibly
- Check your company's exposure proactively
Rate Limits
| Tier | Query Credits | Scan Credits |
|---|---|---|
| Free | 100/month | 100/month |
| Membership ($49) | Unlimited | 5,120/month |
| Small Business | Unlimited | 65,536/month |
Free tier is enough for personal security audits.
Combine With
- VirusTotal API — check IPs for malware
- WHOIS/RDAP — domain registration data
- GitHub API — check repo security
Building a comprehensive free security toolkit. More: GitHub | Technical writing: Spinov001@gmail.com
Top comments (0)