# Sources: Microsoft OAuth error-redirect phishing

Research date: **5th November 2026**. These notes record the observations used
in the article. Public links provide the original case and research sources.
This document is not an independent copy of every source or a complete campaign
inventory. Recipient addresses, passwords, and session values are omitted.

The same-day expansion adds the Cloudflare-token comparison, the 28th October
collector, and the original Microsoft Storm-1167 report. These findings use
ordinary public capture evidence. No restricted collector records were read.

## Main capture

- [Analysis 69f874ad-68ab-45ae-8a8f-7a3ea8d21629](https://sandfi.sh/analyses/69f874ad-68ab-45ae-8a8f-7a3ea8d21629).
- Capture interval: **5th November 2026, 17:21:19–17:22:02 UTC**.
- Browser: mobile Firefox, Pixel 7 profile. The direct egress country was not verified.
- Inspected web fact generation: **2**. Engine: **0.4.1**.
- Retained evidence: **28 requests and 13 page snapshots**.
- The browser passed verification, filled a synthetic password, submitted it,
  and stopped at a repeated password state after an error.
- No original delivery email was available. No new phishing-site visit or
  submission was started for this investigation.

## Claim ledger

All request times in this table are on 5th November 2026, UTC. Public timestamps
have second precision. A source handler establishes configured behavior; a
matching request establishes the captured browser action.

| Claim | Exact source | Evidence class | Limit or alternative |
|---|---|---|---|
| Microsoft supplies an S3 error-return destination without a request `redirect_uri` | [Microsoft response](https://sandfi.sh/api/requests/bbd52e02-e801-47e0-b72e-7e1234a8d354/body), request at 17:21:23; `urlAppError` at text offset 2175 | Archived response and original request | The application manifest, ownership, and complete callback list were not inspected. |
| Microsoft uses page-side navigation rather than an HTTP 302 | [Session-handling bundle](https://sandfi.sh/api/requests/38867c17-c398-4471-9061-f244bd20f819/body), request at 17:21:24; error selection near text offset 134932 and redirect handler at 132487–132660 | Source plus matching subsequent navigation | The MSA iframe's postMessage payload was not retained. |
| S3 decodes and forwards the recipient address | [S3 response](https://sandfi.sh/api/requests/786e5f84-06be-45f3-a5c7-24534e8f52df/body), 17:21:25 | Source and matching request | The address is supplied in the initial URL, not obtained from a Microsoft account. |
| DigitalOcean forwards the same value to the phishing host | [Spaces response](https://sandfi.sh/api/requests/ed71f906-806f-4dd5-aa3a-dab0388cd897/body), 17:21:25 | Source and matching request | Its alternate input formats were inspected statically, not exercised independently. |
| Salesforce and the code-like padding do not control the observed route | Original Microsoft request and both forwarding responses above | Archived source comparison | This does not measure email-filter behavior or all server-side parameter handling. |
| The custom gate screens the browser and has a Google exit | [Gate response](https://sandfi.sh/api/requests/248b41ee-a4fe-4872-951b-17d182e758c1/body), 17:21:26; [verification request](https://sandfi.sh/analyses/69f874ad-68ab-45ae-8a8f-7a3ea8d21629?tab=network&request=cec87606-d321-45e5-9c06-6a3e84769426), 17:21:32 | Source and observed successful branch | Google was not visited in this run. The gate's accuracy against other browsers was not measured. |
| A deployment-specific value selects the collector | [Loader](https://sandfi.sh/api/requests/9a11a9ca-a7b2-4e1f-944a-8c3713fd2ee2/body), 17:21:32; script range `[2016,2410)` | Static source and observed collector requests | The legacy `gentlesunrise[.]de` names were not contacted in this capture. |
| The collector supplies tenant-specific branding | [Configuration response](https://sandfi.sh/analyses/69f874ad-68ab-45ae-8a8f-7a3ea8d21629?tab=network&request=0a853bfd-748f-4c0e-89d8-35e60c01fddc), 17:21:40; banner request `61087dd0-326e-4087-8e12-b5dbb3c09a9b`, 17:21:41 | Observed response, image request, and screenshot | The backend's own identity-provider requests were not captured. |
| The browser submits the encoded synthetic password to a separate collector | [Login request and response](https://sandfi.sh/analyses/69f874ad-68ab-45ae-8a8f-7a3ea8d21629?tab=network&request=7a06df51-2d0e-40f3-80f6-9dd4a6cdd4f7), 17:21:46 | Archived POST and error response | Does not prove storage, operator access, or real Microsoft/Okta validation. |
| The client renders a default Microsoft-style page for `type=okta` | Script `checkEmail` range `[3729218,3746621)` and configuration response above | Static branch analysis with matching visible page | No Okta-specific client branch or browser navigation to Okta was observed. |
| The client includes MFA collection and approval workflows | Script ranges listed below | Static capability | No MFA challenge or completion was observed. Apparent code defects also limit claims of working capability. |
| Earlier deployments share the same complete client file | Three source records listed below | Exact HTTP-decoded script-body digest comparison | Different collectors can serve different operators. No person or account owner is identified. |
| Another delivery route reaches the same downstream infrastructure | [URLscan, 3rd November 2026](https://urlscan.io/result/01a1a2d3-ddea-70c9-9257-5e4dc696650d/) | Public HTTP and response-hash records | This route starts with Beehiiv, not the same OAuth app. |
| A shared analytics token leads to reused delivery code | Three complete response bodies in the delivery-template section below | Exact beacon/helper comparison and a destination-only normalized redirect match | Copied HTML can preserve a public token without common account control. |
| The 28th October page uses a different client and collector | Complete script `fdd28914-ca04-45ab-8195-2bf7e95c0920`; POST `850e33c8-1416-411a-ad2f-62a019ac20ff` | Static decoding and archived browser request | The shared delivery helper does not establish shared kit or operator identity. |
| Its success state depends on the attempt counter | Decoded `loginForm` submit handler, `maxAttempts`, and `submitAttempt` | Static source | One password submission was observed; the third-attempt branch was not executed in the capture. |
| Its Worker response resembles a successful Telegram channel-message receipt | Archived response to `850e33c8-1416-411a-ad2f-62a019ac20ff`; `sendOrEditTelegramMessage` | Client source and Worker response | No independent Telegram-side read or confirmed operator access. |
| Storm-1167 is a historical comparison, not a local actor assignment | Microsoft, 8th June 2023; Hexastrike, 20th May 2026 | Attributed external reporting and local comparison | The November capture lacks the account-side and control evidence required for case-specific attribution. |

## Script identity and recovery

The [complete archived script](https://sandfi.sh/api/requests/827d81ee-797d-4cac-84de-bb6f29eb2530/content)
has **5,072,545 bytes after HTTP content decoding, before JavaScript
deobfuscation**. Its SHA-256 was independently calculated from that complete
response:

```text
aa7351b192f1e577e2033e114155d9ce520762e1abd19a4b3b777b4483ffbb4c
```

The `/body` preview returns 2,097,152 characters with `truncated: true`.
The complete `/content` read supplied the remaining source. This limit is
separate from archive completeness and from identifier extraction limits.

Static recovery used literal parsing, hexadecimal escape decoding, numeric
calculations, string-table decoding, and concatenation. The archived program
was not executed. Byte ranges below are zero-based and half-open in the original
ASCII source:

| Range | Role |
|---|---|
| `[2016,2410)` | Fallback collector and deployment override |
| `[3729218,3746621)` | `checkEmail`: identity check, type selection, branding |
| `[3746621,3787484)` | `submitPass`: password encoding and submission |
| `[3787484,3799591)` | `OfficeSendVerify`: verification selection |
| `[4858161,4866299)` | `OfficeAjaxVerify`: code and approval handling |
| `[4866299,5063137)` | Consumer Microsoft recovery and verification workflows |
| `[5063137,5072545)` | Initial `user-check` and interface startup |

The password transformation uses URI-component encoding followed by Base64.
It does not hash the password. Static capability does not establish successful
backend relay or possession of an authenticated session.

The static review also found a reference to `verifyAppOTPH` without a definition
in this resource, an Office retry that invokes its function inside the
`setTimeout` argument, and a Hotmail telephone branch using jQuery `.value`
access. These are apparent client defects, not measured live branch failures.
They support describing the MFA paths as untested code rather than a verified
working bypass.

## Exact-file comparisons

Every script record below returned HTTP 200 and complete response evidence.
All requests in each additional case were read: 13, 23, and 46 respectively.

| Capture date, 2026 UTC | Analysis | Matching script request | Collector |
|---|---|---|---|
| 19th October, 16:49:27 | [158ccbc5-47b0-4839-9d86-e1f0cb86d9e0](https://sandfi.sh/analyses/158ccbc5-47b0-4839-9d86-e1f0cb86d9e0) | `69aaab9f-4696-4431-a639-b27e5fbb3889` | `pierce99.essentialdocstorage[.]de` |
| 29th October, 17:10:53 | [05662803-f88b-48a7-9dd1-5f2d893f916c](https://sandfi.sh/analyses/05662803-f88b-48a7-9dd1-5f2d893f916c) | `a2dfadf9-d6ab-4efa-8039-25377f1636a5` | `hazel66.silverveilvalley[.]de` |
| 31st October, 13:51:56 | [086d3508-ff5a-4fdd-b625-674a5102b171](https://sandfi.sh/analyses/086d3508-ff5a-4fdd-b625-674a5102b171) | `2503bf07-683a-410b-862d-4c93ed314273` | `clevermarsh.noticedtoday[.]surf` |

The 29th October run records credential-form submissions. The other two record
the script and collector contact without a credential-form submission. None
of these three request inventories contains an OAuth authorization request.

The shared DigitalOcean forwarding object has decoded-response SHA-256:

```text
a1185292fb347e2ff952aad898148bccd88f4c145634b21ecd29f3d8ded5e58c
```

URLscan capture `01a1a2d3-ddea-70c9-9257-5e4dc696650d`, dated **3rd November
2026, 19:12:44.384 UTC**, reports that digest for the same forwarding object.
Its earlier S3 object differs from the main case's application reply URL.

## Cloudflare token and delivery-template comparison

The public identifier is of kind `cloudflare_web_analytics`, with value
`10f6674ecde64ac78cb062e09acb0537`. It occurs inside `data-cf-beacon` in the
following response bodies. It is not a Turnstile sitekey, an account credential,
or evidence that an analytics beacon was successfully sent.

| Capture date, 2026 | Analysis | Token-bearing request | Observed endpoint of the run |
|---|---|---|---|
| 5th October | [363aee69-6779-49db-9bb2-3aa3df46efcc](https://sandfi.sh/analyses/363aee69-6779-49db-9bb2-3aa3df46efcc) | [995a1b1a-9b1d-451e-85d6-45aac4ae56ba](https://sandfi.sh/api/requests/995a1b1a-9b1d-451e-85d6-45aac4ae56ba/body) | Waiting page at `adverse151.ashbyepoint.com[.]de`. |
| 28th October | [35b3beef-3209-45a8-8421-a2c3849a4cfa](https://sandfi.sh/analyses/35b3beef-3209-45a8-8421-a2c3849a4cfa) | [5a04a7cb-3b7d-43f7-9b71-fa2d7fc75966](https://sandfi.sh/api/requests/5a04a7cb-3b7d-43f7-9b71-fa2d7fc75966/body) | DBS-branded form and an observed Worker submission. |
| 5th November | Main capture above | [786e5f84-06be-45f3-a5c7-24534e8f52df](https://sandfi.sh/api/requests/786e5f84-06be-45f3-a5c7-24534e8f52df/body) | CodeStorm-like client and `/google.php` submission. |

The exact public search used `type=analysis`, `per_type_limit=25`,
`include_total=true`, and an `identifier.value equals` condition for the token.
It returned three analyses, `total_is_exact=true`, and `truncated=false` on
5th November. All three were reviewed. This is a complete result for that query
at retrieval, not a complete campaign inventory.

The October runs used engine **0.4.0**, the same Pixel 7/mobile Firefox profile,
and web fact generation **2**. Their complete request inventories contained
8 and 19 records respectively. The October 5 evaluation used score policy
`v5` and recorded a benign verdict. The October 28 evaluation used `v12` and
recorded malicious/partial with `interaction_stalled`. These policy versions
are not equivalent to the November capture's `v13`.

### What was compared

All three `/body` reads returned HTTP 200 with `truncated=false`. Digests below
identify UTF-8 bytes of the HTTP-decoded response text, before any source
normalization:

| Response | Bytes | SHA-256 |
|---|---:|---|
| October 5 S3 | 1,499 | `362cc65afd78294c6ec59296f1ddf4aae6aa3e820248c0eb1239a0baaed64c63` |
| October 28 Fly.io page | 2,649 | `055e9c6bf13273f5786fcdcf8edd5d290608637f712ec9a49dc1a9b674c46f42` |
| November 5 S3 | 1,507 | `9665ccf431116ff345af9d089b31c7ab7c0542abfdcd0391bca8891562eeb162` |

For the two S3 pages, replace only the quoted destination in the
`window.location.href` assignment with `[DESTINATION]`. Make no other change.
The resulting byte strings are equal, with SHA-256
`21286fdfcdebb9eb3a19c8e2bf15c6d76d7a0cb8749f410d36030cfc9907c438`.
The original responses are not byte-identical.

The `getEmailFromURL` helper is equal across all three pages after converting
CRLF to LF and removing surrounding whitespace from that function excerpt.
The complete Cloudflare beacon script element is equal without normalization.
The October 28 page adds a different action: it updates its own query string
before loading the external client script. Shared template, copied code, and
common operator use remain alternative explanations; the comparison does not
establish which one occurred.

The October 5 [fingerprinting script](https://sandfi.sh/api/requests/e5f30681-5faa-43aa-8f9d-f7c2c6ccbc60/body)
contains a configured POST of browser observations. No such request appears
in the eight-record inventory. The reason it did not appear is unresolved.

### October 28 client and collection

The [complete client response](https://sandfi.sh/api/requests/fdd28914-ca04-45ab-8195-2bf7e95c0920/body)
contains 142,888 bytes with SHA-256
`b7dca9dea66cbdad19c66e43e441c1a84d7621ac3f8a7d12ce572b261c89acf7`.
Static recovery decoded numeric XOR literals, reconstructed the permutation
of 12 Base64 chunk arrays, and applied the three RC4 decoding layers. The
reconstructed Base64 stream matched both embedded checksums: DJB2-style
`1595022928` and FNV-1a `4013069924`. The decoded HTML contains 101,506
JavaScript UTF-16 code units. The program was not executed during this review.

Decisive source locations are `maxAttempts = 3`, the `loginForm` submit
handler, `submitAttempt`, `buildTelegramMessage`, and
`sendOrEditTelegramMessage`. The submit handler waits 2.5 seconds and chooses
failure or success from the attempt counter. It does not use a credential
validation response for that decision. Tab-hidden, before-unload, and idle
handlers contain additional collection calls; those branches were not
independently exercised.

The observed [Worker request and response](https://sandfi.sh/analyses/35b3beef-3209-45a8-8421-a2c3849a4cfa?tab=network&request=850e33c8-1416-411a-ad2f-62a019ac20ff)
use `tg8.internalwave.workers[.]dev/send`. The JSON request has `text`,
`parse_mode`, `email`, and `sessionKey` fields. Its formatted message includes
the submitted password as `Attempt 1`. Values are omitted. The response has
`ok: true` and a channel-message-shaped `result`. This is a response from the
Worker, not an independent Telegram API observation.

The post-submit [snapshot](https://sandfi.sh/api/analyses/35b3beef-3209-45a8-8421-a2c3849a4cfa/page-snapshots/f8a3dd66-4058-4a41-8338-9966cd0a6c41/screenshot)
shows `Attempt 2/3`. The capture contains one form submission, not three.
The request inventory and recovered frontend do not establish live Microsoft
authentication or a named kit family for this October deployment.

## Search boundaries

### Related link construction

Captures from [5th October 2026](https://sandfi.sh/analyses/363aee69-6779-49db-9bb2-3aa3df46efcc)
and [6th October 2026](https://sandfi.sh/analyses/bcc3014b-7694-4667-b65e-efa737991322)
used different OAuth application IDs but the same Salesforce-valued `uri`
and unusual padding markers, including `Builder.Decode` and `FlowEmail`.
This suggests shared link-generation logic. Both stopped on waiting pages,
so the comparison does not connect the final credential kit or its operator.
Code-like URL text does not establish execution or a measured filter bypass.

### Query scope

Research used public Sandfish request filters and URLscan searches on
5th November 2026. The exact Sandfish script-hash query returned four analyses,
including the main case; all were reviewed. The `Builder.Decode` request-URL
query returned three analyses, all reviewed. Exact queries for the initial app
ID and the main case's S3 object returned only the main case.
No additional use of that exact application was verified in these searches.

The Cloudflare-token expansion above is a separate exact query. Its two
additional cases are delivery-code comparisons. They do not increase the
four-capture count of identical Tencent client files.

The broader Sandfish request search for `prompt=none` returned 184 records; the
review read 100. A broad URLscan query returned approximately 8,124 records;
the review read 20. These sets include ordinary SSO and cannot supply campaign
or victim totals. The article relies on the stated exact comparisons, not an
assumption that the broad searches were complete.

URLscan's result JSON and archived-body endpoints required login. The public
result HTML exposed the navigation, HTTP records, and response hashes used here.
The main case's empty Sandfish similarity result was explicitly truncated and
inexact. It did not establish that no related cases existed.

## Registration context

The phishing landing and collector domains were registered on **2nd November
2026**, according to registry RDAP reads on 5th November:

- `realitysedge[.]sbs`: 02:54:32 UTC,
  [registry record](https://rdap.centralnic.com/sbs/domain/realitysedge.sbs).
- `breakfastforbirds[.]surf`: 16:32:23 UTC,
  [registry record](https://rdap.nic.surf/domain/breakfastforbirds.surf).

Both use Dynadot registration and Cloudflare nameservers. These providers do not
identify an operator. The request and source relationships connect the domains
in this capture. RDAP is a current lookup, not a preserved registration-history
service.

## Figure sources and transformations

Both screenshots come from analysis `69f874ad-68ab-45ae-8a8f-7a3ea8d21629`.
Coordinates use the original 412 × 839-pixel image and a top-left origin.

| Figure | Source and time, 5th November 2026 UTC | Transformation |
|---|---|---|
| `error-return.svg` | Microsoft, S3, Spaces, and navigation sources in the claim ledger | Original source-derived diagram. Arrows mean observed browser sequence. The final arrow combines the HTTP redirect and browser-recorded HTTPS upgrade. |
| `gate.webp` | Snapshot `af74c396-2637-4a77-b057-295ea3040d0d`, 17:21:27 | Crop `(0,250)` to `(412,605)`. RGB conversion and lossless WebP. No masking. |
| `login.webp` | Snapshot `9474a685-0a1d-4957-abf9-2904b0619643`, 17:21:42 | Mask rectangle `(40,298)` to `(380,331)` before cropping `(20,225)` to `(392,525)`. Add “Recipient address masked.” RGB conversion and lossless WebP. |

The login screenshot precedes password entry. Its company logos establish the
visible branding, not compromise of those organizations. No unmasked login
image is included in the prepared assets.

`case-links.svg` is a source-derived comparison diagram added in the same-day
expansion. Its upper lines refer to the four complete client responses in
the exact-file table. Its lower lines refer to the three token-bearing
responses and the stated transformations above. Lines are comparisons, not
traffic or ownership. It uses capture dates in 2026, has dimensions
520 × 760, and contains no recipient or session values.

Original screenshot SHA-256 values:

```text
gate  56c310c7e598197ab9ad4b0d6111c9ce1b713087e810e3d6e640fdb2488b844e
login 106cf70723070c42e770470cb9e3e3d0a18e132827605a13b32796ece228aafa
```

Prepared screenshot SHA-256 values:

```text
gate  a06499b0eafa5922adb2db8caa3338e7cb9e30501bc61ee61d13de7987cce97f
login dfc6c21b25d6a4a06a63c233301d7b9ad777ceb83e5bc47b817eda8a609bf820
```

## Published research

Research entries use displayed publication dates. Living documentation has no
established original publication date here; its update metadata is recorded
separately. Current text was retrieved on **5th November 2026**. Independent
historical copies were not checked.

| Source | Date basis | Relevant finding and limit |
|---|---|---|
| [Proofpoint: Microsoft and GitHub OAuth implementation redirection attacks](https://www.proofpoint.com/us/blog/cloud-security/microsoft-and-github-oauth-implementation-vulnerabilities-lead-redirection) | 8th December 2021 | Missing response type and application-configured destinations. Not the same app or deployment. |
| [LevelBlue: Phishing with OAuth Redirect](https://www.levelblue.com/blogs/spiderlabs-blog/phishing-with-oauth-redirect) | 18th February 2026 | A demonstration omits both redirect URI and response type and uses `prompt=none`. Does not expose this case's registration. |
| [Microsoft: OAuth redirection abuse enables phishing and malware delivery](https://www.microsoft.com/en-us/security/blog/2026/03/02/oauth-redirection-abuse-enables-phishing-malware-delivery/) | 2nd March 2026 | Failed silent authentication and recipient state transport. Its illustrated invalid-scope request differs from this case. |
| [Hexastrike: CodeStorm](https://hexastrike.com/resources/blog/threat-intelligence/codestorm-a-microsoft-365-aitm-phishing-kit-with-storm-1167-overlap/) | 20th May 2026 | Tencent COS staging, configurable PHP collector, tenant branding, and MFA interfaces. Its gate and some source details differ. |
| [ZeroBEC: CodeStorm tenant-aware replay](https://zerobec.com/blog/codestorm-phishkit-tenant-aware-m365-replay) | 21st June 2026 | Specific `/google.php` discovery protocol and branding/federation response fields. Its login field names differ. Its controlled identity-provider logs describe its own experiment. |
| [Microsoft: Detecting and mitigating a multi-stage AiTM phishing and BEC campaign](https://www.microsoft.com/en-us/security/blog/2023/06/08/detecting-and-mitigating-a-multi-stage-aitm-phishing-and-bec-campaign/) | 8th June 2023 | Original Storm-1167 attribution, indirect-proxy architecture, Tencent hosting, Indonesian IP infrastructure, and account-side follow-on activity. It does not name CodeStorm or attribute the November 2026 case. |
| [RFC 9700 section 4.11.2](https://www.rfc-editor.org/rfc/rfc9700.html#section-4.11.2) | January 2025 | Describes authorization servers used as redirectors through registered callbacks, errors, and silent requests. |
| [Microsoft MSAL Python: `ClientApplication`](https://learn.microsoft.com/en-us/python/api/msal/msal.application.clientapplication?view=msal-py-latest#msal-application-clientapplication-initiate-auth-code-flow) | Original date unknown; update metadata: 6th March 2026 | `initiate_auth_code_flow` documents an optional callback argument and a pre-registered default. It does not expose this application's registration or selection among multiple callbacks. |
| [Microsoft authorization-code flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow) | Original date unknown; `ms.date`: 9th January 2026; update metadata: 15th June 2026 | The normal request table calls `redirect_uri` required. This protocol-request description and the MSAL method have different scopes. Neither is a reproduction of this incomplete request's error path. |

The MSAL documentation supports the callback-default explanation. The captured
Microsoft response and matching S3 request establish the case-specific
destination. This review did not obtain the app manifest or test callback
selection with a new authorization request.

The CodeStorm comparison is a moderate-confidence family assessment, not an
exact source match. The linked reference repository returned 404. The current
[ZeroBEC Figure 5 asset](https://zerobec.com/blog/codestorm-fiddler-check.png)
shows obfuscated HTML in a source editor rather than the HTTP exchange described
by its caption. Direct image inspection on 5th November confirmed the mismatch.
The current asset's SHA-256 is
`ce527376bb52414161ff16101d0bee224da08b2a5244d22b55963e4e91447e76`.

The protocol comparison therefore uses the article's text as published
reporting, not independently checked packet evidence. The mismatch's cause and
the original-edition image remain unknown. It does not independently disprove
the report's other findings. No case-specific Storm-1167 attribution is supported.

### Storm-1167 comparison

Microsoft's 2023 report supplies the original activity-cluster attribution and
its account-side evidence. Hexastrike's May 2026 report assigns the CodeStorm
kit name and makes a moderate-confidence overlap assessment. Its cited bases
are Tencent use, Indonesian-language source artifacts, and post-compromise
tradecraft. Code language and the location of infrastructure are distinct
observations. Shared providers, purchased or copied code, and common
post-compromise methods are alternatives to exclusive operator control.

The local assessment does not turn those two external names into aliases.
The November capture supports the client-family comparison but supplies no
successful session replay, MFA-method change, mailbox activity, or account-control
record tying its operator to Storm-1167. The article's operator remains unassigned.
