[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6P9GWMF6T2NXJDCM3X123QK":3,"skill-same:01M6P9GWMF6T2NXJDCM3X123QK":69},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":27,"commitSha":50,"skillDirectory":51,"forksCount":13,"repositoryHtmlUrl":52,"repositoryHomepageUrl":6,"ownerHtmlUrl":53,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":54,"install":55,"resources":68},"01M6P9GWMF6T2NXJDCM3X123QK","mid-engagement-ir-detection",null,"Methodology for detecting client SOC patches, attacker activity, and security-state changes that occur DURING a red-team engagement","duc-duy-1612\u002FOnline-Delivery-Systems-Web","duc-duy-1612","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F192231384?v=4","01M6NSDKQQDKVTHZ8DZMZ1VYY6","01M6NSDKW8DDVTQHBT1HBDDJPT",0,"MIT","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fmid-engagement-ir-detection\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"mid engagement ir detection","detection","engagement","only","changes","for","detecting","activity","upstream","main","community","safe","process","team","soc","red-team","license","state","security","github","2026-11-10 10:12:30",false,"---\nname: mid-engagement-ir-detection\ndescription: Methodology for detecting client SOC patches, attacker activity, and\n  security-state changes that occur DURING a red-team engagement\ncategory: security\nrisk: safe\nsource: https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\nsource_repo: elementalsouls\u002FClaude-BugHunter\nsource_type: community\ndate_added: '2026-09-20'\nlicense: MIT\nlicense_source: https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\u002Fblob\u002Fmain\u002FLICENSE\ncompatibility: Process guidance; no tooling required. Docs-only; upstream templates\n  not bundled.\nsources: authorized-engagement\nreport_count: 1\n---\n\n## When to use this skill\n\nTrigger when:\n- Running active testing against a target with active SOC monitoring\n- A confirmed-vulnerable finding stops reproducing on recheck\n- Baseline timing shifts unexpectedly (3× slower, sudden errors, new headers)\n- Response sizes change between test windows\n- New WAF cookies or headers appear that weren't there at session start\n- Lockout \u002F error rates change between test windows (especially LOCKED count for credential attacks)\n- Engagement is \"assume breach\" or \"white box\" — client knows you're testing\n\nDO NOT use for:\n- Bug bounty (client doesn't know you're there; no real-time IR)\n- Pure recon (no state-change happening)\n- One-off vulnerability scanning (no temporal dimension)\n\n---\n\n## The core insight\n\nIn a real red-team engagement against a competent SOC, the security state of the target is **not static**. It changes during your test in response to your traffic. These state changes are:\n\n1. **Themselves valuable findings** (positive operational observations about IR responsiveness)\n2. **Confirmation evidence** (mid-engagement patch = the original vulnerability was real)\n3. **Classification signals** (WAF rule deployment vs code fix — different remediation depth)\n\nAnti-pattern: treating reproduction failure as evidence the original signal was a false positive. **Original PoC artifacts captured before the change are still the vulnerability finding.**\n\n---\n\n## The discipline — capture before, diff after\n\n### Before any active test:\n\n```python\n# Capture pre-test fingerprint of the target\nfingerprint = {\n    \"ts_pre\": time.time(),\n    \"ip_seen\": \"\u003Coperator-src-ip>\",\n    \"baseline_response_time_ms\": \u003Cmeasure>,\n    \"baseline_response_size_bytes\": \u003Cmeasure>,\n    \"response_headers\": \u003Ccapture set>,\n    \"waf_cookies\": \u003Clist>,\n    \"lockout_count_in_state\": \u003Ccount from o365_attempts.json>,\n}\n```\n\nPersist to `engagement_log\u002Ffingerprint_pre.json`.\n\n### During the test:\n\nLog every test result with full context (timestamp, IP, payload, response code, response size, response time, headers if relevant) to JSONL append-only.\n\n### After the test session OR on first failed-recheck:\n\n```python\nfingerprint_post = same structure\ndelta = {\n    \"baseline_time_change_ms\": post.time - pre.time,\n    \"baseline_size_change_bytes\": post.size - pre.size,\n    \"new_headers_appeared\": post.headers - pre.headers,\n    \"new_waf_cookies\": post.cookies - pre.cookies,\n    \"new_lockouts\": post.locked_count - pre.locked_count,\n}\n```\n\nIf any delta is significant — **investigate, don't retract**.\n\n---\n\n## The three primary IR observations\n\n### Observation 1 — Mid-engagement WAF rule deployment\n\n**Symptoms:**\n- Original payloads return identical response → no signal at all on recheck\n- Body size identical to baseline (login page reflection)\n- Timing reverts to baseline regardless of payload\n- New cookie or header in responses (e.g., `cf-bm`, `__cf_bm`, `awselb`)\n- Specific keyword in URL\u002Fbody now triggers different response code (403, 406, 429)\n\n**Confirmation:** retry with WAF-evasion variants:\n- URL-encode the payload differently (`%27` vs `%5cu0027`)\n- Change request method (POST → PUT, GET → POST)\n- Different content-type (form-urlencoded → multipart)\n- Slower pace (5s → 60s between requests)\n- Mixed-case keywords (`SLEEP` → `SlEeP`)\n\nIf WAF-evasion variants restore the signal, the mitigation is at the WAF layer (bypassable).\n\nIf even WAF-evasion variants stay blocked, the mitigation is likely in code.\n\n**Finding template:**\n```\nSubject: Mid-engagement mitigation deployed for \u003Cvulnerability X>\nObservation: At engagement timestamp T0, vulnerability \u003CX> on \u003Cendpoint> was\nconfirmed via \u003CPoC>. At T0+\u003Cminutes>, recheck via the original payload no longer\nreproduces the timing\u002Ferror\u002Fsize differential. \u003CWAF-evasion variant> [does\u002Fdoes\nnot] restore the signal.\n\nDescription: This pattern is consistent with the client SOC observing engagement\ntraffic and deploying a mitigation in real time. Mitigation depth assessment:\n[at-WAF, bypassable] vs [in-code, durable].\n\nImpact (positive): Client SOC has both detection-grade visibility into application\ntraffic AND the authority to deploy mitigations within ~\u003Cminutes> of detection.\n\nImpact (caveat): The original vulnerability did exist and was exploitable for at\nleast the engagement window before mitigation. If the mitigation is at the WAF\nlayer only, the underlying code-level flaw remains exploitable via alternative\npayloads.\n\nRecommendation:\n1. Verify the mitigation is in code (parameterized queries, input sanitization),\n   not just at the WAF layer.\n2. Audit the codebase for the same root cause across sister applications.\n3. (Positive) Document the IR responsiveness as a capability metric.\n```\n\n### Observation 2 — Active concurrent attacker\n\n**Symptoms:**\n- Many `AADSTS50053` (LOCKED) responses despite your 1-attempt-per-user discipline\n- Lockouts cluster alphabetically or by some other sort key\n- New lockouts appear DURING your engagement (diff before\u002Fafter)\n- LOCKED rate exceeds expected baseline (in our engagement: 11% of all attempts → red flag)\n\n**Math check:**\n- Your discipline: 1 attempt per user lifetime\n- Smart Lockout default: lockout after 10 failed attempts; lockout duration starts at 60 seconds and grows with each subsequent lockout (not a flat 10-minute window)\n- Therefore: IF tool logs confirm exactly 1 attempt\u002Fuser (no burst retries, no parallel-goroutine duplicate sends, no tool misconfiguration), you cannot mathematically cause Smart Lockout — verify `journal.jsonl` shows 1 attempt\u002Fuser before asserting this\n- Therefore: every NEW AADSTS50053 accumulating during the window (per before\u002Fafter diff) was caused by someone else; pre-existing locks may stem from the legitimate user's own failures or a prior test run, so attribute only the newly-accumulating locks to an external party\n\n**Confirmation:**\n- Sort locked accounts alphabetically; if they cluster, attacker is using sorted username list\n- Compare pre-session lockout count vs post-session — new locks during your session = attacker is active *right now*\n- Probe a known-active \"system\" account (`noreply@`, `info@`, `oc@`) — if it's locked, attacker is hitting service mailboxes too (typically MFA-exempt → high-value to attackers)\n\n**Finding template:**\n```\nSubject: Active external password-spray campaign detected during engagement\n\nObservation: During M365 ROPC validation against the \u003Ctenant> Entra tenant, \u003CN>\nunique principals returned AADSTS50053 (Smart Lockout) when probed with a single\npassword attempt at safe pace. Our tool journal (journal.jsonl) confirms exactly\n1 attempt per user with no duplicate sends, so under the Smart Lockout default\n(lockout after 10 failed attempts) we cannot have caused these lockouts. The\nattribution below applies to the \u003CK> NEW locks that accumulated during our\nengagement window between \u003Ctimestamp_start> and \u003Ctimestamp_end> (per before\u002Fafter\ndiff); pre-existing locks are not attributed to this campaign.\n\nDescription: The pattern (alphabetical clustering, real-time accumulation,\nincluding system mailboxes) is consistent with an external attacker performing\na username-list-driven password spray attack against the tenant.\n\nImpact: An external adversary is actively attempting to compromise corporate\nM365 accounts. The attacker has knowledge of the user-email schema and a\npassword-guess wordlist. \u003CList of locked accounts is now in attacker's hands>\n(Smart Lockout differentiates valid from invalid usernames).\n\nRecommendation (CRITICAL — within 24h):\n1. Open a P1 incident with the SOC. Pull Entra sign-in logs for the \u003CN> locked\n   accounts over the last 30-60 days. Identify source IPs and time windows.\n2. Apply Conditional Access rule blocking sign-ins from outside \u003Cexpected geo>\n   for non-admin accounts.\n3. Enable Identity Protection's User Risk policy with auto-reset on high risk.\n4. Force tenant-wide password reset for all \u003CN> previously-locked accounts.\n5. Audit service accounts for MFA exemptions; ensure all human-interactive\n   accounts have phishing-resistant MFA.\n\nEvidence: engagement_log\u002Fpoc\u002Fm365\u002Flocked_accounts.txt (\u003CN> entries with timestamps)\n```\n\n### Observation 3 — Detection-induced rate limiting \u002F IP blocks\n\n**Symptoms:**\n- Specific IP starts returning 403 \u002F 429 \u002F 451 after a window of normal responses\n- Specific IP starts seeing dramatically slower responses (3x+ baseline)\n- TLS handshake fails or RST mid-connection\n- DNS suddenly returns NXDOMAIN for hosts that resolved before\n- Some hosts work from one IP but not another\n\n**Confirmation:**\n- Rotate to a different IP and retry — if works, you got rate-limited\u002Fblocked\n- Compare TTL on DNS responses — sudden short TTL = active mitigation deployed\n- Check `Server:`, `Via:`, `CF-Cache-Status:` headers for CDN-introduced limits\n\n**Finding template (operational note, usually low\u002Finfo severity):**\n```\nSubject: Engagement traffic detected — IP \u003CX> rate-limited at \u003Ctimestamp>\n\nObservation: After ~\u003CN> requests in \u003Cwindow> from IP \u003CX>, target \u003Chostname>\nbegan returning \u003Ccode> for all subsequent requests. Rotation to IP \u003CY>\nrestored normal responses.\n\nDescription: Active anti-automation control at the perimeter (CDN\u002FWAF\u002Forigin).\n\nImpact (positive): Volumetric anti-automation is functional.\n\nImpact (caveat): Rotation defeats this control trivially (cloud VMs cost \u003C$5).\nA patient adversary or spray-from-residential-proxies attacker is not affected.\n```\n\n---\n\n## State diff — the key technique\n\nMaintain three pieces of state:\n\n### 1. `engagement_log\u002Fbaseline.json` — captured at session start\n\n```json\n{\n  \"ts\": \"2026-05-08T13:00:00\",\n  \"source_ip\": \"\u003Coperator-src-ip>\",\n  \"targets\": {\n    \"https:\u002F\u002Ftarget.example.com\u002Flogin\": {\n      \"baseline_response_time_ms\": 584,\n      \"baseline_response_size_bytes\": 11966,\n      \"headers_seen\": [\"Server: Apache\", \"X-Powered-By: PHP\u002F8.0.26\"],\n      \"set_cookie_names\": [\"PHPSESSID\"]\n    }\n  },\n  \"m365\": {\n    \"lockout_count\": 247,\n    \"valid_creds_count\": 0\n  }\n}\n```\n\n### 2. `engagement_log\u002Fjournal.jsonl` — append-only test log\n\nEvery test logs:\n```jsonl\n{\"ts\":\"...\",\"ip\":\"...\",\"tool\":\"...\",\"target\":\"...\",\"payload\":\"...\",\"resp_code\":...,\"resp_size\":...,\"resp_ms\":...,\"verdict\":\"...\",\"notes\":\"...\"}\n```\n\n### 3. `engagement_log\u002Fstate_changes.jsonl` — observed deltas\n\nWhen a state change is detected, append:\n```jsonl\n{\"ts_observed\":\"2026-05-08T14:30:00\",\"change_type\":\"baseline_time_shift\",\"target\":\"https:\u002F\u002F\u003Cemployee-app-host>\u002F\u003Capp>\u002Flogin.php\",\"baseline_pre_ms\":24412,\"baseline_post_ms\":90403,\"interpretation\":\"likely WAF rule deployed\",\"actions_taken\":[\"tested WAF-evasion variants — no signal restoration\",\"documented as IR-mitigation finding\"]}\n```\n\nThis third file is your finding evidence. Every entry is a candidate finding.\n\n---\n\n## Tooling — automated state-change detection\n\n```bash\n# Bash watcher — runs every 5 min during engagement, alerts on shifts\n\ncd \"$ENGAGEMENT_DIR\"\n\n# Re-measure baseline timing on key targets\nfor target in \"$@\"; do\n  ms=$(curl -sk -o \u002Fdev\u002Fnull -w \"%{time_total}\" \"$target\" --max-time 30)\n  ms_int=$(echo \"$ms * 1000\" | bc | cut -d. -f1)\n  echo \"$(date -u +%FT%TZ) $target $ms_int\" >> baseline_history.log\ndone\n\n# Diff against pre-session baseline\npython3 - \u003C\u003C 'PY'\nimport json, time\nbaseline = json.load(open(\"engagement_log\u002Fbaseline.json\"))\nfor line in open(\"baseline_history.log\"):\n    parts = line.strip().split()\n    ts, target, ms = parts[0], parts[1], int(parts[2])\n    if target in baseline[\"targets\"]:\n        pre = baseline[\"targets\"][target][\"baseline_response_time_ms\"]\n        if abs(ms - pre) > pre * 0.5:  # >50% shift\n            print(f\"ALERT {ts} {target} time {pre} -> {ms}\")\nPY\n```\n\nFor lockout-count tracking on M365:\n```bash\n# Run every 30 min during M365 spray\nLOCK_COUNT=$(grep -c '\"AADSTS50053\"' engagement_log\u002Fo365_results.jsonl)\necho \"$(date -u +%FT%TZ) lockout_count $LOCK_COUNT\" >> lockout_history.log\n# Diff first vs last to surface delta\n```\n\n---\n\n## The \"single signal recanted\" rule\n\nIf a confirmed-vulnerable finding stops reproducing:\n\n1. **DO NOT delete the original PoC.** Original timestamps + payloads + response captures are forever.\n2. **Capture the new state in detail.** What's the recheck response? What's different?\n3. **Try at least 3 alternative vectors** before declaring \"indeterminate\".\n4. **If none restore the signal**, document as \"vulnerability confirmed at T0, mitigation observed at T0+\u003Cdelta>, mitigation depth: [WAF | code]\".\n5. **Both states go in the report.** The original finding + the IR observation.\n\nThis is the discipline that distinguishes professional red team from \"hobbyist scanning\". Your client wants the timeline of vulnerability + mitigation, not just the static state.\n\n---\n\n## Why this is a finding (selling it to the client)\n\nWhen you tell a client \"I confirmed SQLi at 14:24, you deployed a mitigation by 14:55, here's the original PoC and here's why you should still verify the fix is in code\":\n\n- **Confirmed the vulnerability existed** (auditor-grade evidence)\n- **Confirmed the mitigation was deployed** (positive ops finding for the SOC)\n- **Identified the depth question** (WAF vs code — different remediation cost)\n- **Demonstrated red-team value** (you proved both the bug AND the IR responsiveness)\n\nThis is a more valuable deliverable than \"I confirmed SQLi\" alone, because it captures the engagement's full operational picture.\n\n---\n\n## Anti-patterns to avoid\n\n- **\"Recheck failed → false positive.\"** No. Recheck failed because the target changed. Investigate the change.\n- **\"It's no longer vulnerable, drop the finding.\"** No. It WAS vulnerable. The finding stays. Add a \"current state: mitigated\" annotation.\n- **\"They patched it, mission accomplished.\"** No. WAF rule != code fix. Verify the depth.\n- **\"Don't tell the client we observed their patch.\"** Yes, do. It's a positive finding about their IR.\n- **\"Don't include the locked accounts list — they'll think we caused it.\"** No. Math + journaling discipline proves you didn't. Include the list with the math.\n\n---\n\n## Bridge to neighboring skills\n\n- `redteam-mindset` — broader discipline framework; this skill is a specific application\n- `m365-entra-attack` — specific case for M365 \u002F Smart Lockout differential\n- `evidence-hygiene` — how to capture and redact PoC evidence properly\n- `report-writing` — finding template for IR observations\n- `bb-methodology` — note that this skill is INAPPROPRIATE for bug bounty (no real-time IR there)\n\n---\n\n## One-line summary\n\n**Your engagement leaves a footprint. The footprint changes the target. Capture both states. Both are findings.**\n\n---\n\n## Related Skills & Chains\n\n- **`redteam-mindset`** — This skill is a specific application of the broader red-team discipline. Engagement flow: `redteam-mindset` loaded at engagement start → baseline-capture habit built in → when response patterns shift mid-test, `mid-engagement-ir-detection` activates to capture the SOC-patch state as a NEW finding (defensive-action observed = client capability metric, not \"the bug got fixed so we lose the finding\").\n- **`evidence-hygiene`** — Mid-engagement IR detection produces TWO states (pre-patch and post-patch); both need disciplined evidence capture or the second finding can't be defended. Engagement flow: baseline screenshots + timestamped request\u002Fresponse dumps at session start → response shift detected → second capture set with explicit timestamp delta → both packaged together.\n- **`m365-entra-attack`** — The single richest source of mid-engagement IR signal in modern engagements. Engagement flow: M365 spray triggers AADSTS50053 lockout → baseline lockout policy captured → if lockout window changes mid-test (e.g., from 60min to 24hr) → `mid-engagement-ir-detection` captures the policy change as a finding (\"CA policy hardened mid-engagement; defensive response measured\").\n- **`enterprise-vpn-attack`** + **`vmware-vcenter-attack`** — Critical-infrastructure CVE exploitation is the highest-noise activity; expect SOC to patch within hours. Engagement flow: confirmed VPN\u002FvCenter CVE → baseline capture BEFORE exploitation attempt → if appliance updates mid-test, capture as defensive-action finding → report both the original CVE AND the IR-response.\n- **`redteam-report-template`** — IR-observation findings get their own Subject in the deliverable, framed differently from technical-vuln findings. Engagement flow: `mid-engagement-ir-detection` captures behavior-change event → `triage-validation` 7-Question Gate (specifically: \"is the behavior-change attributable to my activity?\") → `redteam-report-template` packages as a \"client capability observation\" with explicit timeline and detection-latency metric.\n\n## Limitations\n\n- Process guidance only; it does not authorize probing or exploitation — those steps need their own authorized skill and confirmation gate.\n- Docs-only import: upstream templates, commands, and research assets are not bundled.\n\n### Example\n\n```markdown\nApply this skill's checklist to the current engagement notes and report gaps.\n```\n\n> Adapted from [elementalsouls\u002FClaude-BugHunter](https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter) (MIT); frontmatter, When to Use\u002FLimitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.\n","## When to use this skill\n\nTrigger when:\n- Running active testing against a target with active SOC monitoring\n- A confirmed-vulnerable finding stops reproducing on recheck\n- Baseline timing shifts unexpectedly (3× slower, sudden errors, new headers)\n- Response sizes change between test windows\n- New WAF cookies or headers appear that weren't there at session start\n- Lockout \u002F error rates change between test windows (especially LOCKED count for credential attacks)\n- Engagement is \"assume breach\" or \"white box\" — client knows you're testing\n\nDO NOT use for:\n- Bug bounty (client doesn't know you're there; no real-time IR)\n- Pure recon (no state-change happening)\n- One-off vulnerability scanning (no temporal dimension)\n\n---\n\n## The core insight\n\nIn a real red-team engagement against a competent SOC, the security state of the target is **not static**. It changes during your test in response to your traffic. These state changes are:\n\n1. **Themselves valuable findings** (positive operational observations about IR responsiveness)\n2. **Confirmation evidence** (mid-engagement patch = the original vulnerability was real)\n3. **Classification signals** (WAF rule deployment vs code fix — different remediation depth)\n\nAnti-pattern: treating reproduction failure as evidence the original signal was a false positive. **Original PoC artifacts captured before the change are still the vulnerability finding.**\n\n---\n\n## The discipline — capture before, diff after\n\n### Before any active test:\n\n```python\n# Capture pre-test fingerprint of the target\nfingerprint = {\n    \"ts_pre\": time.time(),\n    \"ip_seen\": \"\u003Coperator-src-ip>\",\n    \"baseline_response_time_ms\": \u003Cmeasure>,\n    \"baseline_response_size_bytes\": \u003Cmeasure>,\n    \"response_headers\": \u003Ccapture set>,\n    \"waf_cookies\": \u003Clist>,\n    \"lockout_count_in_state\": \u003Ccount from o365_attempts.json>,\n}\n```\n\nPersist to `engagement_log\u002Ffingerprint_pre.json`.\n\n### During the test:\n\nLog every test result with full context (timestamp, IP, payload, response code, response size, response time, headers if relevant) to JSONL append-only.\n\n### After the test session OR on first failed-recheck:\n\n```python\nfingerprint_post = same structure\ndelta = {\n    \"baseline_time_change_ms\": post.time - pre.time,\n    \"baseline_size_change_bytes\": post.size - pre.size,\n    \"new_headers_appeared\": post.headers - pre.headers,\n    \"new_waf_cookies\": post.cookies - pre.cookies,\n    \"new_lockouts\": post.locked_count - pre.locked_count,\n}\n```\n\nIf any delta is significant — **investigate, don't retract**.\n\n---\n\n## The three primary IR observations\n\n### Observation 1 — Mid-engagement WAF rule deployment\n\n**Symptoms:**\n- Original payloads return identical response → no signal at all on recheck\n- Body size identical to baseline (login page reflection)\n- Timing reverts to baseline regardless of payload\n- New cookie or header in responses (e.g., `cf-bm`, `__cf_bm`, `awselb`)\n- Specific keyword in URL\u002Fbody now triggers different response code (403, 406, 429)\n\n**Confirmation:** retry with WAF-evasion variants:\n- URL-encode the payload differently (`%27` vs `%5cu0027`)\n- Change request method (POST → PUT, GET → POST)\n- Different content-type (form-urlencoded → multipart)\n- Slower pace (5s → 60s between requests)\n- Mixed-case keywords (`SLEEP` → `SlEeP`)\n\nIf WAF-evasion variants restore the signal, the mitigation is at the WAF layer (bypassable).\n\nIf even WAF-evasion variants stay blocked, the mitigation is likely in code.\n\n**Finding template:**\n```\nSubject: Mid-engagement mitigation deployed for \u003Cvulnerability X>\nObservation: At engagement timestamp T0, vulnerability \u003CX> on \u003Cendpoint> was\nconfirmed via \u003CPoC>. At T0+\u003Cminutes>, recheck via the original payload no longer\nreproduces the timing\u002Ferror\u002Fsize differential. \u003CWAF-evasion variant> [does\u002Fdoes\nnot] restore the signal.\n\nDescription: This pattern is consistent with the client SOC observing engagement\ntraffic and deploying a mitigation in real time. Mitigation depth assessment:\n[at-WAF, bypassable] vs [in-code, durable].\n\nImpact (positive): Client SOC has both detection-grade visibility into application\ntraffic AND the authority to deploy mitigations within ~\u003Cminutes> of detection.\n\nImpact (caveat): The original vulnerability did exist and was exploitable for at\nleast the engagement window before mitigation. If the mitigation is at the WAF\nlayer only, the underlying code-level flaw remains exploitable via alternative\npayloads.\n\nRecommendation:\n1. Verify the mitigation is in code (parameterized queries, input sanitization),\n   not just at the WAF layer.\n2. Audit the codebase for the same root cause across sister applications.\n3. (Positive) Document the IR responsiveness as a capability metric.\n```\n\n### Observation 2 — Active concurrent attacker\n\n**Symptoms:**\n- Many `AADSTS50053` (LOCKED) responses despite your 1-attempt-per-user discipline\n- Lockouts cluster alphabetically or by some other sort key\n- New lockouts appear DURING your engagement (diff before\u002Fafter)\n- LOCKED rate exceeds expected baseline (in our engagement: 11% of all attempts → red flag)\n\n**Math check:**\n- Your discipline: 1 attempt per user lifetime\n- Smart Lockout default: lockout after 10 failed attempts; lockout duration starts at 60 seconds and grows with each subsequent lockout (not a flat 10-minute window)\n- Therefore: IF tool logs confirm exactly 1 attempt\u002Fuser (no burst retries, no parallel-goroutine duplicate sends, no tool misconfiguration), you cannot mathematically cause Smart Lockout — verify `journal.jsonl` shows 1 attempt\u002Fuser before asserting this\n- Therefore: every NEW AADSTS50053 accumulating during the window (per before\u002Fafter diff) was caused by someone else; pre-existing locks may stem from the legitimate user's own failures or a prior test run, so attribute only the newly-accumulating locks to an external party\n\n**Confirmation:**\n- Sort locked accounts alphabetically; if they cluster, attacker is using sorted username list\n- Compare pre-session lockout count vs post-session — new locks during your session = attacker is active *right now*\n- Probe a known-active \"system\" account (`noreply@`, `info@`, `oc@`) — if it's locked, attacker is hitting service mailboxes too (typically MFA-exempt → high-value to attackers)\n\n**Finding template:**\n```\nSubject: Active external password-spray campaign detected during engagement\n\nObservation: During M365 ROPC validation against the \u003Ctenant> Entra tenant, \u003CN>\nunique principals returned AADSTS50053 (Smart Lockout) when probed with a single\npassword attempt at safe pace. Our tool journal (journal.jsonl) confirms exactly\n1 attempt per user with no duplicate sends, so under the Smart Lockout default\n(lockout after 10 failed attempts) we cannot have caused these lockouts. The\nattribution below applies to the \u003CK> NEW locks that accumulated during our\nengagement window between \u003Ctimestamp_start> and \u003Ctimestamp_end> (per before\u002Fafter\ndiff); pre-existing locks are not attributed to this campaign.\n\nDescription: The pattern (alphabetical clustering, real-time accumulation,\nincluding system mailboxes) is consistent with an external attacker performing\na username-list-driven password spray attack against the tenant.\n\nImpact: An external adversary is actively attempting to compromise corporate\nM365 accounts. The attacker has knowledge of the user-email schema and a\npassword-guess wordlist. \u003CList of locked accounts is now in attacker's hands>\n(Smart Lockout differentiates valid from invalid usernames).\n\nRecommendation (CRITICAL — within 24h):\n1. Open a P1 incident with the SOC. Pull Entra sign-in logs for the \u003CN> locked\n   accounts over the last 30-60 days. Identify source IPs and time windows.\n2. Apply Conditional Access rule blocking sign-ins from outside \u003Cexpected geo>\n   for non-admin accounts.\n3. Enable Identity Protection's User Risk policy with auto-reset on high risk.\n4. Force tenant-wide password reset for all \u003CN> previously-locked accounts.\n5. Audit service accounts for MFA exemptions; ensure all human-interactive\n   accounts have phishing-resistant MFA.\n\nEvidence: engagement_log\u002Fpoc\u002Fm365\u002Flocked_accounts.txt (\u003CN> entries with timestamps)\n```\n\n### Observation 3 — Detection-induced rate limiting \u002F IP blocks\n\n**Symptoms:**\n- Specific IP starts returning 403 \u002F 429 \u002F 451 after a window of normal responses\n- Specific IP starts seeing dramatically slower responses (3x+ baseline)\n- TLS handshake fails or RST mid-connection\n- DNS suddenly returns NXDOMAIN for hosts that resolved before\n- Some hosts work from one IP but not another\n\n**Confirmation:**\n- Rotate to a different IP and retry — if works, you got rate-limited\u002Fblocked\n- Compare TTL on DNS responses — sudden short TTL = active mitigation deployed\n- Check `Server:`, `Via:`, `CF-Cache-Status:` headers for CDN-introduced limits\n\n**Finding template (operational note, usually low\u002Finfo severity):**\n```\nSubject: Engagement traffic detected — IP \u003CX> rate-limited at \u003Ctimestamp>\n\nObservation: After ~\u003CN> requests in \u003Cwindow> from IP \u003CX>, target \u003Chostname>\nbegan returning \u003Ccode> for all subsequent requests. Rotation to IP \u003CY>\nrestored normal responses.\n\nDescription: Active anti-automation control at the perimeter (CDN\u002FWAF\u002Forigin).\n\nImpact (positive): Volumetric anti-automation is functional.\n\nImpact (caveat): Rotation defeats this control trivially (cloud VMs cost \u003C$5).\nA patient adversary or spray-from-residential-proxies attacker is not affected.\n```\n\n---\n\n## State diff — the key technique\n\nMaintain three pieces of state:\n\n### 1. `engagement_log\u002Fbaseline.json` — captured at session start\n\n```json\n{\n  \"ts\": \"2026-05-08T13:00:00\",\n  \"source_ip\": \"\u003Coperator-src-ip>\",\n  \"targets\": {\n    \"https:\u002F\u002Ftarget.example.com\u002Flogin\": {\n      \"baseline_response_time_ms\": 584,\n      \"baseline_response_size_bytes\": 11966,\n      \"headers_seen\": [\"Server: Apache\", \"X-Powered-By: PHP\u002F8.0.26\"],\n      \"set_cookie_names\": [\"PHPSESSID\"]\n    }\n  },\n  \"m365\": {\n    \"lockout_count\": 247,\n    \"valid_creds_count\": 0\n  }\n}\n```\n\n### 2. `engagement_log\u002Fjournal.jsonl` — append-only test log\n\nEvery test logs:\n```jsonl\n{\"ts\":\"...\",\"ip\":\"...\",\"tool\":\"...\",\"target\":\"...\",\"payload\":\"...\",\"resp_code\":...,\"resp_size\":...,\"resp_ms\":...,\"verdict\":\"...\",\"notes\":\"...\"}\n```\n\n### 3. `engagement_log\u002Fstate_changes.jsonl` — observed deltas\n\nWhen a state change is detected, append:\n```jsonl\n{\"ts_observed\":\"2026-05-08T14:30:00\",\"change_type\":\"baseline_time_shift\",\"target\":\"https:\u002F\u002F\u003Cemployee-app-host>\u002F\u003Capp>\u002Flogin.php\",\"baseline_pre_ms\":24412,\"baseline_post_ms\":90403,\"interpretation\":\"likely WAF rule deployed\",\"actions_taken\":[\"tested WAF-evasion variants — no signal restoration\",\"documented as IR-mitigation finding\"]}\n```\n\nThis third file is your finding evidence. Every entry is a candidate finding.\n\n---\n\n## Tooling — automated state-change detection\n\n```bash\n# Bash watcher — runs every 5 min during engagement, alerts on shifts\n\ncd \"$ENGAGEMENT_DIR\"\n\n# Re-measure baseline timing on key targets\nfor target in \"$@\"; do\n  ms=$(curl -sk -o \u002Fdev\u002Fnull -w \"%{time_total}\" \"$target\" --max-time 30)\n  ms_int=$(echo \"$ms * 1000\" | bc | cut -d. -f1)\n  echo \"$(date -u +%FT%TZ) $target $ms_int\" >> baseline_history.log\ndone\n\n# Diff against pre-session baseline\npython3 - \u003C\u003C 'PY'\nimport json, time\nbaseline = json.load(open(\"engagement_log\u002Fbaseline.json\"))\nfor line in open(\"baseline_history.log\"):\n    parts = line.strip().split()\n    ts, target, ms = parts[0], parts[1], int(parts[2])\n    if target in baseline[\"targets\"]:\n        pre = baseline[\"targets\"][target][\"baseline_response_time_ms\"]\n        if abs(ms - pre) > pre * 0.5:  # >50% shift\n            print(f\"ALERT {ts} {target} time {pre} -> {ms}\")\nPY\n```\n\nFor lockout-count tracking on M365:\n```bash\n# Run every 30 min during M365 spray\nLOCK_COUNT=$(grep -c '\"AADSTS50053\"' engagement_log\u002Fo365_results.jsonl)\necho \"$(date -u +%FT%TZ) lockout_count $LOCK_COUNT\" >> lockout_history.log\n# Diff first vs last to surface delta\n```\n\n---\n\n## The \"single signal recanted\" rule\n\nIf a confirmed-vulnerable finding stops reproducing:\n\n1. **DO NOT delete the original PoC.** Original timestamps + payloads + response captures are forever.\n2. **Capture the new state in detail.** What's the recheck response? What's different?\n3. **Try at least 3 alternative vectors** before declaring \"indeterminate\".\n4. **If none restore the signal**, document as \"vulnerability confirmed at T0, mitigation observed at T0+\u003Cdelta>, mitigation depth: [WAF | code]\".\n5. **Both states go in the report.** The original finding + the IR observation.\n\nThis is the discipline that distinguishes professional red team from \"hobbyist scanning\". Your client wants the timeline of vulnerability + mitigation, not just the static state.\n\n---\n\n## Why this is a finding (selling it to the client)\n\nWhen you tell a client \"I confirmed SQLi at 14:24, you deployed a mitigation by 14:55, here's the original PoC and here's why you should still verify the fix is in code\":\n\n- **Confirmed the vulnerability existed** (auditor-grade evidence)\n- **Confirmed the mitigation was deployed** (positive ops finding for the SOC)\n- **Identified the depth question** (WAF vs code — different remediation cost)\n- **Demonstrated red-team value** (you proved both the bug AND the IR responsiveness)\n\nThis is a more valuable deliverable than \"I confirmed SQLi\" alone, because it captures the engagement's full operational picture.\n\n---\n\n## Anti-patterns to avoid\n\n- **\"Recheck failed → false positive.\"** No. Recheck failed because the target changed. Investigate the change.\n- **\"It's no longer vulnerable, drop the finding.\"** No. It WAS vulnerable. The finding stays. Add a \"current state: mitigated\" annotation.\n- **\"They patched it, mission accomplished.\"** No. WAF rule != code fix. Verify the depth.\n- **\"Don't tell the client we observed their patch.\"** Yes, do. It's a positive finding about their IR.\n- **\"Don't include the locked accounts list — they'll think we caused it.\"** No. Math + journaling discipline proves you didn't. Include the list with the math.\n\n---\n\n## Bridge to neighboring skills\n\n- `redteam-mindset` — broader discipline framework; this skill is a specific application\n- `m365-entra-attack` — specific case for M365 \u002F Smart Lockout differential\n- `evidence-hygiene` — how to capture and redact PoC evidence properly\n- `report-writing` — finding template for IR observations\n- `bb-methodology` — note that this skill is INAPPROPRIATE for bug bounty (no real-time IR there)\n\n---\n\n## One-line summary\n\n**Your engagement leaves a footprint. The footprint changes the target. Capture both states. Both are findings.**\n\n---\n\n## Related Skills & Chains\n\n- **`redteam-mindset`** — This skill is a specific application of the broader red-team discipline. Engagement flow: `redteam-mindset` loaded at engagement start → baseline-capture habit built in → when response patterns shift mid-test, `mid-engagement-ir-detection` activates to capture the SOC-patch state as a NEW finding (defensive-action observed = client capability metric, not \"the bug got fixed so we lose the finding\").\n- **`evidence-hygiene`** — Mid-engagement IR detection produces TWO states (pre-patch and post-patch); both need disciplined evidence capture or the second finding can't be defended. Engagement flow: baseline screenshots + timestamped request\u002Fresponse dumps at session start → response shift detected → second capture set with explicit timestamp delta → both packaged together.\n- **`m365-entra-attack`** — The single richest source of mid-engagement IR signal in modern engagements. Engagement flow: M365 spray triggers AADSTS50053 lockout → baseline lockout policy captured → if lockout window changes mid-test (e.g., from 60min to 24hr) → `mid-engagement-ir-detection` captures the policy change as a finding (\"CA policy hardened mid-engagement; defensive response measured\").\n- **`enterprise-vpn-attack`** + **`vmware-vcenter-attack`** — Critical-infrastructure CVE exploitation is the highest-noise activity; expect SOC to patch within hours. Engagement flow: confirmed VPN\u002FvCenter CVE → baseline capture BEFORE exploitation attempt → if appliance updates mid-test, capture as defensive-action finding → report both the original CVE AND the IR-response.\n- **`redteam-report-template`** — IR-observation findings get their own Subject in the deliverable, framed differently from technical-vuln findings. Engagement flow: `mid-engagement-ir-detection` captures behavior-change event → `triage-validation` 7-Question Gate (specifically: \"is the behavior-change attributable to my activity?\") → `redteam-report-template` packages as a \"client capability observation\" with explicit timeline and detection-latency metric.\n\n## Limitations\n\n- Process guidance only; it does not authorize probing or exploitation — those steps need their own authorized skill and confirmation gate.\n- Docs-only import: upstream templates, commands, and research assets are not bundled.\n\n### Example\n\n```markdown\nApply this skill's checklist to the current engagement notes and report gaps.\n```\n\n> Adapted from [elementalsouls\u002FClaude-BugHunter](https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter) (MIT); frontmatter, When to Use\u002FLimitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.\n",{"name":5,"risk":29,"source":43,"license":14,"sources":44,"category":36,"date_added":45,"description":7,"source_repo":46,"source_type":28,"report_count":47,"compatibility":48,"license_source":49},"https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter","authorized-engagement","2026-09-20","elementalsouls\u002FClaude-BugHunter",1,"Process guidance; no tooling required. Docs-only; upstream templates not bundled.","https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\u002Fblob\u002Fmain\u002FLICENSE","f3312ba749561b6159f477dbfc9313c875d40a56",".agents\u002Fskills_archive\u002Fmid-engagement-ir-detection","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612","2026-10-25 23:50:58",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill mid-engagement-ir-detection",{"tool":61,"command":62},"bunx","bunx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill mid-engagement-ir-detection",{"tool":64,"command":65},"pnpm","pnpm dlx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill mid-engagement-ir-detection","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fmid-engagement-ir-detection\u002FSKILL.md",true,[],[70,98,122,144,172,196],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":81,"publishedAt":38},"01M6Q0FA5FZ410Z33Z3TFQ4DBA","zero-trust","Implement zero-trust network architecture. Configure identity-based access, micro-segmentation, and continuous verification. Use when implementing modern security architectures.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fzero-trust\u002FSKILL.md",[76],{"categoryNo":77,"categoryName":78,"taxonomyType":79,"taxonomyTypeDesc":80},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",[82,83,84,85,86,87,72,88,89,90,91,21,28,27,92,93,94,95,96,97],"ZeRO","trust","zero trust","architecture","assessment","verification","segmentation","devops-security","access","configure","active","critical","scope","scripts","relevant","implementing",{"skillNo":99,"name":100,"nameZh":6,"description":101,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":102,"categories":103,"keywords":104,"publishedAt":38},"01M6Q0F1YZJW3KX5FBKEVZ8CV4","zcode-delegate","Delegate coding tasks to the Z.AI ZCode CLI only when the user explicitly requests it, while the orchestrator retains review and landing responsibility.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fzcode-delegate\u002FSKILL.md",[],[105,106,107,108,109,110,111,112,113,114,115,116,117,21,118,28,119,120,93,121],"delegate","zcode","zcode delegate","review","cli","orchestration","tasks","run","Bash","model","node","read","orchestrator","files","desktop app","app","agent-orchestration",{"skillNo":123,"name":124,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":128,"publishedAt":38},"01M6Q0ETK41VGNMYEXW0T00AJN","youtube-transcript","Fetch YouTube transcripts through DeepAPI or local fallback tooling and save clean text output.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-transcript\u002FSKILL.md",[],[129,130,131,29,37,132,133,34,134,135,136,137,138,28,27,139,140,141,142,143],"transcript","youtube transcript","youtube","codex","research","https","text","fetch","tooling","clean","local","output","deepapi","youtube transcripts","blob",{"skillNo":145,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":148,"categories":149,"keywords":161,"publishedAt":38},"01M6Q0EPPH2PVMDW40017F8WX0","youtube-transcript-skills","Fetch YouTube video transcripts, search videos\u002Fchannels, browse channels, and extract playlists via the getyoutubetranscript.com API - free tier, no card required.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-transcript-skills\u002FSKILL.md",[150,153,156],{"categoryNo":151,"categoryName":152,"taxonomyType":79,"taxonomyTypeDesc":80},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":154,"categoryName":155,"taxonomyType":79,"taxonomyTypeDesc":80},"01JBSEEDCAT000000000000016","图像与多媒体",{"categoryNo":157,"categoryName":158,"taxonomyType":159,"taxonomyTypeDesc":160},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[129,130,131,162,29,163,37,132,34,164,165,166,167,136,168,28,27,169,170,171],"youtube transcript skills","gemini","search","api","Video","browse","api-integration","free","card","channels",{"skillNo":173,"name":174,"nameZh":6,"description":175,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":182,"publishedAt":38},"01M6Q0EAB96PX83W5PTE8A0R8D","youtube-notetaker","Turn YouTube talks into local study notes with slides, transcripts, editable annotations, and a markdown-backed viewer.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-notetaker\u002FSKILL.md",[178,181],{"categoryNo":179,"categoryName":180,"taxonomyType":79,"taxonomyTypeDesc":80},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":154,"categoryName":155,"taxonomyType":79,"taxonomyTypeDesc":80},[131,183,184,109,29,185,186,187,188,34,166,189,190,191,192,27,193,139,194,195],"youtube notetaker","notetaker","workflow","official","notes","code","markdown","slides","annotations","README","study","dair-academy","viewer",{"skillNo":197,"name":198,"nameZh":6,"description":199,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":200,"categories":201,"keywords":203,"publishedAt":38},"01M6Q0E55F0GW5MX48V8HXS8B5","youtube-full","Fetch YouTube transcripts, search videos, browse channels, and extract playlists via TranscriptAPI — no yt-dlp, no Google API key, works from any cloud server.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-full\u002FSKILL.md",[202],{"categoryNo":151,"categoryName":152,"taxonomyType":79,"taxonomyTypeDesc":80},[204,131,205,206,29,163,164,207,208,165,166,167,136,209,168,28,27,169,210,142],"youtube full","full","google","server","dlp","credits","automatic"]