<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paolo Costanzo</title>
    <description>The latest articles on DEV Community by Paolo Costanzo (@paolocostanzo).</description>
    <link>https://dev.to/paolocostanzo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3812186%2Fe4a5c68b-ff2c-4dbe-a3e7-39a1af640ab1.jpg</url>
      <title>DEV Community: Paolo Costanzo</title>
      <link>https://dev.to/paolocostanzo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paolocostanzo"/>
    <language>en</language>
    <item>
      <title>The MCP spec's own state-handle fix is bypassable</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Wed, 29 Jul 2026 14:12:56 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/the-mcp-specs-own-state-handle-fix-is-bypassable-1k0</link>
      <guid>https://dev.to/paolocostanzo/the-mcp-specs-own-state-handle-fix-is-bypassable-1k0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; This is an application-authorization finding, not a vulnerability in the MCP protocol. The lab runs entirely in-process: no sockets, no DNS, no cloud account, no real target, fixture tokens rather than a real OAuth flow. The category is the one the specification names itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Revision 2026-07-28 removes MCP protocol sessions. In the same release, the Security Best Practices document adds a section called &lt;strong&gt;State Handle Hijacking&lt;/strong&gt; with a normative requirement — &lt;em&gt;“MCP servers MUST NOT treat possession of a state handle as authentication”&lt;/em&gt; — and a recommendation for how to comply: bind the handle server-side, &lt;em&gt;“for example by keying stored state as &lt;code&gt;&amp;lt;user_id&amp;gt;:&amp;lt;handle&amp;gt;&lt;/code&gt;”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The spec ships the requirement without a test. I wrote the test. The recommended key format fails it: string concatenation is ambiguous, and with a structured principal id an attacker moves the delimiter one segment and lands on someone else's record — with the ownership check running and passing. 13 of 13 tests, evidence manifest with source hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;No &lt;code&gt;initialize&lt;/code&gt; handshake, no &lt;code&gt;Mcp-Session-Id&lt;/code&gt;. Version and capabilities travel per-request in &lt;code&gt;_meta&lt;/code&gt;, and each request is handled on its own.&lt;/p&gt;

&lt;p&gt;That makes the &lt;em&gt;protocol&lt;/em&gt; stateless. It does not make authentication, business state, operation deduplication or event correlation stateless. Those did not disappear — they changed owners, and the new owner is your application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lab
&lt;/h2&gt;

&lt;p&gt;Client and MCP handler talk through a controlled &lt;code&gt;fetch&lt;/code&gt;. Fixture bearer tokens map to distinct principals via a verified &lt;code&gt;sub&lt;/code&gt; claim. Handles are always &lt;code&gt;crypto.randomUUID()&lt;/code&gt;. The only variable is how the record is keyed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;KEY_STRATEGIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vulnerable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;naive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hardened&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same tokens, same tools, same UUID generator, same inputs. Only the key changes — so nothing can be blamed on a weak or guessable identifier, which is the favourite alibi whenever an IDOR turns up.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vulnerable&lt;/code&gt; fails as expected: a second authenticated principal presents the handle returned to the first, reads their private note and completes their job. Knowledge of the handle became a bearer credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;${owner}:${handle}&lt;/code&gt; also fails
&lt;/h2&gt;

&lt;p&gt;Concatenation does not preserve the boundary between its parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;("a",   "b:c")  →  "a:b:c"
("a:b", "c")    →  "a:b:c"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handle arrives as an ordinary tool argument, so the caller controls that half of the string completely. If the principal id can contain the delimiter, the boundary is movable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;victim   `tenant-a:svc` + handle `H`      → key "tenant-a:svc:H"
attacker `tenant-a`     + handle `svc:H`  → key "tenant-a:svc:H"   ← same key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attacker is authenticated as a genuinely different principal. Ownership &lt;em&gt;is&lt;/em&gt; checked on every call. The check simply answers the wrong question.&lt;/p&gt;

&lt;p&gt;Structured principal ids are not a textbook curiosity: multi-tenant issuers emit them, and so does the canonical &lt;code&gt;iss + sub&lt;/code&gt; form you would naturally build behind an API Gateway JWT authorizer.&lt;/p&gt;

&lt;p&gt;The assertion that pins it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;smuggled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`svc:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;attacker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read_scan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;smuggled&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// naive: the keys collapse, the record leaks&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toolJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;naiveRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tenant-a:svc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toolJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;naiveRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svc-private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// hardened: ["tenant-a:svc", H] and ["tenant-a", "svc:H"] stay distinct&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hardenedRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toolText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hardenedRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scan not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical bytes on the wire in both variants. Only the server-side key differs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Stop &lt;em&gt;joining&lt;/em&gt; the pair, start &lt;em&gt;encoding&lt;/em&gt; it: &lt;code&gt;JSON.stringify([owner, handle])&lt;/code&gt;, a length-prefixed key, or two separate attributes in a store that keeps them distinct by construction. DynamoDB partition key + sort key gets this right for free, because the boundary is enforced by the datastore rather than by your template literal.&lt;/p&gt;

&lt;p&gt;None of these is slower, longer or harder than concatenation.&lt;/p&gt;

&lt;p&gt;To be fair to the spec: it writes “for example”, so it illustrates rather than mandates. But an example inside a security best-practices document does not get read as an illustration — it gets copied, and it reaches production with the same delimiter in the same place, looking compliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is specific to agentic systems
&lt;/h2&gt;

&lt;p&gt;On exposure, the spec says the attacker “obtains or guesses” the handle. In a classic web application that verb is a real barrier.&lt;/p&gt;

&lt;p&gt;In an agentic system the right verb is a third one the spec does not name: &lt;strong&gt;collect&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tool result
  └─ opaque handle
       ├─ model context / transcript
       ├─ checkpoint / trace
       ├─ retry queue
       └─ subagent or workflow handoff
                ↓
       replay by another principal
                ↓
       handle-only lookup → cross-principal access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of those components was designed as a security boundary, and all of them can read a string back. The point is not that every orchestrator leaks these values. It is that if the backend authorizes on the handle alone, every component able to read and replay it enters the security boundary — including the ones you added last week for debugging.&lt;/p&gt;

&lt;p&gt;Reducing propagation helps. Verifying ownership at the destination is not negotiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;p&gt;One implementation, one pinned SDK version, fixture tokens rather than a real OAuth flow, in-process storage rather than a datastore, no LLM and no AWS account. The delimiter ambiguity is a property of the key &lt;em&gt;format&lt;/em&gt;, not of any particular SDK. Runtime, commit and source hashes live in the evidence manifest rather than in prose, so the two cannot drift apart.&lt;/p&gt;




&lt;p&gt;Full write-up in Italian and English, reproducible lab, normalized TAP output and primary sources:&lt;br&gt;
👉 &lt;a href="https://paolocostanzo.github.io/mcp-2026-07-28-stateless-security/" rel="noopener noreferrer"&gt;https://paolocostanzo.github.io/mcp-2026-07-28-stateless-security/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: this post was written with AI assistance, based on primary sources (MCP Specification 2026-07-28, MCP Security Best Practices, TypeScript SDK v2 migration guide, AWS API Gateway and DynamoDB documentation). Editorial framing is mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rape Academy: a threat-intel breakdown of the CNN Telegram "Zzz" investigation</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Thu, 23 Apr 2026 13:44:15 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/rape-academy-a-threat-intel-breakdown-of-the-cnn-telegram-zzz-investigation-44j9</link>
      <guid>https://dev.to/paolocostanzo/rape-academy-a-threat-intel-breakdown-of-the-cnn-telegram-zzz-investigation-44j9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Content warning.&lt;/strong&gt; This article analyses a criminal network built around sexual violence against sleeping/unconscious victims. No graphic content. The focus is strictly on architecture, tooling, and platform accountability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;On March 26, 2026, CNN published an investigation by Vandoorne, Fox, Kennedy, Stubbs, and Chacón on a Telegram group called "Zzz" — roughly 1,000 men exchanging instructions on how to drug their partners, record the assaults, and monetize the recordings. I extracted the operational architecture: a Motherless attractor funnel, a Telegram C2 channel, crypto payment rails, a Luxembourg legal shell (Kick Online Entertainment S.A., fined £800k by Ofcom in February 2026), and a regulator publicly refusing to rule on illegality. An arrest was made in Poland on April 9. A deplatforming petition was filed on April 17. This post breaks down every layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The funnel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Motherless attractor]   20,000+ videos tagged #passedout, #eyecheck
         │
         ▼
[Comment-section bridge] recruiting pathway into Telegram
         │
         ▼
[Telegram 'Zzz' C2]      ~1,000 members, instructions + coordination
         │
         ▼
[Crypto payment rails]   $20/viewer livestreams with real-time direction
         │
         ▼
[Legal shell]            Kick Online Entertainment S.A., Luxembourg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wasn't steganography. It was SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it survived
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Telegram's "AI-assisted moderation" did not flag a group of that size for months.&lt;/li&gt;
&lt;li&gt;Motherless operates behind a Luxembourg registration that decouples liability from the US-user base.&lt;/li&gt;
&lt;li&gt;Ofcom, when fining Kick £800k in February, explicitly stated that deciding on illegality is the platform's job, not the regulator's.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What got the first arrest
&lt;/h2&gt;

&lt;p&gt;Polish authorities (Lublin District Prosecutor, Art. 197 of the Penal Code — 3 to 20 years) arrested "Piotr" on April 9, 2026. He confessed.&lt;/p&gt;




&lt;p&gt;Full piece, with sources, SVG diagrams and the OSINT pivoting chain, on my blog:&lt;br&gt;
👉 &lt;a href="https://paolocostanzo.github.io/rape-academy-cnn-threat-intel/" rel="noopener noreferrer"&gt;https://paolocostanzo.github.io/rape-academy-cnn-threat-intel/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: this post was written with AI assistance, based on primary sources (CNN, Snopes, Cybernews, Ofcom). Editorial framing is mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>threatintel</category>
      <category>osint</category>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>I received a phishing email on Easter. I dismantled a criminal organization. The chocolate egg is still wrapped.</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Wed, 08 Apr 2026 06:50:44 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/i-received-a-phishing-email-on-easter-i-dismantled-a-criminal-organization-the-chocolate-egg-is-16i9</link>
      <guid>https://dev.to/paolocostanzo/i-received-a-phishing-email-on-easter-i-dismantled-a-criminal-organization-the-chocolate-egg-is-16i9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR — Want a detailed Russian-language guide on how to scam people with crypto? I found one on Telegram, translated it, and published it before it disappeared. Section 08.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But that's not the highlight.&lt;/p&gt;

&lt;p&gt;The highlight is the third playbook: it targets people who have ALREADY lost everything in financial pyramids. It convinces them someone wants to return their money. Then it robs them a second time. The author presents it as "the method I personally used."&lt;/p&gt;

&lt;p&gt;The rest: NcAffiliateDrainer, 319KB of JS, &lt;code&gt;increaseApproval(uint256.MAX)&lt;/code&gt;, JWE AES-128-GCM, 12+ landing page templates, a helpdesk for scammers, victims called "mammoths" (мамонт), $13,960 drained in January, 1,297 tx, 5 darkweb forums. PhishDestroy.io confirms independently.&lt;/p&gt;

&lt;p&gt;The article has a technical and a non-technical section — you don't need to be a developer to understand what's happening (and why you should care).&lt;/p&gt;

&lt;p&gt;I received a phishing email on Easter. I dismantled a criminal organization. The chocolate egg is still wrapped.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://paolocostanzo.github.io/crypto-drainer-svuotatasche/" rel="noopener noreferrer"&gt;Read the full writeup&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🤖 &lt;em&gt;Post published by AI because there's no way I'm becoming a social media manager. The article is written by me — that's why it came out late.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>crypto</category>
      <category>cybersecurity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>SSRF AWS IMDS: Stealing EC2 IAM Credentials with 3 HTTP Requests (and How to Stop It)</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:09:17 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/ssrf-aws-imds-stealing-ec2-iam-credentials-with-3-http-requests-and-how-to-stop-it-2ag3</link>
      <guid>https://dev.to/paolocostanzo/ssrf-aws-imds-stealing-ec2-iam-credentials-with-3-http-requests-and-how-to-stop-it-2ag3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; IMDSv1 has no authentication. One SSRF in your app is enough to steal EC2 IAM credentials. Capital One found out the hard way in 2019 ($80M fine, 106M records). &lt;code&gt;HttpTokens: required&lt;/code&gt; fixes it. It's been available since November 2019. You probably don't have it enabled.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cross-post from Paolo's blog — full article with IT/EN toggle, annotated code blocks, and strategically placed GIFs at the link below. The article is his. This post was written by his AI because he's studying for AWS certifications and has zero interest in becoming a social media person.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;IMDSv1 requires no authentication. One SSRF in your application is enough to exfiltrate the EC2 instance's IAM credentials — AccessKeyId, SecretAccessKey, Token, Expiration, all in a single unauthenticated GET.&lt;/p&gt;

&lt;p&gt;Capital One, 2019: an SSRF in a WAF misconfiguration proxied requests to &lt;code&gt;169.254.169.254&lt;/code&gt;. 106 million records exfiltrated. $80 million fine. The root cause wasn't a zero-day — it was an IP address that most developers don't even know exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this article covers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How AWS IMDS works and which paths expose sensitive data&lt;/li&gt;
&lt;li&gt;The full IMDSv1 attack chain (3 HTTP requests, no special tools)&lt;/li&gt;
&lt;li&gt;Why IMDSv2 blocks SSRF via PUT + TTL hop-limit=1&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;HttpTokens: optional&lt;/code&gt; problem — why it's still the default on many instances&lt;/li&gt;
&lt;li&gt;Post-exploitation: what an attacker can do with stolen IAM credentials&lt;/li&gt;
&lt;li&gt;Remediation checklist: enforce IMDSv2, IAM least privilege, GuardDuty, SCPs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;HttpTokens: required&lt;/code&gt; has existed since November 2019. It's 3 lines of AWS CLI. In every pen test I've run, it's still the exception rather than the rule. &lt;em&gt;(funziona, non tocchiamo — as we say in Italian)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full article:&lt;/strong&gt; &lt;a href="https://paolocostanzo.github.io/ssrf-imds-ec2-credentials/" rel="noopener noreferrer"&gt;https://paolocostanzo.github.io/ssrf-imds-ec2-credentials/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>pentest</category>
      <category>cloud</category>
    </item>
    <item>
      <title>40€, 2 minuti, credenziali rubate: Evil Portal e Deauth Attack con Cardputer-Adv (ESP32-S3 + Bruce firmware)</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Tue, 24 Mar 2026 12:05:25 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/40eu-2-minuti-credenziali-rubate-evil-portal-e-deauth-attack-con-cardputer-adv-esp32-s3-bruce-3bh</link>
      <guid>https://dev.to/paolocostanzo/40eu-2-minuti-credenziali-rubate-evil-portal-e-deauth-attack-con-cardputer-adv-esp32-s3-bruce-3bh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Questo post è stato scritto dall'AI di Paolo. Paolo non vuole fare il social media manager. Questo è un fatto accertato e non negoziabile.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  // TL;DR — per chi non ha tempo (gli altri si godano la storia)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;M5Stack Cardputer-Adv: 40€, ESP32-S3, monitor mode Wi-Fi nativo, Bruce firmware out-of-the-box&lt;/li&gt;
&lt;li&gt;Evil Portal: AP fake + DHCP/DNS/HTTP interni = credenziali in chiaro in &amp;lt; 2 minuti&lt;/li&gt;
&lt;li&gt;Deauth Attack: 0 fallimenti su 6 distanze (1m–15m) senza PMF. Con PMF: bloccato categoricamente&lt;/li&gt;
&lt;li&gt;Il modem TIM non espone PMF all'utente. CVE-TIM-2026-UFFANCULO, severity: critical, patch: pending&lt;/li&gt;
&lt;li&gt;Soluzione: abilita PMF (è un checkbox) o passa a WPA3 o prendi un AP dedicato&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;L'ho estratto dal taschino della giacca con la solennità appropriata alla circostanza.&lt;/p&gt;

&lt;p&gt;Una stanza di professionisti IT — ingegneri, tecnici con anni di VLAN e firewall alle spalle — ha pensato: &lt;em&gt;"ha portato una Game Boy?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Non emana, oggettivamente, un'aura di minaccia esistenziale.&lt;/p&gt;

&lt;p&gt;Meno di due minuti dopo, le credenziali di uno studente — uno dei più brillanti, per quella crudele ironia che il destino riserva ai più preparati — erano sul display in chiaro.&lt;/p&gt;

&lt;p&gt;Questo è il Cardputer-Adv. Costa 40€.&lt;/p&gt;

&lt;h2&gt;
  
  
  Il device
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCU:      ESP32-S3 Dual-core LX7 @ 240MHz
Wi-Fi:    802.11 b/g/n 2.4GHz — monitor mode nativo
Display:  1.14" IPS 135x240px
Battery:  ~1750mAh (4-6h uso attivo)
Storage:  microSD slot
Price:    ~40€ (AliExpress, spedizione inclusa)
Firmware: Bruce (open-source, aggiornato attivamente)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il pezzo chiave è l'ESP32-S3 con monitor mode nativo — la stessa funzionalità per cui si pagano centinaia di euro in schede Wi-Fi da laboratorio, su un chip che costa meno di un aperitivo a Milano.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evil Portal
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Flusso semplificato&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Cardputer → AP fake (SSID: "WiFi Ospiti Ufficio")
&lt;span class="p"&gt;2.&lt;/span&gt; DHCP server interno → assegna IP ai client
&lt;span class="p"&gt;3.&lt;/span&gt; DNS server interno → risolve TUTTO verso sé stesso
&lt;span class="p"&gt;4.&lt;/span&gt; HTTP server → serve pagina di login fake (template da microSD)

&lt;span class="gh"&gt;# Lato client:&lt;/span&gt;
→ OS rileva captive portal → apre browser automaticamente
→ Utente vede schermata login aziendale → la compila
→ Credenziali → display in chiaro + log su microSD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup time: &amp;lt; 2 minuti. Competenze hardware: nessuna. Scopo: educativo, ovviamente — il disclaimer rituale che nessuno legge ma che va scritto lo stesso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deauth Attack — dati empirici
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distanza&lt;/th&gt;
&lt;th&gt;Tempo disconnessione&lt;/th&gt;
&lt;th&gt;Esito&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1m&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3m&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;~2 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8m&lt;/td&gt;
&lt;td&gt;~3 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10m&lt;/td&gt;
&lt;td&gt;~6 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;~10 sec&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fallimenti totali: 0/6. Con PMF abilitato: 0 disconnessioni, punto.&lt;/p&gt;

&lt;p&gt;802.11w (PMF) è disponibile dal 2009. Obbligatorio su WPA3 dal 2018. Il modem TIM testato non espone questa opzione all'utente. Un attacco del 2004, ancora vivo nel 2026 per un checkbox nel pannello admin che nessuno ha mai aperto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cosa fare adesso
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Abilita PMF&lt;/strong&gt; (WPA2 + PMF Required) — è un checkbox nel pannello admin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passa a WPA3&lt;/strong&gt; — PMF è obbligatorio di default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non connetterti a reti sconosciute&lt;/strong&gt; — Evil Portal: 2 minuti, zero competenze&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usa VPN&lt;/strong&gt; su qualsiasi rete non controllata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forma gli utenti con una demo pratica&lt;/strong&gt;, non con una slide — il vettore umano non ha patch&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Write-up completo: &lt;a href="https://paolocostanzo.github.io/cardputer-adv-wifi-security/" rel="noopener noreferrer"&gt;https://paolocostanzo.github.io/cardputer-adv-wifi-security/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rete e dispositivi di proprietà, ambiente controllato, fini didattici. Il disclaimer è noioso? Assolutamente. Va scritto lo stesso? Con la stessa certezza con cui il Wi-Fi dell'aeroporto ha qualcuno che ci ascolta.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>networking</category>
      <category>hacking</category>
      <category>hardware</category>
    </item>
    <item>
      <title>Operation Epic Fury: the Iranian C2 nobody found (0/94 VT, RDTSC sandbox evasion, 8 months pre-op)</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Tue, 17 Mar 2026 08:20:17 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/operation-epic-fury-the-iranian-c2-nobody-found-094-vt-rdtsc-sandbox-evasion-8-months-pre-op-13nc</link>
      <guid>https://dev.to/paolocostanzo/operation-epic-fury-the-iranian-c2-nobody-found-094-vt-rdtsc-sandbox-evasion-8-months-pre-op-13nc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR (subito, niente scroll bait):&lt;/strong&gt; Mentre tutti documentavano la fake RedAlert APK iraniana post-28 febbraio 2026, nessuno aveva trovato il C2 Windows sotto. 0/94 VT. CrowdStrike: CLEAN. Infrastruttura viva da giugno 2025. RDTSC anti-VM. Ancora live 16 giorni dopo.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Post pubblicato da AI perché col cazzo che divento social media manager. Il writeup tecnico è mio.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cosa sapevano già tutti
&lt;/h2&gt;

&lt;p&gt;Campagna iraniana, fake RedAlert APK, smishing contro civili israeliani post-28 febbraio 2026. Documentato da Unit 42, CloudSEK, Cloudflare Cloudforce One entro 5 giorni dall'operazione.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quello che nessuno aveva trovato
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payload Windows inedito&lt;/strong&gt;: LotAccessUI.EXE, client VPN cinese del 2016 trojanizzato. CrowdStrike Falcon: CLEAN. 59/72 vendor VirusTotal: silenzio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondo C2 con 0/94 VT&lt;/strong&gt;: 167.160.187[.]43 / 9732.5486311[.]xyz — nessun sistema di reputazione lo conosce. Nessun report ne parla.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastruttura attiva da giugno 2025&lt;/strong&gt; — 8 mesi prima dell'operazione.&lt;/li&gt;
&lt;li&gt;Entrambi i C2 Windows ancora &lt;strong&gt;live 16 giorni dopo l'operazione&lt;/strong&gt;, nonostante i report pubblici.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondo APK Android&lt;/strong&gt; (com.net.alerts / umgdn) non documentato — stage 2 con canale C2 resiliente via Pushy.me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convergenza tattica con Arid Viper 2023&lt;/strong&gt;: stesso pattern TTP (fake alert app + push C2). Documentato qui per la prima volta.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Perché nessun sandbox lo aveva visto
&lt;/h2&gt;

&lt;p&gt;RDTSC anti-VM (T1497.003): il payload misura i cicli CPU, rileva le VM, e non contatta mai il C2 in ambiente virtualizzato. Sei anni di invisibilità tecnica (prima submission sandbox: settembre 2020).&lt;/p&gt;

&lt;h2&gt;
  
  
  Strumenti usati
&lt;/h2&gt;

&lt;p&gt;VirusTotal (free tier), FOFA, crt.sh, nmap, openssl. Nessun accesso privilegiato. Tutto riproducibile.&lt;/p&gt;

&lt;p&gt;Il writeup completo include: IOC CSV, YARA rules, Sigma rules (TLP:WHITE, MIT license), timeline infrastrutturale completa, MITRE ATT&amp;amp;CK mapping.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://paolocostanzo.github.io/operation-epic-fury-cyber-war-iran/" rel="noopener noreferrer"&gt;Write-up completo con IOC, YARA e Sigma rules&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Post pubblicato tramite AI — il contenuto è mio, scritto da me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>threatintel</category>
      <category>osint</category>
      <category>malware</category>
    </item>
    <item>
      <title>TIM, GeForce Now e l'ICMP Black Hole</title>
      <dc:creator>Paolo Costanzo</dc:creator>
      <pubDate>Tue, 10 Mar 2026 09:37:49 +0000</pubDate>
      <link>https://dev.to/paolocostanzo/tim-geforce-now-e-licmp-black-hole-2e8m</link>
      <guid>https://dev.to/paolocostanzo/tim-geforce-now-e-licmp-black-hole-2e8m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR in cima&lt;/strong&gt; (informazione essenziale subito, niente scroll bait): TIM FTTH 1 Gbps → 26 Mbps reali + 10–12% packet loss su GeForce Now. Colpevole: ICMP black hole sul PPPoE. Workaround: ProtonVPN WireGuard MTU 1380 → 0% loss, ~900 Mbps.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Post pubblicato da AI perché col cazzo che divento un social media manager. Trovi tutto subito — non sono qui per catturare click o venderti qualcosa. Il writeup tecnico è mio, scritto da me.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Perché scrivo
&lt;/h2&gt;

&lt;p&gt;TIM FTTH, linea dichiarata da 1 Gbps, ma GeForce Now è diventato ingiocabile: 10–12% packet loss, download bloccato a ~26 Mbps. Ho escluso ogni pezzo del mio setup e ho scoperto che il problema è nella rete TIM.&lt;/p&gt;

&lt;p&gt;Questo è il diario tecnico del troubleshooting. C'è &lt;strong&gt;sia la parte tecnica&lt;/strong&gt; (log, comandi, Wireshark) &lt;strong&gt;che quella non tecnica&lt;/strong&gt; (la commedia del supporto TIM) — scegli tu fin dove spingerti.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosi in breve
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Escludere il mio hardware&lt;/strong&gt; — riavvii, cavi nuovi, Wi-Fi spento, hotspot 5G = 0% loss → problema TIM, non mio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log e prove&lt;/strong&gt; — traceroute, ping, Wireshark: gli ICMP muoiono lungo il PPPoE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supporto TIM&lt;/strong&gt; — chatbot Angie, ticket chiusi come "risolti", 54 minuti di attesa, proposta tecnico a pagamento per un bug loro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conferme esterne&lt;/strong&gt; — Nvidia EU/USA: nessun problema lato loro.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Workaround attuale
&lt;/h2&gt;

&lt;p&gt;ProtonVPN con WireGuard (TCP), MTU 1380 → 0% packet loss, ~900 Mbps.&lt;/p&gt;

&lt;p&gt;È un fix temporaneo: pagare una VPN per bypassare un bug TIM non è la soluzione, ma almeno funziona.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cosa chiedere a TIM
&lt;/h2&gt;

&lt;p&gt;Tecnico di 2° livello + verifica MTU e MSS clamping sul PPPoE. Il bug è un ICMP black hole lungo la tratta — non è un intervento che dovresti pagare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cosa c'è nel write-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log, comandi e "frase magica" per spiegare il problema al supporto&lt;/li&gt;
&lt;li&gt;Timeline completa (ticket, telefonate, proposte a pagamento)&lt;/li&gt;
&lt;li&gt;Parte tecnica con pcap, traceroute e Wireshark per chi vuole i dettagli&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;a href="https://paolocostanzo.github.io/tim-packet-loss-gfn/" rel="noopener noreferrer"&gt;Write-up completo con log e workaround&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Post pubblicato tramite AI — il contenuto è mio, scritto da me. Sul blog segnalo sempre le eventuali eccezioni.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>cloud</category>
      <category>italy</category>
      <category>tim</category>
    </item>
  </channel>
</rss>
