{"uuid": "66872959-94ef-4367-b241-8d5197830768", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-66066", "type": "seen", "source": "https://gist.github.com/tubaxenor/6a090585034c654855e57e7bd7fd9595", "content": "# CVE-2026-66066 log analysis: scholarly-app\n\nPhase 5 of `kr2s-was-i-exploited`, run 2026-07-31 against Datadog.\n\n## Method\n\nThe first attempt at this pulled raw log lines and paginated. That was the wrong\napproach: the MCP search tool caps each response at roughly 136\u2013222 records, so\n6,000 lines would have taken about 44 calls and still produced samples rather\nthan answers.\n\nThese logs carry full structured attributes \u2014 `@http.status_code`,\n`@http.method`, `@http.url_details.path`, `@network.client.ip`, `@duration` \u2014\nso every figure below is a DDSQL aggregation over the complete population. No\nsampling, no pagination.\n\n**Range:** 2026-07-16T00:00:00Z (oldest retained log) to 2026-07-29T22:00:00Z\n(just past the fix deploy at 21:02:13Z). Retention is ~15 days with no online\narchives.\n\n## Endpoint totals\n\nGrouping all Active Storage traffic by endpoint, method and status returns three\nrows and no more, which is itself the answer to \"were any requests rejected\":\n\n| Endpoint | Method | Status | Requests | Distinct IPs |\n|---|---|---|---|---|\n| `/rails/active_storage/blobs/*` | GET | 302 | 19,418 | 312 |\n| `/rails/active_storage/representations/*` | GET | 302 | 4,874 | 345 |\n| `/rails/active_storage/direct_uploads` | POST | 200 | 1,014 | 82 |\n\nNot one 4xx or 5xx anywhere in Active Storage traffic over the window.\n\nThe `blobs` endpoint is recorded for completeness and is not an attack vector: it\nredirects to S3 and serves stored bytes directly, with no variant processing, so\nit never reaches libvips or `matload`.\n\n## The five Phase 5 queries\n\n**1. Uploads.** 1,014 across 82 IPs, all 200. Long-tailed distribution \u2014 busiest\naddress 190, then 140, 78, 39, 37, 36 \u2014 mixed residential and campus IPv4/IPv6\nbehind Cloudflare. No single scripted client working a sequence.\n\n**2. Representations.** 4,874 across 345 IPs, all 302 with 0 bytes. Daily volume\n10\u2013648 with dips on 07-18/19 and 07-25/26 (both weekends); peak service time\nstays within 82\u2013317 ms every day. No burst, no outlier, no slow tail of the kind\na large file read would produce.\n\nLimit worth stating: a successful exploitation also returns 302. This rules out a\nnoisy or failing attempt, not a quiet successful one.\n\n**3. Evidence-destruction paths \u2014 the highest-value query.** Zero requests\nreached any of the six routes that resolve a blob from a client-supplied signed\nid, by any mutating method. Three candidates surfaced and all fall away:\n\n- 38 `PATCH /api/v1/faculty_activities/:id` from three AWS addresses\n  (`3.145.136.244` \u00d735, `3.135.197.103` \u00d72, `3.137.160.76` \u00d71), all 200. No\n  `/files` segment, so this is the record update, not the files sub-resource.\n  `FacultyActivitySchema` declares no attachment field \u2014 `attachment` appears\n  only in `mapped_import_schema.rb` \u2014 so the action cannot attach or purge a blob.\n- 187 `PUT` and 26 `PATCH` on profile routes, none on the `avatar`,\n  `preferred_avatar` or `self_managed_cv_upload` sub-paths. `ProfileSchema` also\n  declares no attachment field.\n- 2 `PATCH` matching a `%file_upload%` filter resolved to\n  `/institution/faculty_evaluations/questions/multiple_file_uploads/`, an\n  unrelated feature. Pattern false positive.\n\nThe attach-then-purge blind spot is closed for the period the logs cover.\n\n**4. Rejected requests.** None. The sweep is structurally blind to rejected\nrequests because they create no blob, so this query is the only place a second\nactor could appear \u2014 and it is empty.\n\n**5. Three-stage reconciliation.** Does not descend, structurally rather than\nanomalously. Uploads (1,014) sit below representations (4,874) because most of\nthe latter render avatars attached long before this window. The third term,\nsurviving variant records for unattached blobs, is ~0 because the purge job\nremoved them. The series cannot be formed on this application and its absence\ncarries no signal.\n\n**Checksum join: not applicable.** The skill's attribution path filters log lines\nby the checksums of crafted objects the sweep found. The sweep found none, so\nattribution has no starting point \u2014 not merely bounded by retention, but with\nnothing to anchor to.\n\n## Limits\n\nThese are Heroku router logs. They carry method, path, host, request id, client\nIP, dyno, service time, status and bytes. They do **not** carry a user agent, an\nauthenticated account, or a request body, so attribution reaches the client\naddress and stops there.\n\nA clean 13-day window says nothing about the preceding 30 months.\n\n## Superseded raw capture\n\n`direct_uploads.tsv` and `representations.tsv` are partial raw-line pulls from\nthe first attempt (883 of 1,014 and 889 of 4,874 respectively). The aggregations\nabove cover the full population and supersede them. They are kept only as a\nsample of the raw line format, which is what a future S3 sweep would join\nagainst on timestamp if it ever surfaces a crafted object.\n", "creation_timestamp": "2026-07-31T04:37:45.345308Z"}