<?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: Ali Haider</title>
    <description>The latest articles on DEV Community by Ali Haider (@alihaider_dev).</description>
    <link>https://dev.to/alihaider_dev</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%2F4158670%2Fa534ff3d-7271-42d4-ad36-518f51f4a36c.png</url>
      <title>DEV Community: Ali Haider</title>
      <link>https://dev.to/alihaider_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alihaider_dev"/>
    <language>en</language>
    <item>
      <title>The Server That Robbed Its Own Bank</title>
      <dc:creator>Ali Haider</dc:creator>
      <pubDate>Sun, 11 Oct 2026 20:06:52 +0000</pubDate>
      <link>https://dev.to/alihaider_dev/the-server-that-robbed-its-own-bank-58f9</link>
      <guid>https://dev.to/alihaider_dev/the-server-that-robbed-its-own-bank-58f9</guid>
      <description>&lt;p&gt;In 2019, a single request stole the personal data of over 100 million people from one of the biggest banks in America.&lt;/p&gt;

&lt;p&gt;No malware. No stolen password. No one kicked down a door. The attacker just asked a server a question, and the server, trying to be helpful, answered with the keys to the vault.&lt;/p&gt;

&lt;p&gt;The bug behind it is called &lt;strong&gt;SSRF&lt;/strong&gt;, and once you see how it works, you start noticing the setup for it everywhere. I spent this week learning it hands-on, and I want to walk you through it the way it finally made sense to me, with that real breach as the destination.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, Forget "Breaking In"
&lt;/h2&gt;

&lt;p&gt;When most people imagine a server getting hacked, they picture someone forcing their way inside. SSRF is the opposite of that, and that's exactly why it's so easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You never get in. You convince the server to go somewhere for you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the picture that made it click. Hold onto it, because we're coming back to it.&lt;/p&gt;

&lt;p&gt;You're standing outside a corporate building. Locked doors. Security at the entrance. Inside, down some hallway you'll never walk, there's a back office full of sensitive files. You want those files. You are not getting past that front desk.&lt;/p&gt;

&lt;p&gt;But there's a mailroom clerk inside. He walks the whole building all day. Nobody questions him, he's obviously staff. So you stop him on his smoke break and hand him a note: "Run to the back office, grab the file on the desk, bring it out to me."&lt;/p&gt;

&lt;p&gt;And he does. Because he can go where you can't, and because nobody inside thinks to check whether &lt;em&gt;his&lt;/em&gt; errands are legit.&lt;/p&gt;

&lt;p&gt;You never entered. You broke nothing. You just borrowed someone with better access than you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is the entire idea of SSRF. The server is the clerk.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Now the Technical Version
&lt;/h2&gt;

&lt;p&gt;Say a shopping site has a "check stock" button. Behind the scenes, when you click it, the server reaches out to an internal inventory service to get the number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /product/stock
stockApi=http://stock.internal-shop.net/check?productId=6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that &lt;code&gt;stockApi&lt;/code&gt; value? The server takes that URL and &lt;em&gt;fetches it for you&lt;/em&gt;. The developer assumed it would only ever point at their own inventory service.&lt;/p&gt;

&lt;p&gt;But what if you change it?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stockApi=http://localhost/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you've handed the clerk a different note. Instead of fetching inventory, the server fetches its own admin panel, a page you could never load yourself from the outside, and hands you back the contents.&lt;/p&gt;

&lt;p&gt;That's the whole move. You didn't attack the admin panel. You asked the server to go read it for you, and it did, because the request came from inside the house.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part That Turns "Annoying" Into "Catastrophic"
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't expect. Why does that admin panel even open up for a request from localhost? Shouldn't it still require a login?&lt;/p&gt;

&lt;p&gt;Often, no. And the reason is a quiet assumption baked into how internal systems get built:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Only trusted internal services can reach this machine. So why bother locking it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internal admin panels, dashboards, and APIs are frequently left wide open. No password. Default settings. Old, unpatched software. Because everyone assumed the network itself was the wall, nobody untrusted could ever reach these things, so why waste effort hardening them.&lt;/p&gt;

&lt;p&gt;SSRF knocks the wall down. The second you can make an outward-facing server pass notes inward, every one of those unlocked internal doors is suddenly reachable. And they were built on the belief that this day would never come.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Case Study: How a Bank Lost 100 Million Records
&lt;/h2&gt;

&lt;p&gt;Now we can walk through the real one, and you'll recognize every step.&lt;/p&gt;

&lt;p&gt;In 2019, a major US bank ran its infrastructure in the cloud. Like a lot of companies, they had a web application firewall sitting in front of their servers to filter bad traffic.&lt;/p&gt;

&lt;p&gt;That firewall had an SSRF vulnerability. An attacker could make &lt;em&gt;it&lt;/em&gt; send requests on their behalf. The clerk, in other words.&lt;/p&gt;

&lt;p&gt;Now, here's the cloud-specific twist. Servers running in the cloud have a special internal address, the same one on nearly every cloud instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://169.254.169.254/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;metadata service&lt;/strong&gt;. It's meant for the server to ask questions about itself. And one of the things it will happily tell anyone who asks from the right place is: the server's own access credentials. The temporary keys that let that machine talk to the rest of the company's cloud account.&lt;/p&gt;

&lt;p&gt;You can see where this goes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The attacker found the SSRF in the firewall.&lt;/strong&gt; (Found a clerk.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They made it request &lt;code&gt;http://169.254.169.254/...&lt;/code&gt;.&lt;/strong&gt; (Handed over the note.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The metadata service, seeing a request from a legitimate internal server, returned that server's cloud credentials.&lt;/strong&gt; (The clerk fetched the file.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With those credentials, the attacker reached into the cloud storage and pulled out the data of over 100 million people.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No password was cracked. No one phished an employee. The system worked exactly as designed. It just trusted the wrong request, and that trust was the whole vulnerability.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Is Not an Old, Solved Problem
&lt;/h2&gt;

&lt;p&gt;I came into security assuming bugs like this were mostly legacy, patched out of modern frameworks years ago. SSRF is the opposite. It's arguably &lt;em&gt;more&lt;/em&gt; dangerous today than ever, and it's because of how we build things now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microservices&lt;/strong&gt; mean an app is dozens of small internal services calling each other over HTTP. Every one of those internal calls is a door the clerk can be sent to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cloud&lt;/strong&gt; means that &lt;code&gt;169.254.169.254&lt;/code&gt; metadata endpoint is sitting on nearly every server you deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Fetch this URL" features&lt;/strong&gt; are everywhere now: webhooks, link previews, "import from URL", avatar fetchers, PDF generators. Every single one is a potential clerk waiting for a note.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flaw didn't disappear. It moved into the architecture we all use now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I'm Personally Obsessed With This One
&lt;/h2&gt;

&lt;p&gt;I'm working toward AI and LLM security, and SSRF turned out to be the most important thing I could have learned for it.&lt;/p&gt;

&lt;p&gt;Think about what an AI agent actually does all day. It fetches URLs. It calls tools and APIs. It retrieves documents on your behalf. That is the mailroom clerk, rebuilt in software and handed a lot more autonomy.&lt;/p&gt;

&lt;p&gt;So the modern version of this attack writes itself: instead of changing a &lt;code&gt;stockApi&lt;/code&gt; parameter, you plant an instruction in text the model will read, "go fetch this internal address", and the agent, trusted inside the network, does it. Same bug. Same clerk. The note is just written in English now instead of a URL field.&lt;/p&gt;

&lt;p&gt;Every technique I picked up chasing these labs, reaching localhost, hitting the metadata endpoint, slipping past filters, points straight at how you attack and defend AI agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Do You Actually Do About It?
&lt;/h2&gt;

&lt;p&gt;If you build anything that fetches a URL on the server side, three habits shut most of this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allowlist, don't blacklist.&lt;/strong&gt; Decide the short list of places your server is &lt;em&gt;allowed&lt;/em&gt; to fetch, and reject everything else. Trying to block all the bad addresses is a losing game (that's a whole post on its own).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate where the URL actually resolves,&lt;/strong&gt; not just how it's spelled. Block private IP ranges and that metadata address, and re-check after any redirect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hand the raw fetched response back to the user,&lt;/strong&gt; and use the cloud's hardened metadata mode (&lt;strong&gt;IMDSv2&lt;/strong&gt;) so a naive request can't just walk away with credentials.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;SSRF isn't about breaking in. It's about noticing that the server you're talking to has a trusted seat at a table you weren't invited to, and realizing you can get it to pass notes for you.&lt;/p&gt;

&lt;p&gt;Once that clicks, you can't unsee it. Every "the server fetches this for you" feature starts to look like a clerk waiting for the wrong instructions.&lt;/p&gt;

&lt;p&gt;I'm learning this stuff in public on the way to AI security. If a different analogy or breach finally made SSRF make sense for you, I'd genuinely like to hear it in the comments.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>cybersecurity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>When Your Own Server Becomes the Attacker</title>
      <dc:creator>Ali Haider</dc:creator>
      <pubDate>Sat, 03 Oct 2026 00:17:53 +0000</pubDate>
      <link>https://dev.to/alihaider_dev/when-your-own-server-becomes-the-attacker-jgb</link>
      <guid>https://dev.to/alihaider_dev/when-your-own-server-becomes-the-attacker-jgb</guid>
      <description>&lt;p&gt;In March 2019, a former Amazon engineer made a request to a Capital One server. Not a login, not a password guess. She asked the server to fetch a URL for her.&lt;/p&gt;

&lt;p&gt;The server said yes.&lt;/p&gt;

&lt;p&gt;That one request walked out with temporary AWS credentials, and those credentials unlocked the personal data of &lt;strong&gt;more than 100 million people&lt;/strong&gt;. Social security numbers, bank account details, credit applications. One of the largest financial breaches in US history, and at the bottom of it was a bug so ordinary that you have probably written the ingredients for it yourself.&lt;/p&gt;

&lt;p&gt;It's called &lt;strong&gt;SSRF&lt;/strong&gt;, and I want to show you exactly how something this mundane turns into something this catastrophic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole idea in one line
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SSRF (Server-Side Request Forgery)&lt;/strong&gt; is when you trick a server into making a request &lt;em&gt;for you&lt;/em&gt;, to somewhere you could never reach on your own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. You don't break in. You get the server to walk in for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think about who you trust
&lt;/h2&gt;

&lt;p&gt;Picture a secure office. You're a stranger in the lobby. The server room, the records vault, the executive offices, all locked, all off-limits. Security won't let you past the front desk.&lt;/p&gt;

&lt;p&gt;But the receptionist runs errands. &lt;em&gt;"Could you grab the file from room 237?"&lt;/em&gt; Sure. They walk back, fetch it, hand it over.&lt;/p&gt;

&lt;p&gt;So you try your luck: &lt;em&gt;"Could you bring me whatever's in the server room?"&lt;/em&gt; And because the receptionist has access, and you asked politely, they do.&lt;/p&gt;

&lt;p&gt;You never touched the lock. The receptionist did the walking. &lt;strong&gt;That receptionist is your server&lt;/strong&gt;, and SSRF is the art of asking it to fetch things it shouldn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature that becomes the bug
&lt;/h2&gt;

&lt;p&gt;Here's what makes SSRF sneaky: it almost never looks like a vulnerability. It looks like a &lt;strong&gt;feature request&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Let users add a profile picture by URL."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Check stock levels from the warehouse's internal API."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Generate a PDF preview of any link."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Fire a webhook to the customer's endpoint."&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those ends up as some version of this:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userControlledUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer who wrote that line was picturing a normal URL. An image. A warehouse API. A customer's webhook. What they weren't picturing was someone typing:&lt;br&gt;
&lt;code&gt;http://localhost/admin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And the server, having no opinion about where it's pointed, fetches its own admin panel, the one that's only supposed to be reachable from inside, and hands the attacker the response. The firewall guarding that panel never even got a say, &lt;strong&gt;because the request came from inside the house.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When the server starts scanning for you
&lt;/h2&gt;

&lt;p&gt;Reaching the admin panel is just the opening move. The deeper problem is that the server lives inside a &lt;strong&gt;private network you can't see&lt;/strong&gt; from the outside.&lt;/p&gt;

&lt;p&gt;So you make it look around.&lt;/p&gt;

&lt;p&gt;Point it at &lt;code&gt;192.168.0.1&lt;/code&gt;, then &lt;code&gt;.2&lt;/code&gt;, then &lt;code&gt;.3&lt;/code&gt;, and watch how the responses differ. A connection that refuses instantly means nothing's there. One that hangs, or answers, means something is. Walk the whole range and the server quietly &lt;strong&gt;maps out the internal network for you&lt;/strong&gt;, which machines exist, which ports are open, where the interesting things live.&lt;/p&gt;

&lt;p&gt;You've turned the victim's own server into a scanner aimed at the victim's own network.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that ended Capital One
&lt;/h2&gt;

&lt;p&gt;Now the finale, the move that turns &lt;em&gt;"interesting bug"&lt;/em&gt; into &lt;em&gt;"front-page breach."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every server running on &lt;strong&gt;AWS, Google Cloud, or Azure&lt;/strong&gt; can reach a special internal address: &lt;code&gt;169.254.169.254&lt;/code&gt;. It's the &lt;strong&gt;metadata service&lt;/strong&gt;, a little endpoint the machine uses to learn about itself. Handy for the server. Devastating in the wrong hands, because on a misconfigured instance it will also hand over the server's &lt;strong&gt;temporary cloud credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the attacker points the SSRF here:&lt;br&gt;
&lt;code&gt;http://169.254.169.254/latest/meta-data/iam/security-credentials/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and the server, asked politely, returns its own keys to the kingdom. With those, you're no longer poking at one app. &lt;strong&gt;You're inside the cloud account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the whole Capital One story in one request: a URL field that trusted its input, a server that could reach the metadata endpoint, and 100 million records out the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to not be the next case study
&lt;/h2&gt;

&lt;p&gt;This is the part most write-ups skip, so here's the version I'd actually want on a code review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Allowlist, don't blocklist.&lt;/strong&gt;&lt;br&gt;
The instinct is to block the bad addresses. Don't. Attackers will always find a spelling you didn't think of: &lt;code&gt;127.0.0.1&lt;/code&gt; can be written as &lt;code&gt;0x7f000001&lt;/code&gt;, or &lt;code&gt;2130706433&lt;/code&gt;, or hidden behind a domain that quietly resolves to an internal IP. Blocklists leak. Instead, decide the exact destinations your feature is allowed to hit, and refuse everything else. If the stock checker only ever needs one internal API, that's the only thing it should be able to reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check the resolved IP, not the string.&lt;/strong&gt;&lt;br&gt;
If you must allow user URLs, resolve the hostname first and inspect the actual IP it points to, then block the private ranges (&lt;code&gt;127.x&lt;/code&gt;, &lt;code&gt;169.254.x&lt;/code&gt;, &lt;code&gt;10.x&lt;/code&gt;, &lt;code&gt;192.168.x&lt;/code&gt;, &lt;code&gt;172.16–31.x&lt;/code&gt;). A domain like &lt;code&gt;my-innocent-site.com&lt;/code&gt; can resolve straight to &lt;code&gt;169.254.169.254&lt;/code&gt; if the attacker owns the DNS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Kill the exotic schemes.&lt;/strong&gt;&lt;br&gt;
Allow &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt;, nothing else. &lt;code&gt;file://&lt;/code&gt; reads local files. &lt;code&gt;gopher://&lt;/code&gt; can forge raw requests to internal services like Redis. If your feature doesn't need them, they're pure downside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. On AWS, enforce IMDSv2.&lt;/strong&gt;&lt;br&gt;
The newer metadata service requires a session token before it hands anything over, which shuts down the exact move that hit Capital One. It's close to a one-setting fix for the worst version of this bug. Turn it on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Assume one layer fails.&lt;/strong&gt;&lt;br&gt;
The app server shouldn't be able to reach your sensitive internal systems in the first place. Segment the network so that even a successful SSRF runs into a wall instead of a vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  One question before you close this tab
&lt;/h2&gt;

&lt;p&gt;Go find the place in your codebase where the server fetches a URL it didn't fully choose. The image importer, the webhook, the link preview, the PDF renderer. There's almost always one.&lt;/p&gt;

&lt;p&gt;Now ask it two things: &lt;strong&gt;is the destination allowlisted, and can it reach &lt;code&gt;169.254.169.254&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you don't like the answers, you've just found the same bug that cost Capital One 100 million records, sitting quietly in your own repo, still looking like a feature.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>cybersecurity</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
