Files
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

187 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""BLE security assessment using bleak for device scanning and GATT enumeration."""
import argparse
import asyncio
import json
import sys
import time
from bleak import BleakClient, BleakScanner
SENSITIVE_SERVICE_UUIDS = {
"0000180d-0000-1000-8000-00805f9b34fb": "Heart Rate",
"00001810-0000-1000-8000-00805f9b34fb": "Blood Pressure",
"00001808-0000-1000-8000-00805f9b34fb": "Glucose",
"00001809-0000-1000-8000-00805f9b34fb": "Health Thermometer",
"0000180f-0000-1000-8000-00805f9b34fb": "Battery Service",
"0000180a-0000-1000-8000-00805f9b34fb": "Device Information",
"00001812-0000-1000-8000-00805f9b34fb": "Human Interface Device",
"00001802-0000-1000-8000-00805f9b34fb": "Immediate Alert",
}
SENSITIVE_CHAR_UUIDS = {
"00002a37-0000-1000-8000-00805f9b34fb": "Heart Rate Measurement",
"00002a35-0000-1000-8000-00805f9b34fb": "Blood Pressure Measurement",
"00002a18-0000-1000-8000-00805f9b34fb": "Glucose Measurement",
"00002a1c-0000-1000-8000-00805f9b34fb": "Temperature Measurement",
"00002a19-0000-1000-8000-00805f9b34fb": "Battery Level",
"00002a29-0000-1000-8000-00805f9b34fb": "Manufacturer Name",
"00002a26-0000-1000-8000-00805f9b34fb": "Firmware Revision",
"00002a28-0000-1000-8000-00805f9b34fb": "Software Revision",
"00002a25-0000-1000-8000-00805f9b34fb": "Serial Number",
}
VULNERABLE_DEVICE_PATTERNS = [
"ITAG", "SmartLock", "BLE_Door", "FitBand", "iTag",
"CC2541", "HM-10", "JDY-08", "AT-09", "MLT-BT05",
]
async def scan_devices(scan_time: float) -> list:
devices = await BleakScanner.discover(timeout=scan_time, return_adv=True)
results = []
for addr, (device, adv_data) in devices.items():
name = adv_data.local_name or device.name or "Unknown"
vuln_match = None
for pattern in VULNERABLE_DEVICE_PATTERNS:
if pattern.lower() in name.lower():
vuln_match = pattern
break
results.append({
"address": str(addr),
"name": name,
"rssi": adv_data.rssi,
"service_uuids": [str(u) for u in (adv_data.service_uuids or [])],
"manufacturer_data": {str(k): v.hex() for k, v in (adv_data.manufacturer_data or {}).items()},
"known_vulnerable_pattern": vuln_match,
})
results.sort(key=lambda d: d["rssi"], reverse=True)
return results
async def enumerate_gatt(device_address: str) -> dict:
findings = []
services_info = []
total_chars = 0
async with BleakClient(device_address, timeout=15.0) as client:
if not client.is_connected:
return {"error": f"Failed to connect to {device_address}"}
for service in client.services:
svc_uuid = str(service.uuid)
svc_name = SENSITIVE_SERVICE_UUIDS.get(svc_uuid, service.description or "Unknown")
is_sensitive_svc = svc_uuid in SENSITIVE_SERVICE_UUIDS
chars_info = []
for char in service.characteristics:
total_chars += 1
char_uuid = str(char.uuid)
props = char.properties
char_name = SENSITIVE_CHAR_UUIDS.get(char_uuid, char.description or "Unknown")
is_sensitive_char = char_uuid in SENSITIVE_CHAR_UUIDS
char_entry = {
"uuid": char_uuid,
"name": char_name,
"properties": list(props),
"handle": char.handle,
}
if is_sensitive_char and ("read" in props):
findings.append({
"severity": "high",
"finding": f"{char_name} readable without encryption",
"uuid": char_uuid,
"service": svc_name,
"properties": list(props),
"remediation": "Enable encryption requirement on characteristic",
})
if "write-without-response" in props and is_sensitive_svc:
findings.append({
"severity": "critical",
"finding": f"{char_name} writable without response in sensitive service",
"uuid": char_uuid,
"service": svc_name,
"properties": list(props),
"remediation": "Remove write-without-response or require authenticated pairing",
})
if "write" in props and not is_sensitive_svc:
findings.append({
"severity": "medium",
"finding": f"{char_name} writable without known authentication",
"uuid": char_uuid,
"service": svc_name,
"properties": list(props),
"remediation": "Verify write access requires bonded connection",
})
chars_info.append(char_entry)
services_info.append({
"uuid": svc_uuid,
"name": svc_name,
"sensitive": is_sensitive_svc,
"characteristics": chars_info,
})
severity_weights = {"critical": 10, "high": 7, "medium": 4, "low": 1}
risk_total = sum(severity_weights.get(f["severity"], 0) for f in findings)
risk_score = min(10.0, round(risk_total / max(len(findings), 1), 1))
return {
"services_found": len(services_info),
"characteristics_found": total_chars,
"services": services_info,
"findings": findings,
"risk_score": risk_score,
}
async def run_audit(device_address: str, scan_time: float) -> dict:
scan_results = await scan_devices(scan_time)
target = None
for dev in scan_results:
if dev["address"].upper() == device_address.upper():
target = dev
break
if not target:
return {"error": f"Device {device_address} not found in scan", "scanned_devices": len(scan_results)}
gatt_result = await enumerate_gatt(device_address)
return {
"assessment_type": "ble_security_audit",
"target_device": target,
**gatt_result,
}
def main():
parser = argparse.ArgumentParser(description="BLE Security Assessment Tool")
parser.add_argument("--action", choices=["scan", "enumerate", "audit"],
required=True, help="Action to perform")
parser.add_argument("--scan-time", type=float, default=10.0,
help="BLE scan duration in seconds")
parser.add_argument("--device-address", type=str, default=None,
help="Target BLE device address (MAC or UUID)")
parser.add_argument("--output", type=str, default=None,
help="Output JSON file path")
args = parser.parse_args()
if args.action in ("enumerate", "audit") and not args.device_address:
print(json.dumps({"error": "Device address required for enumerate/audit"}))
sys.exit(1)
start = time.time()
if args.action == "scan":
result = asyncio.run(scan_devices(args.scan_time))
output = {"action": "scan", "devices_found": len(result), "devices": result}
elif args.action == "enumerate":
result = asyncio.run(enumerate_gatt(args.device_address))
output = {"action": "enumerate", "target": args.device_address, **result}
elif args.action == "audit":
output = asyncio.run(run_audit(args.device_address, args.scan_time))
output["elapsed_seconds"] = round(time.time() - start, 2)
report = json.dumps(output, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(report)
print(report)
if __name__ == "__main__":
main()