<?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: Rocky</title>
    <description>The latest articles on DEV Community by Rocky (@rockyyy).</description>
    <link>https://dev.to/rockyyy</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%2F4026445%2F94b46dbb-5b45-421a-be13-61cb9779ec88.jpg</url>
      <title>DEV Community: Rocky</title>
      <link>https://dev.to/rockyyy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rockyyy"/>
    <language>en</language>
    <item>
      <title>Two PCs, Same Switch, Same VLAN, and They Still Can't Talk</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Mon, 14 Sep 2026 21:17:44 +0000</pubDate>
      <link>https://dev.to/rockyyy/two-pcs-same-switch-same-vlan-and-they-still-cant-talk-3g74</link>
      <guid>https://dev.to/rockyyy/two-pcs-same-switch-same-vlan-and-they-still-cant-talk-3g74</guid>
      <description>&lt;p&gt;The ticket says "intermittent," which is the worst word a ticket can say. Two desks over from each other, same switch, same VLAN, same physical run of cable back to the same closet. Workstation A can ping the printer, the file share, the domain controller, everything. Workstation A cannot reliably reach Workstation B, four feet away. Sometimes a ping gets through. A file transfer stalls. Nobody touched a cable. Nobody touched a firewall, because there isn't one between two machines on the same VLAN.&lt;/p&gt;

&lt;p&gt;You pull the IP config on both. Workstation A: &lt;code&gt;192.168.1.10&lt;/code&gt;, mask &lt;code&gt;255.255.255.0&lt;/code&gt;. Workstation B: &lt;code&gt;192.168.1.130&lt;/code&gt;, mask &lt;code&gt;255.255.255.128&lt;/code&gt;. Both plug into the same 192.168.1.0 address space. Both look, at a glance, fine.&lt;/p&gt;

&lt;p&gt;They are not on the same subnet. That's the whole bug, and it's easy to miss because a subnet mask never travels in the packet. It's not metadata attached to an IP address the way a VLAN tag is attached to a frame. It's a purely local, purely private number each device keeps to itself and uses for exactly one decision: when I want to send to this destination, do I ARP for it directly on this wire, or do I hand it to my default gateway instead?&lt;/p&gt;

&lt;p&gt;Workstation A ANDs its own mask against the destination. &lt;code&gt;192.168.1.130&lt;/code&gt; AND &lt;code&gt;255.255.255.0&lt;/code&gt; lands in &lt;code&gt;192.168.1.0/24&lt;/code&gt;, the same network A thinks it's on. So A treats B as local. It ARPs for &lt;code&gt;192.168.1.130&lt;/code&gt; right there on the segment, gets a MAC address back (ARP doesn't care about anyone's subnet math, it's a broadcast on the wire), and sends the frame straight there. That direction works.&lt;/p&gt;

&lt;p&gt;Workstation B does the same math with its own mask. &lt;code&gt;192.168.1.10&lt;/code&gt; AND &lt;code&gt;255.255.255.128&lt;/code&gt; does not land inside &lt;code&gt;192.168.1.128/25&lt;/code&gt;, the network B believes it's on. As far as B is concerned, A is off-subnet. So B doesn't ARP for A directly. It sends the packet to its configured default gateway instead, the same way it would for a request bound for the internet.&lt;/p&gt;

&lt;p&gt;Now the outcome depends entirely on what that gateway does with a packet addressed to a host sitting on the exact same wire it just arrived from. A lot of routers will happily route it right back out the interface it came in on, and per RFC 792 they're supposed to tell the sender it took an unnecessary hop with an ICMP redirect, "you didn't need me for this, talk to this host directly next time." Modern client OSes, for good security reasons, frequently ignore ICMP redirects by default. So B never learns to talk to A directly. Every packet from B takes the long way through the gateway, some fraction of it gets dropped, delayed, or filtered somewhere along that unnecessary detour, and you get exactly the symptom that started the ticket: not down, not blocked, just inconsistent, and only in one direction.&lt;/p&gt;

&lt;p&gt;Nothing here is exotic. It's the single most basic fact about how IP addressing works, that the mask is locally interpreted and never verified against the device on the other end, applied in a spot where two machines quietly ended up with different ones. Fixing it is one line, set B back to &lt;code&gt;/24&lt;/code&gt; to match the segment it's actually wired into. Finding it means checking both masks before you check anything else, because "same VLAN" and "same subnet" are two different claims and only one of them was actually true here.&lt;/p&gt;

&lt;p&gt;This is exactly the layer the Network Engineer L1 Book is built to make automatic instead of a thing you re-derive under pressure: subnetting, VLSM, and the local-vs-remote decision every device on your network is silently making, before routing, before switching, before any of the more advanced material stacks on top of it: &lt;a href="https://resources.codelivly.com/product/network-engineer-l1/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/network-engineer-l1/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free Routing &amp;amp; Switching Fundamentals and Network Services Administration learning paths cover this same beginner ground on codelivly.com if you want to drill it hands-on first: &lt;a href="https://codelivly.com/learning-paths/routing-switching-fundamentals" rel="noopener noreferrer"&gt;https://codelivly.com/learning-paths/routing-switching-fundamentals&lt;/a&gt; and &lt;a href="https://codelivly.com/learning-paths/network-services-administration" rel="noopener noreferrer"&gt;https://codelivly.com/learning-paths/network-services-administration&lt;/a&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>subnetting</category>
      <category>ccna</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>The 21st Router Is Where Full-Mesh iBGP Stops Being a Design</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Mon, 14 Sep 2026 17:55:32 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-21st-router-is-where-full-mesh-ibgp-stops-being-a-design-1g64</link>
      <guid>https://dev.to/rockyyy/the-21st-router-is-where-full-mesh-ibgp-stops-being-a-design-1g64</guid>
      <description>&lt;p&gt;The data center core has been running clean for two years. Twenty routers, full-mesh iBGP, every route from every rack reaching every other rack because every router peers directly with every other router. It has worked the whole time, which is exactly why nobody looked hard at it. Then procurement approves rack 21, and the ticket to bring its router online reads like a chore list: add a new iBGP session, on every single one of the twenty existing routers, before the new box can see a single route the others already know.&lt;/p&gt;

&lt;p&gt;That's not a one-line config change. That's twenty separate sessions, twenty separate config touches, on hardware that's been running in production without incident since the last expansion. The reason traces back to a rule that's easy to forget once a mesh is already built: an iBGP router will not re-advertise a route it learned from one iBGP peer to another iBGP peer. eBGP gets its loop protection for free, a router refuses a route that already has its own AS number in the AS-path. iBGP peers all share the same AS number, so that trick doesn't work between them, and the protocol's fallback is blunt: don't reflect iBGP-learned routes to other iBGP peers at all. The only way to guarantee every router still learns every route is for every router to peer with every other router directly. Full mesh.&lt;/p&gt;

&lt;p&gt;The math behind why this stops scaling is just n(n-1)/2. Ten routers need 45 sessions. Twenty need 190. Fifty need 1,225. Every router added to an existing full mesh needs one new session per router already in it, so growth isn't linear, it's a new peering table entry on every box you already trust not to need touching.&lt;/p&gt;

&lt;p&gt;Route reflectors are the fix, and they work by deliberately breaking the rule that caused the problem. RFC 4456 designates one or more routers in the AS as route reflectors. Everything else becomes a client, and clients peer only with the reflector, never with each other. The reflector is explicitly permitted to do what an ordinary iBGP router can't: take a route learned from one client and re-advertise, "reflect," it to the reflector's other clients and to any full-meshed non-client peers. A 20-router core with two reflectors and 18 clients needs roughly 36 sessions, not 190, and adding router 21 means peering it to the two reflectors, not to the other twenty.&lt;/p&gt;

&lt;p&gt;Breaking the no-reflect rule on purpose creates a new loop risk, so RFC 4456 adds two attributes to close it. CLUSTER_LIST works like AS-path, but for reflection instead of AS hops, every cluster a route passes through gets appended, and a reflector drops anything that already carries its own cluster ID. ORIGINATOR_ID records the router ID of whoever first put the route into iBGP, so if it somehow loops back around to its origin, that router recognizes and drops it. Production designs run at least two reflectors per cluster, same cluster ID, specifically so a single reflector going down doesn't strand every client behind it without a path to the rest of the AS.&lt;/p&gt;

&lt;p&gt;None of this is exotic, and none of it matters until the mesh you built for ten routers is the mesh you're still running at fifty, and the twenty-first router turns a five-minute config push into an afternoon of touching boxes nobody wanted to touch.&lt;/p&gt;

&lt;p&gt;This is exactly the depth the Network Engineer L3 Book is scoped for: advanced BGP, data-center architecture, the design decisions that only bite once a network has already grown past the size it was designed for. &lt;a href="https://resources.codelivly.com/product/network-engineer-l3/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/network-engineer-l3/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free Network Design and Architecture and WAN, VPN and Cloud Networking learning paths on codelivly.com cover the same senior-level architecture ground hands-on if you want to work through it before spending anything: &lt;a href="https://codelivly.com/learning-paths/network-design-and-architecture" rel="noopener noreferrer"&gt;https://codelivly.com/learning-paths/network-design-and-architecture&lt;/a&gt; and &lt;a href="https://codelivly.com/learning-paths/wan-vpn-and-cloud-networking" rel="noopener noreferrer"&gt;https://codelivly.com/learning-paths/wan-vpn-and-cloud-networking&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bgp</category>
      <category>networking</category>
      <category>datacenter</category>
      <category>ccnp</category>
    </item>
    <item>
      <title>The IDOR Test You Passed Was Only Testing One Path Into the Data</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:42:26 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-idor-test-you-passed-was-only-testing-one-path-into-the-data-50po</link>
      <guid>https://dev.to/rockyyy/the-idor-test-you-passed-was-only-testing-one-path-into-the-data-50po</guid>
      <description>&lt;p&gt;The API had already been through a round of testing. Someone tried the obvious thing first: change the ID in &lt;code&gt;GET /api/orders/1837&lt;/code&gt; to a number that belongs to another customer and see what comes back. It came back with a 403. Good, the team had wired in an object-level check on that endpoint, does the requesting user actually own this order, and it worked exactly as designed. Broken Object Level Authorization, OWASP's API1:2023, the single most common API vulnerability class, tested and blocked.&lt;/p&gt;

&lt;p&gt;Then someone pointed the same account at the GraphQL endpoint sitting next to that REST API, serving the same underlying data for the newer mobile client, and asked a different kind of question. Not "can I ask for someone else's order directly." Instead: &lt;code&gt;order(id: 1837) { customer { orders { id, total, items } } }&lt;/code&gt;, starting from an order the account genuinely owns, then walking sideways through the &lt;code&gt;customer&lt;/code&gt; relationship to that customer's full order list. The top-level &lt;code&gt;order&lt;/code&gt; resolver still ran its ownership check, id 1837 belongs to this account, request approved. But the &lt;code&gt;orders&lt;/code&gt; field hanging off &lt;code&gt;customer&lt;/code&gt; is a different resolver entirely, and nobody had wired an ownership check onto it, because nobody thought about it as a place data could leak from. It just walks the relationship and returns whatever it finds. Every order belonging to that customer came back. If that customer object connects to anything else, an organization, a linked account, a shared address book, the same trick usually keeps working one hop further.&lt;/p&gt;

&lt;p&gt;This is not a GraphQL bug in the sense of broken software. GraphQL is doing exactly what it's built to do: let a client traverse relationships between types instead of hitting one flat endpoint per resource. That's the entire value proposition. But it means "authorization" can't be a property of an endpoint anymore, because there often isn't a fixed set of endpoints, there's a schema, and any resolver that returns object data is a place someone can arrive at from more than one direction. A REST API gets its authorization boundary for free, roughly, because each URL only does one thing. A GraphQL API has to reimplement that boundary at every single resolver that touches sensitive data, deliberately, because the framework has no built-in notion of "this field needs the same ownership check as that other field." OWASP's own GraphQL guidance says this directly: authorization has to live in the business logic layer and be applied per resolver, not assumed from the shape of the schema or the fact that some other field already checked.&lt;/p&gt;

&lt;p&gt;What makes this worth specifically testing for, separate from ordinary IDOR testing, is that the team can do everything right at the entry point and still ship the hole. The top-level query was reviewed, the ownership check was code-reviewed, it might even have a test. The nested field three hops away that returns the same data through a different name almost never gets that same attention, because it doesn't look like "the orders endpoint," it looks like a field on a type. Testing it means picking every relationship in the schema that leads back to sensitive data and walking it from an account that shouldn't have access, not just re-testing the fields with the obvious names.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of authorization-boundary thinking the API Hacking Book is built to drill, BOLA, JWT and OAuth abuse, SSRF and GraphQL abuse specifically, across 40 chapters of hands-on labs instead of a single checklist item: &lt;a href="https://resources.codelivly.com/product/hacking-your-first-apis/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/hacking-your-first-apis/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free IDOR and JWT labs and the HelpHub IDOR / Broken Access Control CTF on codelivly.com are the same object-level-authorization muscle, hands-on, before you spend anything: &lt;a href="https://codelivly.com/labs/idor" rel="noopener noreferrer"&gt;https://codelivly.com/labs/idor&lt;/a&gt; and &lt;a href="https://codelivly.com/ctf/challenges/helphub-idor" rel="noopener noreferrer"&gt;https://codelivly.com/ctf/challenges/helphub-idor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>api</category>
      <category>appsec</category>
      <category>bugbounty</category>
    </item>
    <item>
      <title>Your IOC Matching Script Has Been Returning Zero Hits for a Reason You Didn't Test</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Mon, 14 Sep 2026 13:50:54 +0000</pubDate>
      <link>https://dev.to/rockyyy/your-ioc-matching-script-has-been-returning-zero-hits-for-a-reason-you-didnt-test-3e03</link>
      <guid>https://dev.to/rockyyy/your-ioc-matching-script-has-been-returning-zero-hits-for-a-reason-you-didnt-test-3e03</guid>
      <description>&lt;p&gt;def check_logs(iocs, log_lines):&lt;br&gt;
    hits = []&lt;br&gt;
    for ioc in iocs:&lt;br&gt;
        for line in log_lines:&lt;br&gt;
            if ioc in line:&lt;br&gt;
                hits.append((ioc, line))&lt;br&gt;
    return hits&lt;/p&gt;

</description>
      <category>python</category>
      <category>cybersecurity</category>
      <category>soc</category>
      <category>automation</category>
    </item>
    <item>
      <title>One OSPF Keyword and a Subnet Vanishes From the Routing Table</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Thu, 10 Sep 2026 20:19:07 +0000</pubDate>
      <link>https://dev.to/rockyyy/one-ospf-keyword-and-a-subnet-vanishes-from-the-routing-table-3l5o</link>
      <guid>https://dev.to/rockyyy/one-ospf-keyword-and-a-subnet-vanishes-from-the-routing-table-3l5o</guid>
      <description>&lt;p&gt;router ospf 1&lt;br&gt;
 area 10 stub&lt;/p&gt;

</description>
      <category>networking</category>
      <category>ospf</category>
      <category>ccnp</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>The Rename Field That Was Actually a Shell</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Thu, 10 Sep 2026 16:04:49 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-rename-field-that-was-actually-a-shell-nmm</link>
      <guid>https://dev.to/rockyyy/the-rename-field-that-was-actually-a-shell-nmm</guid>
      <description>&lt;p&gt;os.system(f"mv {upload_dir}/{old_name} {upload_dir}/{new_name}")&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cybersecurity</category>
      <category>appsec</category>
      <category>pentesting</category>
    </item>
    <item>
      <title>Two Switches, One Trunk, and a VLAN That Wasn't Where Anyone Thought</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Thu, 10 Sep 2026 12:41:09 +0000</pubDate>
      <link>https://dev.to/rockyyy/two-switches-one-trunk-and-a-vlan-that-wasnt-where-anyone-thought-3dgb</link>
      <guid>https://dev.to/rockyyy/two-switches-one-trunk-and-a-vlan-that-wasnt-where-anyone-thought-3dgb</guid>
      <description>&lt;p&gt;A new access switch goes into a third-floor wiring closet on a Friday afternoon. It gets patched into the trunk running back to the core, configured from the same template that's worked a dozen times before: trunk the uplink, allow the VLANs, done. By Monday there are three tickets. Phones on the voice VLAN are dropping calls mid-conversation. One desk can print but can't reach the file share. A laptop that should land on the data VLAN is pulling an IP from the guest subnet instead. Nobody touched the core switch. The new switch's config looks identical to every other access switch in the building.&lt;/p&gt;

&lt;p&gt;The thing the template didn't carry over is the native VLAN, and it's easy to see why: it's the one VLAN on a trunk link that doesn't get an 802.1Q tag. Every other VLAN crossing that trunk gets a 4-byte tag inserted into the Ethernet frame identifying which VLAN it belongs to, and the receiving switch reads that tag to know where the frame goes. The native VLAN is the exception, sent untagged, by design, mostly for backward compatibility with old equipment that never learned to speak 802.1Q. On Cisco gear the default native VLAN is VLAN 1 unless someone explicitly sets it elsewhere.&lt;/p&gt;

&lt;p&gt;Here's where the new switch goes wrong: its trunk port still has the native VLAN set to the factory default, VLAN 1, while the core switch's matching trunk port was configured months ago with the native VLAN set to VLAN 99, a dedicated management VLAN, standard practice specifically so VLAN 1 traffic doesn't sneak across trunks untagged. Two switches, same physical trunk, disagreeing about which VLAN travels untagged. When the new switch sends untagged frames, it means VLAN 1. When the core switch receives untagged frames on that trunk, it interprets them as VLAN 99, because that's what its own configuration says untagged means. The frame gets placed on the wrong VLAN the instant it crosses the link, no misconfigured cable, no bad port, nothing that shows up on a physical layer check.&lt;/p&gt;

&lt;p&gt;That's exactly the pattern behind the three tickets: traffic that should have stayed on one VLAN quietly reappearing on another, intermittently, depending on which frames happened to be untagged when they crossed that specific trunk. It's also precisely the kind of mismatch CDP is built to catch, if it's enabled: Cisco switches exchange native VLAN information in CDP advertisements, and a genuine mismatch triggers a syslog warning naming both VLANs and both ports. The fix is one line on the new switch's trunk configuration, matching its native VLAN to what the core switch already expects. The part worth remembering isn't the command, it's the instinct: a trunk isn't fully configured just because the VLANs are allowed across it. Two switches also have to agree on which one crosses without a tag, and that's a setting most templates silently assume rather than actually check.&lt;/p&gt;

&lt;p&gt;This is the kind of trunk-level detail that separates knowing VLANs exist from being able to diagnose them under time pressure, which is exactly the ground the CCNA 200-301 Study Guide covers with 41 hands-on labs, including trunk and VLAN troubleshooting scenarios built to produce exactly this failure mode so you learn to recognize it before it's a helpdesk ticket: &lt;a href="https://resources.codelivly.com/product/ccna-practical-the-hands-on-network-engineer-guide/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/ccna-practical-the-hands-on-network-engineer-guide/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free Routing &amp;amp; Switching Fundamentals path covers the OSI-model and VLAN groundwork this scenario builds on: &lt;a href="https://codelivly.com/learning-paths/routing-switching-fundamentals" rel="noopener noreferrer"&gt;https://codelivly.com/learning-paths/routing-switching-fundamentals&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ccna</category>
      <category>networking</category>
      <category>cybersecurity</category>
      <category>infosec</category>
    </item>
    <item>
      <title>The Root Cron Job That Was Already Giving You a Shell, If You Looked</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Thu, 10 Sep 2026 12:10:46 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-root-cron-job-that-was-already-giving-you-a-shell-if-you-looked-1n24</link>
      <guid>https://dev.to/rockyyy/the-root-cron-job-that-was-already-giving-you-a-shell-if-you-looked-1n24</guid>
      <description>&lt;p&gt;*/10 * * * * root cd /var/www/uploads &amp;amp;&amp;amp; tar czf /backup/uploads-$(date +\%s).tar.gz *&lt;/p&gt;

</description>
      <category>linux</category>
      <category>pentesting</category>
      <category>privesc</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>What a Malware Sample Tells You Before You Ever Run It</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Wed, 09 Sep 2026 20:17:05 +0000</pubDate>
      <link>https://dev.to/rockyyy/what-a-malware-sample-tells-you-before-you-ever-run-it-1hao</link>
      <guid>https://dev.to/rockyyy/what-a-malware-sample-tells-you-before-you-ever-run-it-1hao</guid>
      <description>&lt;p&gt;It's the kind of alert that shows up at the worst hour: an EDR flags an executable dropped by an email attachment, the sandbox queue already has six samples ahead of it, and someone upstream wants a verdict in five minutes, not forty. Detonating it is the right long-term answer. It is also not an option right now. So the question becomes: what can you actually learn from this file without running a single instruction of it?&lt;/p&gt;

&lt;p&gt;More than most people expect. A PE file's import table lists every external function the binary declared it might call, resolved by name at load time. You don't need to execute anything to read it, and it tells you what the program was built to be capable of, whether or not it ever gets the chance. Some combinations are worth recognizing on sight. VirtualAlloc, WriteProcessMemory and CreateRemoteThread showing up together is the classic process injection triad: allocate memory in another process, write code into it, start a thread there. WinHttpOpen or InternetOpenUrl next to URLDownloadToFile reads as a downloader. Heavy CryptAcquireContext and CryptEncrypt usage is worth flagging before you know anything else about the sample. None of this proves malicious intent by itself, plenty of legitimate software calls these same functions, but it narrows what you're looking at and what dynamic analysis should actually go check.&lt;/p&gt;

&lt;p&gt;The more interesting case is when the import table doesn't show you much at all: a handful of entries, mostly LoadLibraryA and GetProcAddress, nothing that looks like real functionality. That's not a boring binary. That's a binary resolving its real imports manually at runtime specifically so a static import table won't show them, which is standard packer and custom-loader behavior. The thinness of the table is itself the finding. Pair that with section entropy: legitimate native code sections tend to sit somewhere in the middle of the range, while a section pushing toward the high end of Shannon entropy per byte is almost always compressed or encrypted content, which is exactly what a packed payload looks like before it unpacks itself in memory. A binary with a sparse, uninformative import table and one section entropy reading way higher than the rest isn't ambiguous. It's a sample that is actively trying not to be read statically, and that itself is enough to justify spending real sandbox time on it instead of the five other things in the queue.&lt;/p&gt;

&lt;p&gt;This is the actual shape of triage under time pressure: read the header, read the imports, check entropy, and use those three things to decide what dynamic analysis needs to answer, rather than detonating everything with equal priority because nobody looked first. Static analysis doesn't replace dynamic analysis. It tells you which dynamic questions are worth asking, and in what order, when the queue is backed up and someone is waiting on an answer.&lt;/p&gt;

&lt;p&gt;That triage instinct, reading a PE file cold and knowing what the header, imports and entropy are actually telling you before committing to a sandbox run, is exactly what the Static and Dynamic Malware Analysis Guide builds from the ground up, lab setup included: &lt;a href="https://resources.codelivly.com/product/practical-malware-analysis-guide/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/practical-malware-analysis-guide/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>malware</category>
      <category>infosec</category>
      <category>reverseengineering</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The SSRF Everyone Scored as Low Because It 'Only Reaches Internal Stuff</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Wed, 09 Sep 2026 16:13:36 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-ssrf-everyone-scored-as-low-because-it-only-reaches-internal-stuff-582d</link>
      <guid>https://dev.to/rockyyy/the-ssrf-everyone-scored-as-low-because-it-only-reaches-internal-stuff-582d</guid>
      <description>&lt;p&gt;An SSRF turns up in an internal admin panel during a cloud engagement. The app fetches a URL parameter server-side, and the tester confirms it: point it at another host on the same VNet, get the response back. Severity gets scored low. The reasoning sounds right on the surface: this thing can't reach the internet, it can only touch other internal hosts, and there's nothing juicy on the internal network to pivot to anyway. Case closed, move to the next finding.&lt;/p&gt;

&lt;p&gt;Except "internal" on a cloud VM includes an address most people forget to think about: 169.254.169.254. That's the instance metadata service, a link-local address every major cloud provider exposes from inside a running instance, and it is very much reachable by anything that can make an internal HTTP request, including an SSRF that "only reaches internal stuff." On Azure specifically, that endpoint at /metadata/identity/oauth2/token hands back a live OAuth2 access token for whatever managed identity is attached to the VM, scoped to whatever Azure role assignments that identity holds. The one guardrail is a required Metadata: true header on the request. If the app is the one issuing the outbound call, not the tester's browser bouncing through a proxy, and the SSRF gives any way to set that header or the vulnerable code already sets it by default because that's how its own metadata calls work, the token comes back to whoever controls the SSRF, not to the app.&lt;/p&gt;

&lt;p&gt;That token is not a low-severity finding. It's a live credential scoped to real Azure RBAC role assignments, and those assignments are very often broader than they should be, because narrowing a managed identity down to a genuinely minimal custom role is more setup work than assigning Contributor at the resource group or subscription level and moving on. A token like that can mean reading storage account keys, enumerating other resources in the subscription, or in the worse cases making changes directly through the Azure Resource Manager API, all without ever touching a username or password.&lt;/p&gt;

&lt;p&gt;The mistake in the original triage wasn't technical, it was a scoping mistake: judging SSRF severity by what's reachable on the visible network instead of by what's reachable at all, and 169.254.169.254 doesn't show up on a network diagram. It shows up the moment someone remembers that cloud metadata services exist and checks for them specifically, every time, regardless of how "contained" the SSRF looks at first glance.&lt;/p&gt;

&lt;p&gt;Cloud IAM attacks like this, across AWS, Azure and GCP, are exactly what the Cloud Penetration Testing Book for Beginners walks through with more than 60 hands-on labs: &lt;a href="https://resources.codelivly.com/product/cloud-pentesting-l1/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/cloud-pentesting-l1/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloudsecurity</category>
      <category>pentesting</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The Log Parser That Passed Every Test And Still Missed The Attack</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:42:56 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-log-parser-that-passed-every-test-and-still-missed-the-attack-4h1n</link>
      <guid>https://dev.to/rockyyy/the-log-parser-that-passed-every-test-and-still-missed-the-attack-4h1n</guid>
      <description>&lt;p&gt;A SOC analyst gets pulled into a post-incident review for something that should have triggered days earlier: a repeated failed-login pattern against a service account, exactly the kind their detection rule is built to catch. The rule didn't fire. Not because the logic was wrong, because the events it needed were never in the parsed data to begin with. Somewhere between the vendor's log format and the alert, a batch of entries just disappeared, and nobody noticed because the pipeline kept reporting success the whole time.&lt;/p&gt;

&lt;p&gt;The parser had been running fine for months. It read each log line, split it on commas, and pulled fields by position: line.split(",")[3] for the source IP, [7] for the outcome. That works exactly as long as the vendor's format never changes, and eventually it did: an update added one new field near the front of the line, and every position after it shifted by one. The IP field started returning a timestamp. The outcome field started returning something that was never a valid value, so a broad except: pass around the parse step caught it, moved on, and left no trace that anything had gone wrong.&lt;/p&gt;

&lt;p&gt;Fixed-position parsing is the actual root cause here, not the vendor's update, because any format change breaks it silently, where a parser built around field names doesn't care what order they arrive in. JSON logs should be parsed as JSON, not string-split. Delimited logs without a schema should go through named capture groups, not index positions, so a shifted field produces a clear mismatch instead of a plausible-looking wrong value. And the except block matters as much as the parsing itself: catching narrowly and counting failures turns a silent data loss into a visible metric, and a parse-failure rate that spikes above baseline is a detection signal worth alerting on in its own right, not a nuisance to suppress with a broader catch.&lt;/p&gt;

&lt;p&gt;None of this is exotic. It's the difference between a script that looks like it's working and one that's actually still parsing what it thinks it's parsing six months and one vendor update later. Python for Cybersecurity Automation Book: SOC Tools works through building that kind of production-grade log parser and threat-intel pipeline properly the first time, not after a review turns up a gap nobody meant to leave: &lt;a href="https://resources.codelivly.com/product/python-for-cybersecurity-automation/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/python-for-cybersecurity-automation/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>soc</category>
      <category>blueteam</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The 'Least Privilege' Policy That Wasn't</title>
      <dc:creator>Rocky</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:16:31 +0000</pubDate>
      <link>https://dev.to/rockyyy/the-least-privilege-policy-that-wasnt-521n</link>
      <guid>https://dev.to/rockyyy/the-least-privilege-policy-that-wasnt-521n</guid>
      <description>&lt;p&gt;A cloud security engineer running a quarterly access review pulls up a role tied to a backup Lambda function. The policy looks right at first glance: two actions, s3:GetObject and s3:PutObject, nothing broader. Someone clearly tried to do this properly. Then the Resource line reads "*", and the review stops being routine. That role can touch every object in every bucket in the account, not the one backup bucket it was written for. Nobody meant for that to be true. The policy just never said otherwise.&lt;/p&gt;

&lt;p&gt;IAM doesn't grant anything by default. Every request starts denied, and it stays denied unless something explicitly allows it: an identity policy, a resource policy, or both. Scoping actions without scoping resources is the most common way "least privilege" quietly isn't: the action list narrows what the role can do, but a wildcard resource means it can do it everywhere that action exists. The fix is mechanical once you know to look for it: resource ARNs scoped as specifically as the actions sitting next to them, not the other way around.&lt;/p&gt;

&lt;p&gt;The part that actually matters during an incident is what happens when two policies disagree. An explicit Deny beats every Allow in the evaluation, no matter which policy it's written in and no matter how permissive the other one is: an identity policy granting AdministratorAccess loses to a single explicit Deny sitting in a permissions boundary or a service control policy. That's not a technicality, it's the one lever that actually works as a hard backstop. If there's an action or a resource prefix that must never be reachable no matter what some future policy grants, an explicit Deny is the only thing that survives a mistake made later by someone who didn't know it existed. A permissions boundary uses that same mechanic proactively: it doesn't grant a single permission on its own, it just caps what an identity policy is allowed to grant, which is exactly what stops a wildcard resource like the one above from ever mattering in production if the boundary was set correctly when the role was created.&lt;/p&gt;

&lt;p&gt;None of this shows up until someone goes looking, which is what makes access reviews the place mistakes like this actually get caught instead of exploited. Knowing to check the resource field, not just the action list, and knowing that Deny always wins, is the difference between a review that rubber-stamps a policy because the action names sound right and one that catches the gap before someone else finds it first. The Cloud Security Engineer Handbook works through IAM policy evaluation, permissions boundaries and incident response across both AWS and Azure end to end, which is the actual depth behind catching this kind of thing on sight instead of after it's been used: &lt;a href="https://resources.codelivly.com/product/the-cloud-security-engineers-handbook/" rel="noopener noreferrer"&gt;https://resources.codelivly.com/product/the-cloud-security-engineers-handbook/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>cloudsecurity</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
