Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/mapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
sarif-report: mapi.sarif
html-report: mapi.html
target: forallsecure-demo/mapi-node-example/node
duration: 6001

- name: Shut down API
run: pgrep node | xargs kill || true; sleep 5
Expand All @@ -64,7 +65,7 @@ jobs:
flags: vulnerability-tests
fail_ci_if_error: true

- name: Archive Mayhem for API report
- name: Archive Mayhem for API report
uses: actions/upload-artifact@v3
with:
name: mapi-report
Expand All @@ -73,4 +74,4 @@ jobs:
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: mapi.sarif
sarif_file: mapi.sarif
41 changes: 41 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const express = require('express');
const sqlite3 = require('sqlite3').verbose(); // Verbose for easier debugging
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;

Expand All @@ -22,7 +24,46 @@ db.run('CREATE TABLE users (email TEXT, password TEXT)', (err) => {
app.get('/', (req, res) => {
res.send('Hello, World!');
});

// Login endpoint (Unsafe)
app.get('/login', (req, res) => {
const { email, password } = req.query;

Check warning

Code scanning / CodeQL

Sensitive data read from GET request

[Route handler](1) for GET requests uses query parameter as sensitive data.

if (!email || !password) {
return res.status(400).send('Email and password are required');
}
const query = `SELECT * FROM users WHERE email = '${email}' and password = '${password}'`;

db.get(query, [], (err, row) => {

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources

This query string depends on a [user-provided value](1). This query string depends on a [user-provided value](2).
if (err) {
return res.status(500).send(`{"error": "${err.stack}"}`);

Check warning

Code scanning / CodeQL

Exception text reinterpreted as HTML

[Exception text](1) is reinterpreted as HTML without escaping meta-characters. [Exception text](2) is reinterpreted as HTML without escaping meta-characters.
}
return res.send('Login successful');
});
});
Comment on lines +29 to +43

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [a database access](1), but is not rate-limited.


// Vulnerable attachment endpoint
app.get('/attachment/:name', (req, res) => {
// This line directly takes the user input and appends it to the directory path
const attachmentName = req.params.name;
const attachmentPath = path.join(__dirname, 'attachments', attachmentName);

// Check if file exists
if (!fs.existsSync(attachmentPath)) {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).
return res.status(404).send('Attachment not found');
}

// Read the file and send it in the response
fs.readFile(attachmentPath, (err, data) => {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).
if (err) {
return res.status(500).send('Error reading file');
}
res.setHeader('Content-Type', 'text/plain');
res.send(data);
});
});
Comment on lines +47 to +65

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [a file system access](1), but is not rate-limited. This route handler performs [a file system access](2), but is not rate-limited.

const server = app.listen(port, () => {
console.log(`Listening at http://localhost:${port}`);
});
Expand Down
1 change: 1 addition & 0 deletions attachments/a
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
1 change: 1 addition & 0 deletions attachments/test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
1 change: 1 addition & 0 deletions attachments/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World
68 changes: 67 additions & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,70 @@ paths:
type: string
example: Hello, World!


/login:
get:
summary: User login
description: "Allows user to log in (Note: this is an unsafe method and not recommended for production use)."
parameters:
- in: query
name: email
required: true
description: User's email
schema:
type: string
format: email
- in: query
name: password
required: true
description: User's password
schema:
type: string
responses:
'200':
description: Login successful
content:
text/html:
schema:
type: string
example: "<p>Login successful</p>"
'400':
description: Bad request, parameters missing or invalid
content:
text/html:
schema:
type: string
example: "<p>Bad request</p>"

/attachment/{name}:
get:
summary: Retrieve attachment
description: "Endpoint to retrieve an attachment by name. Warning: This endpoint is vulnerable to path traversal attacks and is only for demo purposes."
parameters:
- in: path
name: name
required: true
description: The name of the attachment to retrieve, such as "test.txt"
schema:
type: string
responses:
'200':
description: Attachment retrieved successfully
content:
text/plain:
schema:
type: string
example: "Contents of the file..."
'400':
description: Bad request, parameters missing or invalid
content:
text/html:
schema:
type: string
example: "<p>Bad request</p>"
'404':
description: Attachment not found
content:
text/html:
schema:
type: string
example: "<p>Attachment not found</p>"