DEV Community

Cover image for The Security Bugs That Look Like Successful Features
Sonia Bobrik
Sonia Bobrik

Posted on

The Security Bugs That Look Like Successful Features

A customer applies a welcome discount twice, receives a refund without losing the credited reward, or opens several sessions to claim the same limited benefit. Nothing crashes. The API returns a normal response, monitoring stays quiet, and every function appears to behave correctly. Yet the idea of the hidden life behind a simple digital action captures the real problem: every feature carries invisible rules about sequence, timing, identity, and value. When those rules live only in a product document or a developer’s assumptions, the application may work exactly as built and still be insecure.

Why Scanners Miss the Most Expensive Mistakes

Security tools are good at finding recognizable weaknesses such as exposed secrets, outdated dependencies, unsafe input handling, or suspicious configuration. Business-logic flaws are different. They often involve valid accounts, legitimate requests, and intended product functionality.

The attacker does not need to break the feature. They need to understand it better than the team that built it.

A refund process may confirm that an order exists but never verify whether it has already been refunded. An invitation system may check that the sender is an administrator but fail to repeat that check when the invitation is accepted several days later. A free trial may be restricted per account even though the real business rule is one trial per person or company.

Every individual check can appear reasonable while the complete workflow remains exploitable.

The OWASP Top 10 for Business Logic Abuse approaches these failures as problems of application state, permitted transitions, and unenforced business constraints. The important lesson is simple: an application can reject malformed requests while accepting a perfectly valid request that should never have been allowed.

Stop Reviewing Screens and Start Reviewing State

Development teams often review a feature as a sequence of screens: start checkout, enter payment details, confirm the purchase, and receive a receipt.

An attacker sees something different. They see operations that can be repeated, reordered, delayed, interrupted, or executed at the same time.

A secure workflow must know the current state of the relevant object, which transitions are permitted from that state, and whether another process has already changed it.

“Cancel order” is not inherently safe. It is safe only when the order has not shipped, no refund is already being processed, no irreversible service has been delivered, and the requester still has permission to cancel it.

Important states therefore need explicit definitions. A password-reset token should not remain valid merely because its expiration time has not passed. A promotion should not remain available because the interface still displays it. An order should not be considered refundable simply because one database field has not yet been updated.

Every security-relevant state needs one authoritative meaning and one controlled path into the next state.

Use Abuse Cases Before Writing Test Cases

A normal acceptance test asks whether the intended user can complete the expected journey. An abuse-case review asks what happens when the same user stays inside the available functionality but abandons the intended journey.

Before shipping a feature involving payments, permissions, quotas, inventory, credits, approvals, or account recovery, the team should ask:

  • What can a user repeat that should happen only once?
  • Which steps can be skipped, reversed, or completed out of order?
  • What happens if two valid requests arrive almost simultaneously?
  • Which sensitive value is accepted from the user instead of calculated by the server?
  • What permission, balance, price, or status could change between approval and execution?

These questions are valuable because they challenge assumptions rather than syntax.

They also explain why business-logic reviews cannot belong only to security engineers. Product managers understand the intended rule. Developers understand the implementation. Customer-support teams know the recovery paths that users actually take. Finance teams know when a credit, refund, or balance becomes final.

The vulnerability often exists between those perspectives.

A product manager may believe that a discount can be used only once because the interface removes it after checkout. A developer may believe the payment service prevents duplicate transactions. Support may have an internal tool that restores canceled orders. Each belief may be individually reasonable, but their interaction can create a path nobody intended.

Treat Time as Part of the Attack Surface

Many logic failures are not caused by missing checks. They happen because the correct check is performed at the wrong moment.

An application confirms that a customer has enough balance and deducts the amount later in a separate process. It verifies that a user is an administrator and then queues an action that runs after the user has lost that role. It checks that inventory is available and then allows several concurrent purchases to claim the final item.

The decision may have been correct when it was made and invalid when it was used.

Sensitive workflows should therefore prevent the same operation from taking effect twice, stop competing requests from claiming one limited resource, and repeat critical checks as close as possible to the irreversible action.

Retries deserve particular attention. Distributed applications routinely repeat failed jobs and network requests for reliability. The system must distinguish between “the first attempt failed” and “the first attempt succeeded, but its response was lost.”

Without that distinction, a reliability feature can quietly become a duplicate-payment, refund, credit, or inventory vulnerability.

Make the Safe Path the Default

Telling developers to “remember to validate this” is not a durable security control. The safer approach is to create shared mechanisms that make invalid states difficult to produce.

Promotion redemption should be controlled by one trusted service instead of being reimplemented in every checkout flow. Sensitive operations should reject duplicate execution automatically. Status changes should use defined transitions rather than accepting arbitrary new values. Permission checks should also apply to support dashboards, mobile APIs, background jobs, imports, and internal tools.

The system should record who initiated an important action, which rule allowed it, and what previous state was changed. This makes unusual behavior easier to investigate and prevents internal workflows from becoming invisible security exceptions.

This reflects the broader principle behind CISA’s Secure by Design guidance: software producers should take responsibility for secure outcomes instead of expecting users and customers to compensate for unsafe defaults.

The goal is not to predict every clever attack. It is to remove places where the application depends on an undocumented promise that users will follow the expected path.

Start Every Security Review With One Sentence

For each important feature, write its non-negotiable rule in plain language:

A customer may receive this credit only once.

Only the current owner may transfer this resource.

A canceled transaction cannot generate another refund.

Then try to make the application violate that sentence using only legitimate functionality.

This exercise is more revealing than asking whether an endpoint requires authentication. It forces the team to define what must remain true regardless of request order, retries, concurrency, hidden interfaces, or future product changes.

The most damaging application-security bug may not look like an attack. It may look like a successful request, a completed workflow, and a feature doing exactly what the software was told to do.

The real question is whether the software was told the whole truth.

Top comments (0)