Product Security · Offensive Security · Secure Engineering
Product Security Engineers Should Practice Exploitation
Exploitation practice is not theater. It is how defenders learn which assumptions fail, what evidence matters, and how to recommend fixes engineers can use.
I think Product security engineers should practice exploitation.
Not because every product security engineer needs to become a full-time exploit developer or red teamer. Not because the work needs more drama. Not because a clever demo is the same thing as impact.
They should practice exploitation because defenders who never follow a bug all the way from assumption to impact often learn vulnerability names before they learn failure paths.
That difference matters.
Knowing a category is useful. Broken access control, injection, SSRF, deserialization, path traversal, cross-site scripting, confused deputy, tenant isolation failure. The names give us a shared vocabulary. They help us search, teach, document, and classify.
But the name is not the lesson.
The lesson is what had to be true for the system to fail. Which value was trusted. Which control was missing, late, or checking the wrong thing. Which boundary was crossed. Which assumption looked reasonable in code review but did not survive contact with actual behavior.
Exploitation practice makes that lesson harder to dodge.
Offensive Practice Is Not Performance
There is a version of offensive security that becomes performance. Loud language. Big screenshots. Overstated impact. A rush to make the finding look more impressive than the evidence supports.
That is not what I mean.
Useful offensive practice is quieter. It is disciplined. It starts with a narrow question, proves one thing at a time, and stops when the evidence stops. It cares less about showing that software can break and more about understanding why it broke in this specific way.
For a defender, the goal is not to collect trophies. The goal is to build better judgment, put yourself in the shoes of an attacker, and build empathy for developers.
That judgment shows up later in boring but important places:
- A design review question that catches a bad assumption early.
- A code review comment that points at the exact missing control.
- A finding that separates confirmed behavior from speculation.
- A recommendation that fixes the pattern instead of only patching the symptom.
- A test case that keeps the same failure from coming back.
That is the useful part. The exploit is not the end of the work. It is a way to make the failure concrete enough to learn from it.
Vulnerability Names Are Not Enough
Security teams can accidentally teach people to think in labels.
This looks like SSRF. That looks like IDOR. This smells like injection. That might be privilege escalation.
Labels are fine as a starting point, but they are a bad stopping point. A label does not tell an engineer what to change. It does not prove reachability or impact. It does not explain whether the issue is blocked by a control. It does not say whether the control failed because it was missing, misplaced, incomplete, or applied to the wrong object.
Exploitation practice forces better questions.
Instead of:
Is this vulnerable to IDOR?
Ask:
Can one authenticated actor cause the system to read or change an object they do not have authority over?
Where is the target object resolved?
What proves the actor can act on that object?
What happens if the object reference belongs to someone else?
Instead of:
Is this an injection bug?
Ask:
Does user-controlled input reach an interpreter, query builder, template engine, expression evaluator, command runner, or parser?
What transforms the value before it arrives?
What values are allowed?
What evidence shows the value stayed data instead of becoming behavior?
Those questions come more naturally after you have reproduced the failure mode yourself. You stop treating the category as the answer and start treating it as a pointer toward the real work.
Practice Builds Design Instincts
The best time to catch a security issue is before the code is written, but early review is only useful if the reviewer can imagine how the design might fail.
That imagination is not magic. It is pattern memory.
If you have practiced authorization failures, you notice when a design talks about users but never names the object being authorized. If you have practiced tenant isolation bugs, you notice when the list path is scoped but the export path is vague. If you have practiced parser bugs, you notice when validation happens before normalization and nobody has said what the normalized value can become.
Hands-on practice turns abstract caution into specific questions:
- Which actor is allowed to trigger this action?
- Which object proves that authority?
- Does the async path keep the original actor context, or does it run later with broader service authority?
- Does the UI prevent something that the server still needs to enforce?
- Does this field stay inert data, or can it become configuration, a query, a template, a path, a URL, or a command?
- What would we need logged to understand abuse after the fact?
That is where offensive practice pays off for product security. It moves the lesson upstream.
I wrote a shorter version of this idea in How Hands-On Exploitation Made Me Better At Product Security. The practical point is the same: offensive skill is most useful when it improves the decisions people make before the bug exists.
Practice Makes Code Review Less Vague
Weak security review often sounds confident but imprecise.
This might be risky.
Can we sanitize this?
Make sure this is authorized.
Could this be abused?
Those comments may be directionally right, but they leave too much work for the engineer receiving them. What is risky? Which value needs sanitization? Authorized against what? Abused by whom? What would prove the concern is real or not real?
Exploitation practice makes review comments sharper because it trains you to trace the path.
A better comment sounds more like:
This handler resolves the target object from a caller-controlled ID, then writes to it before I see an ownership or permission check on that resolved object. If the ID can reference another user's object, the caller may be able to change state outside their authority. I would expect the authorization check to happen after object resolution and before the write.
That comment is longer, but it is easier to act on. It names the source, the target, the missing control, the consequence, and the expected fix shape.
This is also why I like sink-first code review. Start at the operation that would matter if abused, then walk backward to the first source that can influence it. The method keeps the review grounded in behavior instead of vibes.
Evidence Is The Difference Between A Lead And A Finding
Practicing exploitation also changes your threshold for evidence.
A lead is an interesting possibility. A finding is something you can explain, reproduce in an authorized setting, and tie to a failed control.
That distinction matters because product security depends on credibility. If every weak theory gets written like a confirmed issue, engineers learn to discount the work. If every confirmed issue comes with clear evidence, engineers can move faster because they do not have to reverse-engineer the claim.
Good evidence answers boring questions:
- What path was reviewed?
- What actor, input, object, or state was controlled?
- What control was expected?
- What actually happened?
- What impact is confirmed?
- What impact is still speculative?
- What change would remove the failed assumption?
The point is not to make every write-up huge. The point is to make the claim checkable.
Exploitation practice helps because it teaches humility. A path that looked obvious may not be reachable. A payload may be transformed before it matters. A dangerous-looking helper may only receive trusted values. A missing check in one layer may be enforced in another. A scary result may be real, but lower impact than the first theory.
That is all useful information. Failed hypotheses are not wasted effort if they sharpen the map.
The Defender Loop
For product security, the loop is simple:
- Reproduce the bug class.
- Trace the assumption.
- Prove the control failed.
- Translate the lesson into prevention.
Reproduce the bug class so you understand the mechanics. Use labs, intentionally vulnerable apps, old public write-ups, or safe local examples. Keep the scope clean. You do not need a dramatic target to learn the pattern.
Trace the assumption so the issue becomes more than a label. What did the system believe? That the caller owned the object? That a redirect stayed on an allowed host? That a field was already normalized? That a queued job still represented the original user’s authority? That a template value stayed text?
Prove the control failed with the smallest safe check that answers the question. Do not jump from suspicion to final write-up. Confirm reachability. Confirm the sensitive operation. Confirm the missing or weak control. Confirm the actual impact. Stop where the evidence stops.
Translate the lesson into prevention. This is the part that makes the practice defensive. A bug class should become a design question, a code review check, a test case, a safer helper, a logging improvement, or a default that makes the unsafe path harder to choose.
That translation is the work.
The Offensive Security For Defenders guide has a more structured practice plan, but the core habit is compact: learn the mechanics, identify the failed assumption, then make the safer engineering path clearer.
What This Changes
Product security engineers who practice exploitation tend to write better findings.
They are more likely to explain root cause instead of only naming impact. They are more likely to know when a result is confirmed and when it is still a theory. They are more likely to recommend a fix at the layer where the failure actually happened.
They also tend to ask better design questions.
Not because they are more cynical, but because they have seen how ordinary systems fail. They know that small differences matter. One route checks ownership and another does not. One field is normalized and another is not. One path runs in the user’s context and another runs later as a service. One UI hides the button and one API accepts the request anyway.
Those are not exotic failures. They are normal engineering failures with security consequences.
Offensive practice makes them visible sooner.
The Main Thing
Product security is not improved by theater. It is improved by judgment.
Exploitation practice is one way to build that judgment because it makes risk concrete. It forces the reviewer to move from category to path, from suspicion to evidence, from impact claim to failed control, and from one bug to a reusable prevention pattern.
That is why product security engineers should practice exploitation.
Not to prove that software can break. Everyone already knows that.
Practice so you can help engineers build it so it does not break that way again.