Scanning the Access Matrix with Burp Intruder: A Repeatable Setup

Scanning the authorization matrix with two sessions and an ID list: recording a baseline, bulk requests with the attacker cookie, triage by length, single-request confirmation.

16 min read
ibrahimsql
3,047 words

Scanning the Access Matrix with Burp Intruder: A Repeatable Setup#

Manual IDOR testing starts with two users and a few changed IDs, but once the matrix grows, continuing by hand is slow and error-prone. Burp Intruder takes over the bulk-request work; the decisions stay human. The setup below applies only against targets you are allowed to test.

Setup: two users, one baseline#

  1. Create two users on the target: A (victim, owner of the object) and B (attacker, valid but unauthorized).
  2. Send a normal request for the object with A's cookie and save the response. That is the baseline: status code, length, one distinguishing field.
  3. Move B's cookie into Intruder. The attacking side is always B.

No scan runs without a baseline. Interpreting length differences requires knowing what the normal response looks like.

Lab target preparation: users and objects on your own target#

Every step in this section runs only against a target you set up yourself. Do not try it against systems owned by others.

Step 1: create user A and user B#

  1. Create two separate accounts on your own lab target. Use two different email addresses when a registration form exists.
  2. Mark user A as the object owner. Keep user B in the same role but in a different tenant or group.
  3. Store passwords in a password manager. Passwords never appear in these notes, and password fields stay empty in examples.
  4. Verify that both accounts can log in separately. Do not move to scanning when account B cannot log in, because an invalid cookie and an unauthorized cookie mean different things.

Step 2: create target objects and note the IDs#

  1. Log in as user A and create at least three objects. For an invoice app, open three invoices; for a profile app, open three profile records.
  2. Write each object ID into a table. The record format below works as a starting point.
  3. Create one object as user B as well. That record shows what a normal response looks like on the B side.
  4. Note where each object ID came from: the address bar, the id field in an API response, a downloaded export file.
OwnerObjectIDNote
AA invoice 1101Used for the baseline
AA invoice 2102Goes into the scan list
AA invoice 3103Goes into the scan list
BB invoice 1201Normal response on the B side

Step 3: register and log in through the proxy#

  1. Open the Burp proxy listener and route the browser through the proxy.
  2. Log in as user A first. Find the login request in proxy history and note the session cookie name.
  3. Clear the browser profile or open a private window, then log in as user B.
  4. Confirm that both logins appear in proxy history. A session missing from history leads to moving the wrong cookie into the scan.
  5. Write the ID list into a text file. Keep one ID per line and avoid blank lines, because a blank line produces an extra request.

Do not open the Intruder tab before this preparation is done. Missing users or a missing ID list make scan results impossible to read.

Proxy and Repeater baseline#

A baseline is a reproducible record of one request. The goal is to fix the normal response as a triple: status code, length, and one distinguishing field.

Reproducible request flow#

  1. Turn intercept off in the Proxy tab first. Picking traffic from history misses fewer requests.
  2. Send a normal request for the target object as user A. Example: open invoice 101.
  3. Find the matching line in proxy history. Right-click and send it to Repeater.
  4. Resend the request in Repeater. Confirm that the response stays the same across repeats.
  5. Mark that request as the baseline. Add a note in Burp or rename the tab to baseline-A-101.

Baseline record table#

Write the three fields below for each baseline. Length is read in bytes.

FieldExample valueMeaning
Status code200Access by A to its own object
Response length1843Byte value in the Repeater response panel
Distinguishing fieldaccount_no: ****-4401Value seen only in the A object

Length alone means little. Writing down which field distinguishes the record saves time during triage.

Example redacted request and response pair#

Cookies and private fields below are redacted. Do not use real values.

GET /invoice/101 HTTP/1.1 Host: lab.local Cookie: session=SESSION_COOKIE_REDACTED Accept: application/json
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 1843 { "id": 101, "owner": "USERNAME_REDACTED", "account_no": "ACCOUNT_NO_REDACTED", "amount": 1250, "status": "paid" }

Send the same request once with the B cookie in Repeater and save the result separately. That second record shows the expected denial form for an unauthorized attempt.

GET /invoice/101 HTTP/1.1 Host: lab.local Cookie: session=SESSION_COOKIE_REDACTED Accept: application/json
HTTP/1.1 403 Forbidden Content-Type: application/json Content-Length: 312 { "error": "access_denied", "message": "You are not allowed to read this record." }

These two records become the comparison point while reading the scan table. A 200 response of 1843 bytes against a 403 response of 312 bytes sets the scale for triage.

Sniper attack, single position: the object ID list. The cookie always belongs to B. The ID list comes from predictable ranges, export files, or previously leaked references; brute force is not the only source.

Sort the results table by response length. Rows matching the baseline length are the first suspects. Rows returning 403/404 are discarded; 200s with different lengths get separate review, because a different object can return a different size from the same endpoint.

Intruder Sniper configuration step by step#

This section gives the click order from attack type to filter settings.

Step 1: attack type and position marks#

  1. Send the B-cookie request from Repeater to Intruder.
  2. Set attack type to Sniper. One parameter changes, so Sniper is enough.
  3. Press Clear to remove automatic marks.
  4. Select only the ID value and press Add to mark the position. Example target:
GET /invoice/§101§ HTTP/1.1 Host: lab.local Cookie: session=SESSION_COOKIE_REDACTED
  1. Confirm that no mark sits on the cookie line. When the cookie changes, the scan no longer runs as B.

Step 2: payload sets#

Prepare two payload sources. One is a range, one is a list.

Source 1: number range

  • Payload type: Numbers
  • Range: 101 through 110
  • Step: 1
  • This source fits lab targets that assign sequential IDs.

Source 2: simple list

  • Payload type: Simple list
  • Example entries:
101 102 103 201 9999 abc
  • Write the source of each entry separately:
EntrySource
101, 102, 103Objects created by user A
201Own object of user B, control entry
9999Missing ID, error response sample
abcType check, error handling check

Load the list from a file with one column when typing is impractical. Keep no blank lines in the file, because each blank line sends a request.

Step 3: resource pool and throttle settings#

  1. Open the resource pool settings and create a new pool.
  2. Set concurrent requests to 1. Start low even against your own lab target.
  3. Turn on a delay between requests. The pattern below places a short pause after each request.
  4. Do not relax these settings against a target owned by someone else. Every bulk-requested target needs written authorization.

Step 4: grep-match rules#

Grep-match flags text found in responses. Add the example strings below:

RuleWhy it is added
account_noShows whether the distinguishing field appears in the response
ACCOUNT_NO_REDACTEDSearch this placeholder, never write a real value into the table
access_deniedFlags the denial body
not allowedCatches error text variants
paidShows whether the status field leaks

Do not write real account numbers, email addresses, or full names into grep-match fields. Searched text should be a field name or an error code.

Step 5: response filters#

  1. Open the status-code filter on the results table.
  2. Show 200 responses apart from 403 and 404 responses.
  3. Keep the length column visible. Sorting runs on that column.
  4. Write the baseline length next to the filter. When the baseline is 1843 bytes, add that value to the filter note.

Review the starting request once more before launch: the cookie must belong to B, the mark must sit only on the ID, and the payload list must hold the expected entries.

Result triage: sorting by length#

When Intruder finishes, first sort the results table by response length. Length never decides alone, but it sets the review order.

Sorting steps#

  1. Click the length column header and sort from large to small.
  2. Flag rows equal to the baseline length. Those rows form the first candidate set.
  3. Find the control entry 201. The own object of B shows the expected normal response.
  4. Find the missing-ID entry 9999. That row shows the length of the error body.
  5. Split the rest into length groups: same as baseline, near control, near error, matching none.

Candidate classification table#

Place each row into one of three classes. Example rows are redacted.

IDStatusLengthClassReasonNext step
1022001843matchSame length as baseline and account_no presentConfirm in Repeater
1032001912suspiciousStatus 200 but length differs, partial distinguishing fieldCompare bodies in Repeater
9999404298discardedMissing ID with error bodyClose review
2012001204discardedOwn object of B, expected normal responseKeep as control
abc400276discardedType check fired, error returnedClose review

Why the same endpoint returns different sizes#

Two 200 responses from the same endpoint can differ in length. Knowing the reasons prevents wrong filtering.

  1. Objects differ in size. One invoice holds three lines, another holds ten lines.
  2. Optional fields are full or empty. A note field is full in one object and empty in another.
  3. Related record counts change. A member list holds two people in one object and eight in another.
  4. A soft-delete placeholder returns. A deleted object returns a short status body instead of full data.
  5. An error body returns with 200. The app explains the error in the body without changing the status code.

The rule follows: length sets the order, body comparison makes the call.

Confirmation: single requests, not bulk#

Intruder produces candidates, not findings. Each candidate is confirmed with a single request in Repeater:

  • Does the response under B's cookie contain A's data?
  • Does the response belong to another tenant?
  • Do soft-deleted or ex-member candidates behave the same?

An unconfirmed length difference does not enter a report. The typical false-positive source is related data fetched too broadly while the tenant filter sits only on the main query.

Repeater confirmation protocol: five-question checklist#

Answer the same five questions in order for each candidate. When one answer points away, do not write a finding.

  1. Was the request really sent with the B cookie? Compare the cookie line in Repeater against the B session in proxy history.
  2. Does the response hold a distinguishing field owned by A? Search for the A-specific value, not the field name.
  3. What returns when the same request runs with the A cookie? Place both bodies side by side and mark line-by-line differences.
  4. What returns with no cookie or with an invalid cookie? Public data does not count as an access problem.
  5. Does the candidate behavior repeat? Send the request twice and confirm that both responses support the same call.

Worked example#

Candidate: ID 102 returns 200 and 1843 bytes under the B cookie. Same length as baseline 101.

The confirmation request is redacted:

GET /invoice/102 HTTP/1.1 Host: lab.local Cookie: session=SESSION_COOKIE_REDACTED Accept: application/json

The response body is redacted:

HTTP/1.1 200 OK Content-Type: application/json Content-Length: 1843 { "id": 102, "owner": "USERNAME_REDACTED", "account_no": "ACCOUNT_NO_REDACTED", "amount": 980, "status": "paid" }

Comparison steps:

  1. Object 102 is registered in the table of user A. The owner field belongs to user A.
  2. The same request with the A cookie returns the same body. No body difference exists.
  3. The same request with no cookie returns 401. The data is not public.
  4. The request ran twice, and both responses showed the same fields.
  5. The row moves from candidate to finding, because all five answers point the same way.

Keep this example as a template in your own lab notes. Request, response, comparison, and repeat record belong in the same file for each finding.

False-positive catalog#

Each case below carries a one-line explanation.

  • Soft-deleted record: A deleted object returns a short status body instead of full data, and the length gap reads as an access gap.
  • Different object size: Two normal records from the same endpoint differ in length because row counts differ.
  • Cached response: An intermediate layer serves a stale body, and the current access check never reaches the response.
  • Uniform error page: All denials share one body, so a real denial cannot be told apart from a routing error.
  • Tenant filter only on the parent query: The main record is filtered but the related list is fetched wide, so neighbor tenant data appears in the body.

Keep this catalog next to the scan table. When closing a candidate, note which catalog entry it matched.

Finding record format#

Fill the same fields for each confirmed finding. A record with missing fields stays out of review.

FieldContent
ComponentTarget endpoint and object type
VersionVersion label of the lab target
PreconditionsAccounts A and B plus required objects
ReproductionNumbered request order
ObservedRedacted response summary
ImpactWhich data was readable by whom
LimitationsCases left untested
RemediationServer-side check proposal

Filled example#

  • Component: GET /invoice/:id endpoint, invoice record read.
  • Version: lab target label lab-2026-10-05, local install.
  • Preconditions: Account A owns invoices 101 and 102. Account B holds a valid session with no rights over 102.
  • Reproduction:
    1. Send GET /invoice/102 with the A cookie in Repeater, save the 200 and the distinguishing field.
    2. Send the same request with the B cookie, observe the 200 and the same distinguishing field.
    3. Send the same request with no cookie, confirm the 401.
    4. Send the B request a second time, confirm the same response.
  • Observed: The body under the B cookie showed the account_no field of record 102. The value is redacted: ACCOUNT_NO_REDACTED.
  • Impact: Valid but unauthorized user B could read an invoice record owned by user A.
  • Limitations: Write endpoints stayed untested. Other roles and share links sit outside this record.
  • Remediation: Add a server-side ownership check before object read, treat the access check as the control instead of hard-to-guess IDs, and reduce error responses to one form.

Rate and permission limits#

Intruder Community runs throttled; that is politeness toward the target, not an obstacle. Keep thread counts low even in your own lab, and never scan someone else's target without written permission. The rule is simple: every bulk-requested target needs authorization.

Community throttle and a short Turbo Intruder note#

Intruder in the Community edition queues requests and slows the flow. That behavior suits careful work against a target.

  1. That pace fits small ID lists. Lists of 10 to 30 IDs need no extra tool.
  2. Keep the pool at one request with a short pause. Keep the same setting even against your own lab target.
  3. Turbo Intruder enters the picture only in two cases: a long list and written permission for the target.
  4. The permission rule stays the same for Turbo Intruder. A different tool never changes the scope.
  5. Finish baseline and filter rules before adding pace. A fast but blind scan only adds triage load.

When Turbo Intruder is used, first try a short list, compare the results table against normal Intruder output, then move to the full list. That comparison shows that requests trigger the same server behavior.

Permission and scope notes#

Bulk requests carry more responsibility than single requests.

  1. Every bulk-requested target needs written authorization. Verbal approval is not enough.
  2. Send no requests to out-of-scope endpoints. Permission covers only the named host and named endpoints.
  3. Work against your own lab target. The host in the address bar must match the host in the permission note.
  4. Keep a log of each scan: date, target host, ID list range, cookie owner, result file.
  5. Redact personal data when storing responses. Never place raw bodies in shared folders.
  6. Invalidate session cookies when testing ends. Do not leave old cookies active, even on a lab target.

These notes bind as much as the technical steps. Logs show later what was tested.

Short result#

The setup has three parts: baseline recording, ID scanning with B's cookie, single-request confirmation in Repeater. Intruder compresses the matrix from hours of work into a minutes-long candidate list; the human still makes the authorization call.

Close the run with three files in one folder: the baseline record, the classified scan table, and the filled finding form. The next scan reuses the same ID list and the same question list, so results stay comparable.

---
Share this post:

What do you think?

React to show your appreciation