# Web application security testing

Test browser behavior, server sessions and file-processing boundaries together. Distinguish scanner output from a validated finding.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## Scope and session lifecycle

Test authorization should identify the environment, accounts, data boundary, window and stop contact. URL tokens may reach history, logs and referrers. Cookie presence alone is insufficient: the server session must be active and unexpired. Retry with the old session after logout and record behavior across tabs and concurrent sessions.

## CSRF and content boundaries

Do not design state-changing GET requests. For cookie sessions, assess SameSite alongside method, exact origin and CSRF checks. CORS is not authorization. For uploads, neither extension nor the client Content-Type is authoritative; bound size, decode the actual format with a controlled parser, and separate serving from the application origin.

## From finding to retest

A finding needs prerequisites, minimal reproduction, expected/actual behavior and business impact. Redact sample data and exclude live session material. After correction, rerun the original case, a nearby negative variant and the legitimate workflow. WSTG 4.2 is the stable testing guide; ASVS 5.0 supports verification requirements. Automated scanning does not cover either in full.

## Worked example: a denied mutation

The local HTTP test establishes a valid fictional session, then sends PATCH with a wrong origin and a wrong CSRF value. Both must return 403, and a later read must show the original title and version. Correct CSRF cannot authorize another user’s record: the object boundary still applies. Try normal use in the browser and inspect denials and body limits in the automated tests. There is no upload endpoint in this fixture; its upload policy model prepares you to examine an actual parser. Treat scanner alerts as candidates until minimal reproduction and data invariants are demonstrated within scope.

## Working sequence

1. Prepare written scope and two distinct test accounts.
2. Trace login, renewal and logout.
3. Test CSRF and upload boundaries.
4. Capture minimal finding evidence.
5. Retest the fix with negative and normal flows.

## 1. Session revocation

A URL token or old cookie cannot replace a valid server session. The model denies a revoked session. It does not implement token signatures or actual browser cookie policy; those require integration tests.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>Boolean(s.cookie||s.queryToken)
```

### Corrected implementation
```js
s=>Boolean(s.cookie&&s.serverSession&&s.serverSession.active)
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "server-session",
      "input": {
        "cookie": "fixture-cookie",
        "queryToken": "",
        "serverSession": {
          "active": true
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "url-token",
      "input": {
        "cookie": "",
        "queryToken": "fixture-token",
        "serverSession": null
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "revoked",
      "input": {
        "cookie": "fixture-cookie",
        "queryToken": "",
        "serverSession": {
          "active": false
        }
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "server-session",
      "input": {
        "cookie": "fixture-cookie",
        "queryToken": "",
        "serverSession": {
          "active": true
        }
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "url-token",
      "input": {
        "cookie": "",
        "queryToken": "fixture-token",
        "serverSession": null
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "revoked",
      "input": {
        "cookie": "fixture-cookie",
        "queryToken": "",
        "serverSession": {
          "active": false
        }
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Cross-origin request

Being logged in does not prove the user initiated a request. Require exact origin, valid CSRF and the correct method together. The downloadable API actually denies PATCH requests with a wrong origin or CSRF value.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>s.authenticated
```

### Corrected implementation
```js
s=>s.authenticated&&s.method==='POST'&&s.origin===s.expectedOrigin&&s.csrfValid
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "authorized-post",
      "input": {
        "authenticated": true,
        "method": "POST",
        "origin": "https://harbor.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "cross-origin",
      "input": {
        "authenticated": true,
        "method": "POST",
        "origin": "https://other.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "state-changing-get",
      "input": {
        "authenticated": true,
        "method": "GET",
        "origin": "https://harbor.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "authorized-post",
      "input": {
        "authenticated": true,
        "method": "POST",
        "origin": "https://harbor.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "cross-origin",
      "input": {
        "authenticated": true,
        "method": "POST",
        "origin": "https://other.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "state-changing-get",
      "input": {
        "authenticated": true,
        "method": "GET",
        "origin": "https://harbor.example",
        "expectedOrigin": "https://harbor.example",
        "csrfValid": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. A file named like an image

A PNG extension cannot make HTML content safe. The model requires separate decoding, size and authorization evidence. It does not decode files or run malware scanning; test the actual organizational parser separately.

This code is a local policy model; it does not replace target/device testing.

### Weak implementation
```js
s=>s.extension==='.png'
```

### Corrected implementation
```js
s=>s.extension==='.png'&&s.detectedType==='image/png'&&s.decoded&&s.bytes<=1024&&s.authorized
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "bounded-image",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 800,
        "authorized": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "renamed-content",
      "input": {
        "extension": ".png",
        "detectedType": "text/html",
        "decoded": false,
        "bytes": 800,
        "authorized": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "oversize",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 2000,
        "authorized": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "unauthorized",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 800,
        "authorized": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "bounded-image",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 800,
        "authorized": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "renamed-content",
      "input": {
        "extension": ".png",
        "detectedType": "text/html",
        "decoded": false,
        "bytes": 800,
        "authorized": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "oversize",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 2000,
        "authorized": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "unauthorized",
      "input": {
        "extension": ".png",
        "detectedType": "image/png",
        "decoded": true,
        "bytes": 800,
        "authorized": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Session lifecycle matrix
- Finding, fixed revision and retest evidence

## References

- [OWASP WSTG 4.2 stable](https://owasp.org/projects/web-security-testing-guide)
- [OWASP ASVS 5.0.0](https://asvs.owasp.org/)
