← All posts

My Pentest Methodology, Full Breakdown

The system underneath the testing — how I pick targets, take notes, and decide what's worth reporting.

For a while I didn't really test anything. I'd land on a program, open the app, and freeze — too much surface, no clear place to start, and a nagging feeling that everyone else already had a system figured out except me. That's the honest reason this post exists: I built this process to get myself unstuck, and it worked well enough that it's worth writing down for anyone stuck in that same spot right now.

So here it is, start to finish. This isn't a tool list, and it isn't a payload cheat sheet — there are plenty of those already. This is the system underneath: how I pick what to test, how I take notes so I don't lose the thread three days in, and how I decide whether something is worth reporting at all.

1. Choosing the program

Before touching a single request, I spend real time picking what to test — because the choice of target does more for my hit rate than any technique I know.

Two things I look for:

  • Low report volume with fast triage. A program with few reports in the last 90 days and a triage team that actually responds is a program that hasn't been picked clean. Popular, high-payout programs get hammered by thousands of hunters; the signal-to-noise on what's left is terrible.
  • Narrow scope over wide scope. One domain, well understood, beats ten domains skimmed. Depth wins over breadth almost every time in bug bounty.

There's a counterintuitive signal I weigh heavily too: friction is an advantage. A program that requires phone verification, has a convoluted signup flow, or otherwise makes onboarding annoying will get tested by far fewer hunters — most people bounce at the first piece of friction. If you're willing to push through it, you're often looking at a much shallower pool of prior testing than the scope would suggest.

As a general example: a program with a single SaaS domain in scope, a signup flow that requires phone verification, and only a handful of reports in the last three months is a far better bet than a five-domain program with thousands of existing reports, even if the second one looks more "interesting" on paper.

2. Initial setup — before testing anything

Once I've picked a target, there's a short checklist I run before a single payload goes out:

  • Read the full program rules, not just the scope. Rate limits, disclosure restrictions, what counts as out-of-scope testing (social engineering, physical, third-party integrations) — all of it. Getting this wrong costs you so much time.
  • Set the required User-Agent as a global proxy rule immediately. If the program asks for an identifying UA on all traffic, I bake it into the proxy so it's impossible to forget on a later request.
  • Write down the basics in an initial-notes file: target, platform, scope, restrictions. Future-me, three sessions from now, does not remember all of this.

3. Passive recon — then, more importantly, manual recon

I start with the usual automated pass to build a map before I touch anything by hand:

  • Subdomain enumeration — casting a wide net across the attack surface before narrowing.
  • Port scanning — including non-standard ports, since that's often where the interesting stuff (admin panels, internal tooling exposed by accident) lives.
  • Technology fingerprinting — what's actually running under the hood, since that shapes which vulnerability classes are even plausible.
  • Content discovery — directory and file brute-forcing with wordlists tailored to the tech stack, not generic ones.
  • Parameter discovery — hidden parameters are a disproportionately good source of findings, precisely because they're not in the visible UI and rarely get the same validation scrutiny.
  • JS analysis — client-side bundles leak endpoints, internal API structure, and occasionally secrets that never should have shipped.

But passive recon only gives you a map — it doesn't tell you how the app actually behaves. It's the part that's easiest to over-invest in, because it feels productive and it's fully automatable. The step that actually produces findings is the one right after: manual recon, walking through the app yourself. Automated tools see requests and responses; they don't understand what a feature is for, what a normal user flow looks like, or which fields quietly trust the client more than they should. That understanding only comes from using the app yourself with a proxy open. I treat manual recon as the real starting line, not a fallback for when the automated pass comes up empty.

As a general example: automated content discovery might surface an endpoint like /api/v1/invite and stop there — it exists, it returns 200, move on. Only manual poking reveals that the response quietly includes an internal identifier you never asked for, or that the same endpoint behaves differently depending on which role sent the request. That kind of detail only shows up when a human is reading the traffic, not just enumerating it.

4. Crown jewel — decide the worst case before you test anything

Right after the initial notes are down and before I start the manual testing loop, I write the crown jewel, or CJ: the worst-case version of a bug for this type of application, and the rough chain of steps that would get an attacker there. Not a specific vulnerability yet — just a question like "what would be the worst thing someone could do here, and what would have to be true for that to happen?"

I write this early, before deep testing, on purpose. It isn't a summary of what I found — it's a compass for what I go looking for. Knowing the worst case up front changes how I read every feature afterward: I'm not just collecting oddities, I'm checking whether each one moves toward or away from that scenario.

Here's roughly how a cj.md entry looks for a generic multi-tenant SaaS target — this is a generalized example, not a real finding:

# Crown Jewels

## Cross-tenant data access
Worst case: an attacker in Company A reads or modifies Company B's data.

Steps needed:
    1) enumerate every endpoint that takes a tenant/company id from the client
    2) two-account test swapping the id on each one.

## Intra-tenant privilege escalation
Worst case: a normal member-level account grants itself admin permissions
within its own organization.

Steps needed:
    1) map every endpoint that assigns or changes roles
    2) test whether the role field is validated server-side or just
       trusted from the client, and try to manipulate requests to escalate.

I don't know yet if any of that is true — the point is having that shape in mind before I start testing, so I recognize the pieces when I run into them.

5. The manual testing loop

This is the actual day-to-day loop, repeated feature by feature across the whole application:

  1. Explore the feature like a normal user, with Burp open, and log it in feature-map.md — the URL, what it accepts as input, what it returns. No judgment yet, just an accurate record.
  2. Read the requests as you go. With the proxy open, look for patterns that hint at how the feature could be pushed off its intended path — a parameter that looks like it maps to an internal ID, a role or permission value that's just echoed back, a field that behaves differently than the UI suggests.
  3. Anything that feels strange gets a line in notes.md, immediately, even half-formed. You don't need to know why it's strange yet — just that it is.
  4. Once you understand the feature well and the notes start pointing somewhere, turn that into an attack vector in av.md: a yes/no question, written simply and clearly, with the concrete steps you'd take to test it and the hypothesis behind it.
  5. Test the AV if you can, and mark it positive or negative. Either outcome is useful — a negative AV, written down, stops you from re-deriving and re-testing the same idea two weeks later.
  6. Repeat the loop for the next feature, carrying the crown jewel in the back of your mind the whole time.

A generalized walkthrough of one pass through the loop, on a made-up "team invite" feature. First, what lands in feature-map.md while exploring:

# Feature Mapping

## Team invite

Flow:
  1. Admin enters an email and picks a role from a dropdown
  2. Invite created, email sent
  3. Invitee accepts, account is provisioned with that role

Objects:
  POST /api/v1/org/invite
    - "email": "invitee@example.com"
    - "role": "member"

Then, something about that response feels off, so it goes straight into notes.md:

# notes

- the role field in the response of POST /api/v1/org/invite is exactly
  what I sent in the request — no server-side default or override visible
- *to check*: is role re-validated anywhere on acceptance, or just carried
  through as-is from the invite object?

Once there's enough there to actually test something, it becomes an entry in av.md:

## Role trusted from invite payload

Hypothesis: the role field on invite creation is never re-validated when
the invite is accepted, so whatever role was set at creation time is what
the new account ends up with.

Test:
    1. As a member-level account, send an invite with role=owner
    2. Accept the invite from a second test account
    3. Check the resulting permission level of the new account

Status: done
Result: *Negative* — role gets silently downgraded server-side on acceptance

Negative result, but it's written down for good: it stops me from re-deriving and re-testing the same idea two weeks later, and the original observation about the echoed role field stays in notes.md in case it combines with something else down the line.

6. Prioritization and operational habits

A few habits that keep the whole thing from turning into busywork:

  • Prioritize high impact plus low complexity first. Findings like SSRF against internal infrastructure, unauthenticated RCE, auth bypass, and critical IDOR chains get tested before anything cosmetic, because they're both the most valuable and often the fastest to confirm once you spot the right pattern. As a general example: a confirmed AV pointing toward account takeover gets tested before a cosmetic bug that only breaks page layout, even if the layout bug is quicker to write up.
  • Automate the repetitive parts, stay manual for confirmation and exploitation. Automation is great at casting a wide net; it's bad at judgment calls about whether something is actually exploitable versus a false positive.
  • Respect scope, always. Anything out of scope gets reported as informational at most, never tested further, no matter how interesting it looks.

Why the structure matters more than the payloads

If there's one thing I'd want someone new to bug bounty to take from this, it's that the payloads are the least differentiating part of the whole process. Everyone has access to the same XSS strings and the same SQLi cheat sheets. What separates a consistent hunter from someone who gets lucky once is the scaffolding around the testing: picking targets deliberately instead of randomly, separating observation from hypothesis so good ideas don't get lost in noise, and knowing the worst case you're chasing before you start.

None of this is complicated. It's just structure applied consistently, session after session, on targets most people didn't bother to look at closely enough. If you're stuck at the "I don't even know where to start" stage right now — that was the whole point of writing this down.

← All postsQuestions? Email me