<?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: HACKBOX</title>
    <description>The latest articles on DEV Community by HACKBOX (@hackbox).</description>
    <link>https://dev.to/hackbox</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%2F4197222%2F78287e11-230c-4da3-a1b1-5a5679150119.png</url>
      <title>DEV Community: HACKBOX</title>
      <link>https://dev.to/hackbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hackbox"/>
    <language>en</language>
    <item>
      <title>Broken Access Control Tops the OWASP List. Your Scanner Still Can't See It.</title>
      <dc:creator>HACKBOX</dc:creator>
      <pubDate>Wed, 04 Nov 2026 07:15:32 +0000</pubDate>
      <link>https://dev.to/hackbox/broken-access-control-tops-the-owasp-list-your-scanner-still-cant-see-it-4412</link>
      <guid>https://dev.to/hackbox/broken-access-control-tops-the-owasp-list-your-scanner-still-cant-see-it-4412</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; the author works at HACKBOX PTE. LTD., which builds HackBox — referenced once below, in the section on autonomous decision-making. Everything else here is a technical argument you can test yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Broken access control has held the top spot in the &lt;a href="https://owasp.org/Top10/2025/" rel="noopener noreferrer"&gt;OWASP Top 10&lt;/a&gt; since 2021, and it kept it in the 2025 edition — OWASP's own data puts it in &lt;strong&gt;3.73%&lt;/strong&gt; of the applications it tested. As of 2026 it is still the vulnerability class that automated testing reports least often. That combination is not bad luck — it is a property of how payload-based testing works, and it is the first thing to check when you evaluate whether a tool is actually testing your application or just checking its surface.&lt;/p&gt;

&lt;p&gt;Here is the version of the bug that shows up in almost every web app I have seen tested:&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="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/api/invoices/1043&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
&lt;span class="na"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;session=eyJhbGciOi...   # account 1043's own session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now change one digit:&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="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/api/invoices/1044&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
&lt;span class="na"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;session=eyJhbGciOi...   # still account 1043's session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1044&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8871&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"billing_address"&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="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4820&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a cross-tenant data leak, and nothing in the request is malformed. The session is valid, the path is a real endpoint, the method is correct, there is no injection string, no traversal, no anomalous header. A WAF sees a well-formed GET with a legitimate cookie and lets it through, because at the protocol level it is indistinguishable from normal traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why matchers can't see it
&lt;/h2&gt;

&lt;p&gt;Most automated detection is built on one loop: send a request, compare the response against an expected signature, record a match. That loop is fast and it works well for a large class of bugs — the ones that announce themselves.&lt;/p&gt;

&lt;p&gt;Access control violations don't announce themselves. Catching one requires three operations a matcher cannot perform:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build an identity model.&lt;/strong&gt; Know which account owns resource 1044, and which resources account 1043 is entitled to reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross the boundary on purpose.&lt;/strong&gt; Request a resource outside that entitlement using a &lt;em&gt;valid&lt;/em&gt; session — not a crafted payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge the result semantically.&lt;/strong&gt; Decide whether the response body constitutes a violation. A 200 with a JSON object is not automatically a leak; a 403 is not automatically a control working.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# roughly what step 3 has to do, and why a signature cannot do it
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner_map&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;          &lt;span class="c1"&gt;# not evidence either way
&lt;/span&gt;    &lt;span class="n"&gt;actual_owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;VIOLATION&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;actual_owner&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hard part isn't the comparison — it's having &lt;code&gt;owner_map&lt;/code&gt; at all. That map is application-specific. It comes from crawling, from object-ID patterns, from watching what a user can legitimately reach. A scanner that only knows "there is an &lt;code&gt;id&lt;/code&gt; parameter here" has nothing to compare against, so it reports "possible IDOR" and moves on. At scale, that note is indistinguishable from noise, which is why it ends up unfixed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://owasp.org/www-project-web-security-testing-guide/" rel="noopener noreferrer"&gt;OWASP Web Security Testing Guide&lt;/a&gt; documents the manual version of this workflow in detail. Knowing the steps was never the bottleneck. Executing them across thousands of objects, again after every deploy, is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when the engine picks the next step itself
&lt;/h2&gt;

&lt;p&gt;The interesting shift in AI-assisted testing is not throughput. It is that the engine stops executing a branch structure written in advance and chooses its next action at run time from what it has already observed.&lt;/p&gt;

&lt;p&gt;Concretely, that changes three things you can test for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity-aware probing.&lt;/strong&gt; If the engine can hold a model of "account A can reach X, account B can reach Y", it can generate the boundary-crossing requests itself instead of waiting for a human to notice the candidate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iterative exploitation.&lt;/strong&gt; A matcher tests an endpoint and closes the case. An engine that decides its own path treats a confirmed finding as a new starting position: what is reachable from this session now, what does that access unlock, is there an object I can reach that I could not reach a minute ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lateral movement.&lt;/strong&gt; Nobody remembers the first finding. They remember the fourth, which they could only reach because of the third, on a machine that had no business being reachable from the internet at all. Staying in the environment after the first hit is what makes a report say "this server reaches the billing database" instead of "this server has a flaw".&lt;/p&gt;

&lt;p&gt;That last capability is the one that matters for access control, and it is the design HackBox is built around: an engine that carries a model of what it has already reached, instead of closing a ticket when a signature matches. To be clear about scope — this is a property of how the engine is built, not a claim that autonomous testing finds every authorisation bug. It finds the ones whose boundary can be inferred from observed behaviour, which is most object-level IDOR and far from all business-logic abuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification is the part that decides whether anyone fixes it
&lt;/h2&gt;

&lt;p&gt;Every team has received a report with 300 findings where 40 were real. The usual response is not to fix the 40 — it is to stop reading the reports.&lt;/p&gt;

&lt;p&gt;Verification is a different operation from detection: send the payload, confirm the effect actually occurred, keep the evidence. In the example above, verification is not "the response had 200 and a JSON body". It is: the response contained &lt;code&gt;account_id: 8871&lt;/code&gt; while the session belonged to &lt;code&gt;1043&lt;/code&gt;, and here is the request, the response, and the field that proves it.&lt;/p&gt;

&lt;p&gt;Findings that carry evidence fall into three states, and the third one matters more than most tools admit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What to do with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Confirmed in this environment&lt;/td&gt;
&lt;td&gt;Schedule normally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Exploited&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exploit succeeded, impact demonstrated&lt;/td&gt;
&lt;td&gt;Treat as urgent — impact is proven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Manual Collaboration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verification passed, exploitation evidence unclear&lt;/td&gt;
&lt;td&gt;Engineer review before spending budget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A tool with no third state is optimizing for a clean dashboard rather than for accuracy. When you evaluate anything in this category, ask how many findings it &lt;em&gt;refused&lt;/em&gt; to confirm.&lt;/p&gt;

&lt;p&gt;Vendors in this category tend to quote a false positive rate around 5% or below, self-reported. Treat any such number as a claim and test it against an application you already know well.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 20-minute evaluation you can run yourself
&lt;/h2&gt;

&lt;p&gt;If you are comparing tools, skip the feature matrix and run this against a staging app with two accounts at different privilege levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Two-account object test.&lt;/strong&gt; Log in as A, request an object owned by B. Does the tool attempt it without being pointed at the endpoint?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-finding behavior.&lt;/strong&gt; After a confirmed hit on a host, does it continue — enumerate what the new access reaches — or does it write the row and move on?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence check.&lt;/strong&gt; Open one finding. Do you get payload, request, response, and the extracted proof? Or a severity badge?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncertainty routing.&lt;/strong&gt; Ask how many findings were downgraded to human review in the last 30 days. Zero is a warning, not a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal reach.&lt;/strong&gt; Can it get to a host with no internet route? Cloud-only testing cannot see your internal database, whatever the dashboard says.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can automated testing find IDOR at all?&lt;/strong&gt;&lt;br&gt;
Yes — when the engine reasons about identity and ownership instead of matching payloads. It needs to know who owns what object, attempt the cross-tenant request with a valid session, and judge whether the response is a violation. That is a different operation from signature scanning, and it is the cleanest way to tell the two apart in a demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this replace a human pentest?&lt;/strong&gt;&lt;br&gt;
No. It replaces the repeatable part — reconnaissance, verification, regression after each deploy — and does it far more often than a human engagement can. Business logic flaws, chained authorization bugs across multiple services, social engineering, and bespoke exploitation still need a person. Any vendor claiming otherwise has not run a real engagement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How often should it run?&lt;/strong&gt;&lt;br&gt;
Tied to deployment events, not to a calendar. A quarterly cadence reproduces the same gap annual testing already has.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure, again, because it belongs at the end too: the author works at HACKBOX PTE. LTD., a cybersecurity company headquartered in Singapore, which builds HackBox — an AI-powered automated penetration testing SaaS platform that runs the full attack chain (reconnaissance, exploitation, privilege escalation) against a target and returns a verified report. It is sold either by confirmed result or by IP subscription.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Website: &lt;a href="https://www.hackbox.ai" rel="noopener noreferrer"&gt;https://www.hackbox.ai&lt;/a&gt; · Contact: &lt;a href="mailto:partners@hackbox.ai"&gt;partners@hackbox.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>ai</category>
      <category>owasp</category>
    </item>
  </channel>
</rss>
