justthetip

Lessons Learned from 3 Months of Bug Bounty Hunting

lessons-from-three-months-of-bug-bounty.md · · bugbountybugcrowdvulnerability-researchlessons-learned

Three months. A dozen programs. A full tour of what works, what doesn’t, and why none of it has anything to do with how good a researcher you are.


The thing nobody wants to say

The bugs are real.

I want to start there, because I spent the first month of this doubting the work when I should have been doubting the process. The bugs I submitted were real. The evidence was reproducible. The vulnerabilities were present, in shipping code, affecting real customers, causing real exposure. Several of them are still present, months later, because the system that is supposed to get them fixed is structurally incapable of doing so.

What wore me down was not finding bugs. Finding bugs turned out to be the easy part. What wore me down was the grind of proving bugs to a triage pipeline that cannot read them.

Finding bugs is the easy part. Surviving the triage pipeline is the job.

The people who sit between you and a bug fix are, in aggregate, subliterate. This is not a comment on their intelligence. It is a description of their incentive structure. They are paid to close tickets, not to understand findings. They are evaluated on throughput, not accuracy. They skim titles, they pattern-match against templates, and they close things they don’t recognize. When you hand them a forty-page writeup with reproducible evidence, packet captures, disassembly, and CVE precedents, they reply with, “Could you clarify: Target URL? HTTP method?” on a finding that is a local privilege escalation on a root-owned daemon. There is no URL. There has never been a URL. There will never be a URL. They ask anyway, because the template has a field for it.

This is the actual opposition. Not the vendor. Not the complexity of the bug. Not the competition from other researchers. The triage layer between your work and the fix.

And so the single most important skill I have developed in three months is not reverse engineering, not vulnerability research, not exploit development. It is target selection optimized for surviving the triage bottleneck. Everything else flows from that.

Here is what I have learned, in order of importance.


Lesson 1: Your target selection is your triager-proofing

Every program has bugs. Every installer you can download has a privilege escalation in it somewhere if you look hard enough. The question is not “can I find bugs on this target.” The question is:

Can I find bugs on this target that a hostile, impatient, skim-reading triager cannot argue their way out of?

That is an entirely different question, and most researchers, me included, took three months to start asking it.

The mental model I use now: before I commit to a target, I imagine the finding I’m likely to produce and I hand it to an imaginary triager whose entire personality is Reddit mod energy. Unreasonable, territorial, absolutely certain they know more than you, limited patience for nuance, ready to dismiss anything that doesn’t fit a template. Then I ask: will this finding survive contact with that person?

Some targets are built for survivability. Downloadable installer, runs as root or SYSTEM, has a local service, writes files with bad permissions, has IPC between privileged and unprivileged components. The findings you produce look like: unprivileged user runs script, root-owned file appears on disk. Three commands. One screenshot. A Reddit mod cannot argue with a root-owned file. The operating system does not care about their opinion. The file is there or it isn’t.

Other targets are built to filter you out. Pure web applications behind Cloudflare with no local component. SaaS dashboards where every “finding” requires you to set up a tenant, configure a dashboard, deploy a webhook, and then explain at length why the resulting behaviour is harmful. Cloud infrastructure where the proof of impact requires an AWS account in a specific region and the triager does not and will not have one. Mobile apps with certificate pinning, binary DRM, and rate-limited APIs where every interesting endpoint returns 403 from external IPs.

Those second-category targets are not impossible. They are just territory where you have to be five times better than someone else to be noticed at all. Because the bar for “finding the triager will accept” is so much higher, you end up racing researchers whose entire professional identity is beating exactly that type of target. You, the binary RE generalist who wandered in, are not going to win that race.

The first filter I apply to every new program now is: does the shape of this program’s likely payouts match findings I can produce in a form a Cletus-tier triager can’t dismiss? If no, I don’t engage. Not because the bugs aren’t there. They are. I just can’t afford to spend six weeks arguing with “sowwy, Cletus no dun lern ’local privilege escalation’ yet, a hyuk” for a payout that doesn’t cover the time.


Lesson 2: Access is not exploitation, and nobody will give you credit for proximity

You will find, over and over, real security problems that look like: unauthenticated access to production infrastructure. A provisioning endpoint that accepts requests without credentials. An IoT admin interface with no login prompt. A production API key sitting in plain sight inside a mobile app. An internal management console exposed to the open internet.

Every single one of these will feel, at discovery, like an emergency. You will feel an obligation to report it immediately, because real people are exposed, right now, in production.

And the triager will close it informational.

Not because the finding is wrong. Because “I can reach this endpoint” is not the same sentence as “I harmed a real user through this endpoint.” The program owner is paying the platform to keep out findings that don’t have a dollar figure attached to the harm. The triager’s job is to make sure they don’t pay you for proximity. Your job, if you want to get paid and if you want the bug fixed, is to close the gap yourself before you submit.

This is infuriating. The endpoint is already dangerous. A more sophisticated attacker than you, one with fewer ethical constraints, will find it and use it. You did the responsible thing by stopping short of hijacking a real customer’s account, and the responsible thing got you a point deduction and a “not applicable.” The bug remains in production. The harm remains possible. Only your reputation on the platform moved.

The responsible thing got you a point deduction. The bug is still shipping.

The practical move is to be less precious about what counts as “exploitation” when you write the submission. You don’t need to steal a real customer’s data. You need to demonstrate, with evidence, that you could. Ideally on your own test account, against infrastructure you control, in a way where the proof of impact is visible in one screenshot.

Not “this endpoint responds to unauthenticated requests.” Instead:

Using only an email address, I submitted a password reset request. The response contained a session token that I used to read the authenticated /me endpoint for my own test account. Here is the screenshot. The token works. The account is reachable. This works for any address.

Same finding. Same underlying bug. The second version is un-dismissible. The first version dies in triage.


Lesson 3: “Interesting” is for you. “Un-dismissible” is for the triager.

The most dangerous moment in an engagement is when you find something interesting. A hardcoded token in a binary. A weird debug endpoint. An obfuscated config file that looks like it has real secrets in it. Your adrenaline spikes. You are now five seconds away from sinking an entire afternoon into a path that leads nowhere.

The thing that separates researchers who get paid from researchers who don’t is the ability to kill interesting findings quickly.

You will find ten interesting things for every one that pays. Your job is to apply pressure to each one until it either becomes a real finding or dissolves. The pressure is a single question, asked out loud, in your own voice, as brutally as you can manage:

“If I hand this to a triager whose entire career strategy is closing tickets to maintain throughput metrics, what does their rejection template look like?”

If you can write the rejection template in under a minute, the finding is not ready. Every rejection template you can imagine is a weakness in the finding. Go back, tighten it, and ask again.

A finding is ready when you cannot imagine the rejection. When the proof speaks for itself and the only response available is “pay the man.” That finding pays. Every finding that lives in the grey zone, where you can hear the rejection writing itself before you’ve even submitted, those are the ones that eat your months.

Kill interesting fast. Keep digging. The thing that pays is the tenth thing you find, not the first.


Lesson 4: The screenshot test

A finding that cannot be captured in a single screenshot will be argued about for six weeks. Not a guideline. A physical law.

The triager gets, at most, ten minutes of attention per ticket, and they spend eight of it on the ones with no reproduction steps. Yours has to land in the remaining two. It has to close before the argument begins. And the only shape of finding that closes before the argument is one where the proof is self-evident:

Compare that to the alternative: a submission that opens with “In a configured multi-tenant deployment, under specific conditions involving…” That submission is technically correct. It describes a real vulnerability. It will never be paid, because the triager cannot verify it in ten minutes, the vendor will not spin up a multi-tenant deployment to reproduce it, and the ticket will circulate through reviewers until someone closes it to clear their queue.

The skill is not writing thorough submissions. The skill is compressing a real finding into something undismissable. Your thirty-page writeup is ego. The one-paragraph description with a screenshot is the real product.

If you can’t compress your finding into a screenshot’s worth of proof, that is the signal to keep digging, not to submit. Keep digging until the screenshot is possible, or flush the finding and move on. The middle ground, “I’ll submit and argue,” is where calendar goes to die.


Lesson 5: If it involves a GUI, they won’t set it up

This is a corollary of Lesson 4 but it deserves its own headline, because it kills more findings than any other single factor.

If your proof of impact requires the triager to:

…the triager will not do any of that. They will type “unable to reproduce” into the comment box, close the ticket, and move on. I have watched dashboard-gated findings die three different ways: the triager doesn’t know which version to install, the triager doesn’t have an account at the right tier, the triager spins up a fresh tenant and can’t find the setting because they don’t know the dashboard the way you do. Every one of those outcomes is a ticket closure in the dismiss column.

The findings that survive the GUI filter are findings with zero GUI. A binary, a script, a terminal command, a curl invocation. Anything else is a triager obstacle course, and the obstacle course is designed to exhaust the triager before it exhausts the bug.

This is also, by the way, the actual reason so many real bugs in enterprise software stay unpatched. It is not that the vendor doesn’t care. It is that the finding never survives long enough in the triage pipeline for the vendor to see it. The platform wraps the vendor in a triager shell that filters out anything inconvenient. Your clean, simple, command-line finding gets through. Your dashboard-dependent finding doesn’t. The dashboard bug remains in production. The customer remains exposed.

If you want bugs fixed, submit bugs that cannot be filtered out.

Pick bugs the bureaucracy cannot argue with. Pick bugs the operating system itself vouches for.

Lesson 6: Flush fast, write the post-mortem

Every hour without a concrete lead on a target increases the sunk cost and decreases your willingness to cut. This is the universal failure mode, and I have tripped it more times than any other lesson in this post.

My current rule, written on a sticky note: four hours of investigation without a promising thread, the session is over, write up what you learned, move to the next target. Breaking this rule has never, in three months, produced a win. It has only produced worse losses.

The kicker: the post-mortem is free value. Every failed target teaches you something concrete about its shape, the class of bug it was built to resist, the reason your particular skill set didn’t find traction there. Write that down. File it with your other lessons. A researcher with twenty failed targets and twenty post-mortems is stronger than a researcher with one failed target and a month of sunk cost.

Dead ends are data, not defeat. Flush, document, move.


Lesson 7: Low ceiling, low investment

Programs advertise their maximum reward. The typical accepted finding pays around a fifth of the advertised ceiling. Budget accordingly.

A program with a $5,000 ceiling does not justify three days of binary reverse engineering. The same three days on a program with a $50,000 ceiling or an uncapped bounty is an excellent trade, and the high-ceiling programs also tend to have better triage, more responsive vendors, and less tolerance for the “close it informational” reflex.

Low-ceiling programs are where researchers who don’t yet understand the economics sink their time. They feel accessible and reassuring, which is exactly why they’re dangerous. Use them only as warm-ups. Go in knowing you will spend at most a few hours. Take anything that pops quickly, or leave.


Lesson 8: Point deductions are permanent damage

Most platforms track researcher reputation, and several deduct reputation for submissions closed as not applicable or out of scope. This number affects how quickly your next submission is triaged, which triager it gets assigned to, and how favorably they approach it.

In other words: a bad submission is not free. You pay for it on the next finding, and the one after. Three minor point deductions cost more, in expected future earnings, than the absence of three marginal submissions.

The calibration exercise I run on every finding before I submit: if I had to bet a meaningful amount of my own money on this paying out at the severity I’m claiming, would I? If the answer is no, I find more proof, lower the claim, or move on. A submission I’m not willing to stand behind with real money is a submission that’s going to cost me on the next one.


The system is obstinate. The work is not.

Three months in, here is the summary I wish I’d been handed on day one.

The bugs are real. You will find them. You will find more of them than you expected, in places you did not expect, including in products sold by companies with hundreds of engineers and nine-figure security budgets. The work itself, the hunt, the research, the reverse engineering, the disassembly, the moment when the proof-of-concept writes a root-owned file for the first time, that work is righteous. That work is why you are here.

But there is a triage layer between the work and the fix, and it is composed of people who cannot read, evaluated on metrics that reward closing over fixing, protecting vendor budgets from being spent on your findings. Their existence is the actual opposition. Your job, if you want to see bugs fixed and if you want to eat, is to pick targets and finding shapes that physically cannot be dismissed by someone with Reddit mod energy and a dropdown menu.

Downloadable agent that runs as root. Local privilege escalation with a root-owned proof file. One screenshot. Three commands. A bug so simple in form that a seventh grader could reproduce it, pinned to impact so obvious that even the template cannot flinch away from it.

That is the target. Every lesson in this post is a variant of that one sentence.

Pick bugs the bureaucracy cannot argue with. Pick bugs that produce proof the operating system itself vouches for. Pick bugs where the triager, if they are being even marginally honest, has no template available to close with.

And when you find the ones that pay, it won’t feel triumphant. You will feel a brief flicker of relief. That flicker is the actual signal of good work. Learn to produce more of that, and less of the drama. The bugs are still out there. They are still harming people. Go get them, and design the finding so the bureaucracy can’t protect them on the way to the fix.


If this saves you even one week of your life, pay it forward. Tell someone else. We all owe each other the version of this advice that we paid for.


Lucid_Duck reverse-engineers things that weren’t meant to be, and writes about it here.