Skip to main content

Overview

Client-side JavaScript security controls can never be fully trusted. Any code sent to the browser can be:
  • Analyzed using developer tools
  • Manipulated by modifying variables and functions
  • Bypassed by directly calling backend APIs
  • Reverse engineered even when obfuscated
This module demonstrates why security decisions must never be made client-side. JavaScript is useful for user experience but should never be the sole enforcement mechanism for security policies.

Objective

Submit the phrase “success” to win each level. Each level implements different client-side protections that must be analyzed and bypassed.

Server-Side Validation Logic

All levels validate the submission server-side (/vulnerabilities/javascript/index.php:34-72):
if ($_SERVER['REQUEST_METHOD'] == "POST") {
    if (array_key_exists ("phrase", $_POST) && array_key_exists ("token", $_POST)) {
        $phrase = $_POST['phrase'];
        $token = $_POST['token'];

        if ($phrase == "success") {
            switch( dvwaSecurityLevelGet() ) {
                case 'low':
                    if ($token == md5(str_rot13("success"))) {
                        $message = "<p style='color:red'>Well done!</p>";
                    } else {
                        $message = "<p>Invalid token.</p>";
                    }
                    break;
                case 'medium':
                    if ($token == strrev("XXsuccessXX")) {
                        $message = "<p style='color:red'>Well done!</p>";
                    } else {
                        $message = "<p>Invalid token.</p>";
                    }
                    break;
                case 'high':
                    if ($token == hash("sha256", hash("sha256", "XX" . strrev("success")) . "ZZ")) {
                        $message = "<p style='color:red'>Well done!</p>";
                    } else {
                        $message = "<p>Invalid token.</p>";
                    }
                    break;
            }
        } else {
            $message = "<p>You got the phrase wrong.</p>";
        }
    }
}
```bash

**Key Points**:
- Server validates both `phrase` and `token`
- Each level has different token generation algorithm
- Token must match expected value derived from "success"

## Vulnerability Analysis by Security Level

### Low Security

**Implementation**: All JavaScript embedded in HTML page

**Source Code** (`/vulnerabilities/javascript/source/low.php:2-24`):

```javascript
<script>
// MD5 implementation code (truncated for brevity)
// ...

function rot13(inp) {
    return inp.replace(/[a-zA-Z]/g,function(c){
        return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);
    });
}

function generate_token() {
    var phrase = document.getElementById("phrase").value;
    document.getElementById("token").value = md5(rot13(phrase));
}

generate_token();
</script>
How It Works:
  1. Takes phrase from input field
  2. Applies ROT13 cipher: successfhpprff
  3. Calculates MD5 hash of ROT13’d phrase
  4. Sets hidden token field
Expected Token Calculation:
rot13("success") = "fhpprff"
md5("fhpprff") = "38581812b435834ebf84ebcc2c6424d6"
```javascript

**Attack Method 1: Browser Console**

1. Open Developer Tools (F12)
2. Navigate to Console tab
3. Change phrase value:
```javascript
document.getElementById("phrase").value = "success";
  1. Regenerate token:
generate_token();
```javascript
5. Submit form

**Attack Method 2: Direct Token Calculation**

```javascript
// In browser console
function rot13(inp) {
    return inp.replace(/[a-zA-Z]/g,function(c){
        return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);
    });
}

document.getElementById("phrase").value = "success";
document.getElementById("token").value = md5(rot13("success"));
document.forms[0].submit();
Attack Method 3: Direct POST Request
curl -X POST \
  -d "phrase=success&token=38581812b435834ebf84ebcc2c6424d6&send=Submit" \
  -b "PHPSESSID=abc123; security=low" \
  http://dvwa.local/vulnerabilities/javascript/
```bash

### Medium Security

**Implementation**: JavaScript in external minified file

**Source Code** (`/vulnerabilities/javascript/source/medium.js:1`):

```javascript
function do_something(e){for(var t="",n=e.length-1;n>=0;n--)t+=e[n];return t}setTimeout(function(){do_elsesomething("XX")},300);function do_elsesomething(e){document.getElementById("token").value=do_something(e+document.getElementById("phrase").value+"XX")}
De-minified Version:
function do_something(e) {
    for(var t="", n=e.length-1; n>=0; n--) {
        t += e[n];
    }
    return t;
}

setTimeout(function() {
    do_elsesomething("XX")
}, 300);

function do_elsesomething(e) {
    document.getElementById("token").value = do_something(
        e + document.getElementById("phrase").value + "XX"
    );
}
```text

**How It Works**:
1. `do_something()` reverses a string
2. After 300ms delay, calls `do_elsesomething("XX")`
3. Concatenates: `"XX" + phrase + "XX"`
4. Reverses the result
5. Sets as token

**Expected Token Calculation**:
```javascript
Input: "success"
Concatenation: "XX" + "success" + "XX" = "XXsuccessXX"
Reverse: "XXsseccusXX"
Attack Method 1: Browser Dev Tools Pretty Print
  1. Open Developer Tools (F12) → Sources tab
  2. Open medium.js
  3. Click {} (Pretty Print button) to de-minify
  4. Read the code to understand logic
  5. In Console:
document.getElementById("phrase").value = "success";
do_elsesomething("XX");
document.forms[0].submit();
```javascript

**Attack Method 2: Manual Token Calculation**

```javascript
// Reverse function
function reverse(s) {
    return s.split('').reverse().join('');
}

phrase = "success";
token = reverse("XX" + phrase + "XX");
// token = "XXsseccusXX"

document.getElementById("phrase").value = phrase;
document.getElementById("token").value = token;
document.forms[0].submit();
Attack Method 3: Direct POST
curl -X POST \
  -d "phrase=success&token=XXsseccusXX&send=Submit" \
  -b "PHPSESSID=abc123; security=medium" \
  http://dvwa.local/vulnerabilities/javascript/
```python

**Python Script**:
```python
import requests

url = 'http://dvwa.local/vulnerabilities/javascript/'
cookies = {'PHPSESSID': 'session_id', 'security': 'medium'}

phrase = "success"
token = ("XX" + phrase + "XX")[::-1]  # Reverse string

data = {
    'phrase': phrase,
    'token': token,
    'send': 'Submit'
}

response = requests.post(url, data=data, cookies=cookies)
if 'Well done' in response.text:
    print('Success!')

High Security

Implementation: Heavily obfuscated JavaScript Source Code: Referenced from /vulnerabilities/javascript/source/high.js (obfuscated) The code has been obfuscated using multiple packers:
  1. Dan’s Tools JavaScript Obfuscator
  2. JavaScript Obfuscator Tool
Deobfuscation Approach:
  1. Use online deobfuscator: http://deobfuscatejavascript.com/
  2. Browser interception: Replace obfuscated JS with deobfuscated version
  3. Dynamic analysis: Use debugger to step through execution
Expected Token Algorithm (from server-side code):
$token == hash("sha256", hash("sha256", "XX" . strrev("success")) . "ZZ")
```text

**Token Calculation Logic**:
```javascript
// Step 1: Reverse "success"
reversed = "sseccus"

// Step 2: Prepend "XX"
step1 = "XX" + "sseccus" = "XXsseccus"

// Step 3: SHA256 hash
hash1 = sha256("XXsseccus")

// Step 4: Append "ZZ"
step2 = hash1 + "ZZ"

// Step 5: SHA256 hash again
token = sha256(step2)
Attack Method 1: Source Code Analysis
  1. View page source, find <script src=".../high.js"></script>
  2. Download high.js
  3. Paste into http://deobfuscatejavascript.com/
  4. Read deobfuscated code
  5. Identify three functions that need to be called
Attack Method 2: Network Interception
  1. Use Burp Suite or browser DevTools
  2. Intercept response for high.js
  3. Replace with deobfuscated version
  4. Forward to browser
  5. Analyze readable code
  6. Call necessary functions in console
Attack Method 3: Direct Calculation
// Using CryptoJS library (included in page)
function calculateToken(phrase) {
    // Step 1: Reverse phrase
    var reversed = phrase.split('').reverse().join('');
    
    // Step 2: Prepend XX
    var step1 = "XX" + reversed;
    
    // Step 3: First SHA256
    var hash1 = CryptoJS.SHA256(step1).toString();
    
    // Step 4: Append ZZ
    var step2 = hash1 + "ZZ";
    
    // Step 5: Second SHA256
    var token = CryptoJS.SHA256(step2).toString();
    
    return token;
}

document.getElementById("phrase").value = "success";
document.getElementById("token").value = calculateToken("success");
document.forms[0].submit();
```bash

**Attack Method 4: Python Script with hashlib**

```python
import hashlib
import requests

def calculate_token(phrase):
    # Reverse phrase
    reversed_phrase = phrase[::-1]
    
    # Prepend XX
    step1 = "XX" + reversed_phrase
    
    # First SHA256
    hash1 = hashlib.sha256(step1.encode()).hexdigest()
    
    # Append ZZ
    step2 = hash1 + "ZZ"
    
    # Second SHA256
    token = hashlib.sha256(step2.encode()).hexdigest()
    
    return token

url = 'http://dvwa.local/vulnerabilities/javascript/'
cookies = {'PHPSESSID': 'session_id', 'security': 'high'}

phrase = "success"
token = calculate_token(phrase)

data = {
    'phrase': phrase,
    'token': token,
    'send': 'Submit'
}

response = requests.post(url, data=data, cookies=cookies)
if 'Well done' in response.text:
    print('Success! Token was:', token)

Impossible Security

Implementation: No client-side validation possible Source Code (/vulnerabilities/javascript/source/impossible.php:1): The file is empty - no JavaScript is loaded. Message Displayed (/vulnerabilities/javascript/index.php:75-84):
if ( dvwaSecurityLevelGet() == "impossible" ) {
    $page[ 'body' ] = <<<EOF
    <div class="body_padded">
        <h1>Vulnerability: JavaScript Attacks</h1>
        <div class="vulnerable_code_area">
        <p>
            You can never trust anything that comes from the user or prevent them 
            from messing with it and so there is no impossible level.
        </p>
EOF;
}
```bash

**Key Principle**: 
> "You can never trust anything that comes from the user or prevent them from messing with it."

This level demonstrates that **client-side security is impossible**. Any JavaScript code can be:
- Read by viewing source
- Modified in browser DevTools
- Bypassed by direct API calls
- Deobfuscated using tools

## Defense Recommendations

### 1. Never Trust Client-Side Validation

```javascript
// BAD: Client-side only
function validateInput(data) {
    if (data.length < 10) {
        alert('Invalid input');
        return false;
    }
    return true;
}

// GOOD: Client-side for UX, server-side for security
function validateInput(data) {
    // Client-side: Quick feedback
    if (data.length < 10) {
        showError('Input too short');
        return false;
    }
    // Server will validate again
    return true;
}

2. Server-Side Validation Always

// Server must validate everything
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Never trust client data
    $input = $_POST['data'];
    
    // Validate server-side
    if (strlen($input) < 10) {
        http_response_code(400);
        die('Invalid input');
    }
    
    // Process only after validation
    processData($input);
}
```bash

### 3. Don't Rely on Obfuscation

```javascript
// BAD: Security through obscurity
var secret = "admin_password_123";
if (eval(atob("dXNlci5pc0FkbWlu"))) {
    doAdminStuff();
}

// GOOD: No secrets in JavaScript
// Check permissions server-side
fetch('/api/admin/action', {
    method: 'POST',
    credentials: 'include'
})
.then(response => {
    // Server validates if user is admin
    if (response.ok) {
        doAdminStuff();
    }
});

4. Use JavaScript for UX, Not Security

// GOOD: Client-side for better UX
form.addEventListener('submit', function(e) {
    e.preventDefault();
    
    // Show loading indicator
    showLoading();
    
    // Submit to server
    fetch('/api/submit', {
        method: 'POST',
        body: new FormData(form)
    })
    .then(response => response.json())
    .then(data => {
        // Server validated and responded
        if (data.success) {
            showSuccess();
        } else {
            showError(data.error);
        }
    })
    .finally(() => {
        hideLoading();
    });
});
```bash

### 5. Token Generation Server-Side

```php
// Generate CSRF token server-side
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>

<form method="POST">
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>" />
    <input type="text" name="data" />
    <input type="submit" />
</form>

<?php
// Validate server-side
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        die('Invalid CSRF token');
    }
    // Process request
}

6. API Authentication

// Client-side: Include auth token
fetch('/api/sensitive-operation', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': getCsrfToken()  // From server
    },
    credentials: 'include',  // Include session cookie
    body: JSON.stringify(data)
})
```bash

```php
// Server-side: Validate everything
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Check authentication
    if (!isAuthenticated()) {
        http_response_code(401);
        die('Unauthorized');
    }
    
    // Check CSRF token
    $csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $csrfToken)) {
        http_response_code(403);
        die('Invalid CSRF token');
    }
    
    // Check authorization
    if (!hasPermission('sensitive-operation')) {
        http_response_code(403);
        die('Forbidden');
    }
    
    // Only now process request
    performSensitiveOperation();
}

Client-Side Security Anti-Patterns

1. Price/Amount in Hidden Fields

<!-- BAD: User can modify price -->
<form method="POST" action="/checkout">
    <input type="hidden" name="product_id" value="123" />
    <input type="hidden" name="price" value="99.99" />
    <input type="submit" value="Buy Now" />
</form>
```bash

Attacker can change price to `0.01` in DevTools.

### 2. Role/Permission Checks in JavaScript

```javascript
// BAD: User can modify this
if (user.role === 'admin') {
    showAdminPanel();
}
Attacker can set user.role = 'admin' in console.

3. License Validation in JavaScript

// BAD: Can be bypassed
if (checkLicense(licenseKey)) {
    enableFeatures();
}
```bash

Attacker can just call `enableFeatures()` directly.

### 4. File Type Validation Client-Only

```javascript
// BAD: Can be bypassed
if (!file.name.endsWith('.pdf')) {
    alert('Only PDF files allowed');
    return;
}
Attacker can upload any file via direct POST request.

Deobfuscation Tools & Techniques

Online Tools

Browser DevTools

  1. Pretty Print: Click {} in Sources tab
  2. Debugger: Set breakpoints, step through code
  3. Console: Call functions, inspect variables

Manual Analysis

// Obfuscated
eval(function(p,a,c,k,e,d){...})

// Instead of eval, use console.log to see deobfuscated code
console.log(function(p,a,c,k,e,d){...})
```bash

### Common Obfuscation Patterns
```javascript
// String encoding
eval(atob("YWxlcnQoMSk="))  // Base64

// Character encoding
String.fromCharCode(97,108,101,114,116,40,49,41)  // alert(1)

// Hex encoding
"\x61\x6c\x65\x72\x74\x28\x31\x29"  // alert(1)

Key Takeaways

  1. JavaScript runs in hostile environment: User has full control
  2. Never make security decisions client-side: Always validate server-side
  3. Obfuscation ≠ Security: Can always be deobfuscated
  4. Use JS for UX: Form validation, animations, interactivity
  5. Server is source of truth: All important logic server-side
  6. Assume client is compromised: Design accordingly
  7. No secrets in client code: API keys, passwords, logic

References

Build docs developers (and LLMs) love