Web Application Vulnerability Lab v2

MicroSim 42 -- Advanced OWASP Exploitation & Detection (Expanded Edition)

Difficulty: Intermediate-Expert Duration: 90-120 min Chapters: 30, 35, 44
100% simulated environment. No real systems are attacked. All data is synthetic (RFC 5737/1918 IPs, *.example.com hosts). Every vulnerability is paired with detection and prevention guidance. Educational use only.
Deserialization XXE Insecure Randomness SSRF Broken Access Control OWASP Top 10 Detection Engineering
Overall Progress:
0 / 5 levels completed
Total Score
0
Points earned across all levels
Java -- Vulnerable Endpoint (UserPrefs.java)
// VULNERABLE: Deserializes untrusted user input directly @PostMapping("/api/preferences") public ResponseEntity loadPrefs(@RequestBody byte[] data) { ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(data)); // No type validation -- any serialized object accepted! UserPrefs prefs = (UserPrefs) ois.readObject(); return ResponseEntity.ok(prefs); }
Python -- Vulnerable Endpoint (views.py)
# VULNERABLE: pickle.loads on untrusted input import pickle, base64 @app.route('/load-session', methods=['POST']) def load_session(): data = base64.b64decode(request.form['session_data']) # Deserializes arbitrary objects -- RCE possible! session_obj = pickle.loads(data) return jsonify(session_obj.to_dict())
Vulnerability Analysis

What Is Insecure Deserialization?

Deserialization converts structured data back into objects. When applications deserialize untrusted input without validation, attackers can inject malicious objects that execute code during the deserialization process.

Impact

Remote Code Execution (RCE), denial of service, authentication bypass, privilege escalation. OWASP ranks this as A08:2021 (Software and Data Integrity Failures).

Attack Vectors

  • Java: Gadget chains (Commons Collections, Spring, etc.)
  • Python: pickle/shelve with __reduce__ method
  • .NET: BinaryFormatter, TypeNameHandling in JSON.NET
  • PHP: unserialize() with magic methods
https://app.example.com/api/preferences

User Preferences API

Submit serialized preferences object (Base64 encoded).

Try these simulated payloads:

  • Normal: eyJ0aGVtZSI6ImRhcmsiLCJsYW5nIjoiZW4ifQ== (valid prefs)
  • Malicious: rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA... (simulated RCE gadget)
  • Python pickle: gASVKwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOU... (os.system)
Java gadget chains abuse classes already on the classpath. The object's readObject() method triggers code execution during deserialization. In Python, pickle's __reduce__ method can call arbitrary functions like os.system().
Server Console & Analysis

Server Log Output

-- Submit a payload to see server-side processing --

Object Graph

-- Object inspection will appear here --
Java -- Secure Implementation
// SECURE: Use allowlist-based deserialization filter (JEP 290) @PostMapping("/api/preferences") public ResponseEntity loadPrefs(@RequestBody byte[] data) { ObjectInputFilter filter = ObjectInputFilter.Config .createFilter("com.example.model.UserPrefs;!*"); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(data)); ois.setObjectInputFilter(filter); // Only UserPrefs class allowed -- all others rejected UserPrefs prefs = (UserPrefs) ois.readObject(); return ResponseEntity.ok(prefs); } // BETTER: Avoid native serialization entirely -- use JSON @PostMapping("/api/preferences") public ResponseEntity loadPrefs(@RequestBody UserPrefsDTO dto) { // Jackson only maps declared fields -- no gadget chains UserPrefs prefs = mapper.convertValue(dto, UserPrefs.class); return ResponseEntity.ok(prefs); }
Python -- Secure Implementation
# SECURE: Never use pickle for untrusted data import json from pydantic import BaseModel class SessionData(BaseModel): user_id: int theme: str language: str @app.route('/load-session', methods=['POST']) def load_session(): # Parse JSON with strict schema validation data = SessionData(**request.get_json()) return jsonify(data.dict())
Prevention Checklist

Best Practices

  • Never deserialize untrusted data with native serialization
  • Use JSON/XML with strict schema validation instead
  • Java: Enable JEP 290 deserialization filters
  • Python: Use json.loads() instead of pickle.loads()
  • Implement integrity checks (HMAC) on serialized data
  • Monitor for known gadget chain classes on classpath
  • Use SCA tools to detect vulnerable libraries

Testing Tools

  • ysoserial (Java gadget chain generator -- test only)
  • peas (Python exploitation tool -- test only)
  • Burp Suite Deserialization Scanner extension
KQL -- Deserialization Anomaly Detection
SecurityAlert | where AlertName has "deserialization" | union ( W3CIISLog | where csUriStem has_any ("/api/preferences", "/load-session") | where csBytes > 10000 | extend SuspiciousSize = "Oversized serialized payload" ) | project TimeGenerated, cIP, csUriStem, csBytes
SPL -- Splunk Detection
index=web sourcetype=access_combined | where uri_path IN ("/api/preferences", "/load-session") | where bytes_in > 10000 | eval risk=if(bytes_in>50000,"critical","high") | stats count by src_ip, uri_path, risk
SIEM Integration

Detection Indicators

  • Java magic bytes: AC ED 00 05 (0xACED) in HTTP body
  • Base64-encoded payloads starting with rO0AB
  • Python pickle magic: \x80\x04\x95
  • Unusually large POST bodies to API endpoints
  • Process spawning from web server (post-exploitation)

YARA Rule Snippet

rule Java_Serialized_Object { meta: description = "Detects Java serialized objects in HTTP traffic" strings: $magic = { AC ED 00 05 } $b64 = "rO0AB" ascii condition: any of them }
Java -- Vulnerable XML Parser
// VULNERABLE: Default XML parser allows external entities @PostMapping(value="/api/import", consumes="application/xml") public ResponseEntity importData(@RequestBody String xml) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // No security features enabled! DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new InputSource( new StringReader(xml))); // Entity references resolved -- file disclosure possible String name = doc.getElementsByTagName("name") .item(0).getTextContent(); return ResponseEntity.ok("Imported: " + name); }
Python -- Vulnerable XML Parser
# VULNERABLE: lxml with resolve_entities=True (default) from lxml import etree @app.route('/import', methods=['POST']) def import_data(): parser = etree.XMLParser() # entities resolved by default tree = etree.fromstring(request.data, parser) name = tree.find('name').text return f"Imported: {name}"
Vulnerability Analysis

What Is XXE?

XML External Entity injection abuses XML parsers that resolve external entities. Attackers craft XML documents with entity declarations that reference local files, internal URLs, or trigger denial-of-service via recursive expansion (Billion Laughs).

Impact

Local file disclosure, SSRF to internal services, denial of service, port scanning. OWASP A05:2021 (Security Misconfiguration).

Common Targets

  • SOAP/XML APIs without hardened parsers
  • File upload accepting SVG, DOCX, XLSX (XML-based)
  • SAML SSO implementations
  • RSS/Atom feed processors
https://app.example.com/api/import

Data Import (XML)

Upload XML data to import records into the system.

Try these attack payloads:

  • File Disclosure: Read /etc/passwd (simulated)
  • SSRF: Access internal metadata endpoint
  • Billion Laughs: Entity expansion DoS
Define an external entity in the DOCTYPE that references a file path. Then use &entityname; in the XML body. The parser will resolve the entity and include the file contents in the response.
Server Response & Analysis

Parsed XML Output

-- Submit XML to see parser output --

Entity Resolution Log

-- Entity expansion details --
Java -- Secure XML Parser Configuration
// SECURE: Disable DTD and external entities DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new InputSource(new StringReader(xml)));
Python -- Secure XML Parser
# SECURE: Use defusedxml library import defusedxml.ElementTree as ET @app.route('/import', methods=['POST']) def import_data(): # defusedxml blocks DTD, entities, and external refs tree = ET.fromstring(request.data) name = tree.find('name').text return f"Imported: {name}" # Alternative: lxml with hardened settings parser = etree.XMLParser( resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False )
Prevention Checklist

Best Practices

  • Disable DTD processing in all XML parsers
  • Disable external entity resolution
  • Use defusedxml (Python), secure defaults (Java 17+)
  • Prefer JSON over XML where possible
  • Validate XML against strict XSD schemas
  • Inspect SVG/DOCX uploads for embedded entities
  • WAF rules to block DOCTYPE declarations in input
KQL -- XXE Detection
W3CIISLog | where csContentType has "xml" | where csUriQuery has_any ("DOCTYPE", "ENTITY", "SYSTEM") or csBytes > 50000 // Possible entity expansion | extend AttackType = "Potential XXE" | project TimeGenerated, cIP, csUriStem, csBytes // WAF Rule -- Block DOCTYPE in XML input SecRule REQUEST_BODY "@rx (?i)<!DOCTYPE" \ "id:2001,phase:2,deny,msg:'XXE Attempt - DOCTYPE detected'"
SPL -- Splunk XXE Detection
index=web sourcetype=access_combined | where content_type="application/xml" | where match(request_body, "(?i)DOCTYPE|ENTITY|SYSTEM") | eval severity="critical" | stats count by src_ip, uri_path
SIEM Indicators

Detection Signals

  • <!DOCTYPE in XML POST body
  • <!ENTITY declarations referencing SYSTEM or PUBLIC
  • References to file://, http://, ftp://
  • Abnormally large XML responses (entity expansion)
  • Outbound connections from XML parser processes

Sigma Rule

title: XXE Injection Attempt logsource: category: webserver detection: selection: cs-content-type|contains: 'xml' request_body|contains: - '<!DOCTYPE' - '<!ENTITY' - 'SYSTEM' condition: selection level: high
Python -- Weak Token Generation
# VULNERABLE: Using math.random for security tokens import random, time def generate_session_token(): # Seeded with current timestamp -- predictable! random.seed(int(time.time())) token = ''.join( random.choice('abcdef0123456789') for _ in range(16) ) return token def generate_reset_code(): # Only 6 digits, sequential seed -- trivially predictable random.seed(int(time.time())) return str(random.randint(100000, 999999))
JavaScript -- Weak Token Generation
// VULNERABLE: Math.random() is NOT cryptographically secure function generateToken() { let token = ''; for (let i = 0; i < 16; i++) { token += Math.floor(Math.random() * 16).toString(16); } return token; } // Predictable: Same seed = same sequence // Browsers share PRNG state across tabs
Vulnerability Analysis

What Is Insecure Randomness?

Using non-cryptographic pseudo-random number generators (PRNGs) for security-sensitive values like session tokens, password reset codes, CSRF tokens, or API keys. These PRNGs have predictable output given the seed value.

Impact

Session hijacking, account takeover via predictable reset codes, CSRF bypass, authentication bypass. OWASP A02:2021 (Cryptographic Failures).

Why Math.random() Fails

  • Deterministic output given the same seed
  • Limited internal state (often 48-bit)
  • Previous outputs leak information about future outputs
  • Some implementations use timestamp as seed
https://app.example.com/token-analysis

Token Predictability Analyzer

Compare weak PRNG vs cryptographic token generation.

Generated Tokens

Click a button above to generate tokens

Entropy Analysis

Low entropy (predictable) High entropy (secure)

Generate tokens to analyze entropy

Password Reset Code Predictor

Simulated: if you know the approximate timestamp, you can predict the reset code.

Weak PRNGs seeded with time are predictable. If the attacker knows when a token was generated (within a few seconds), they can reproduce the seed and predict the token. Try generating weak tokens and notice the pattern.
Analysis Console

PRNG State Analysis

-- Generate tokens to see analysis --

Token Comparison

PropertyWeak PRNGCSPRNG
Seed SourceTimestampOS entropy pool
Output PredictabilityDeterministicUnpredictable
Bits of Entropy~32 (timestamp)128-256
Brute Force TimeSecondsHeat death of universe
Python -- Secure Token Generation
# SECURE: Use secrets module (CSPRNG) import secrets def generate_session_token(): # 32 bytes = 256 bits of entropy return secrets.token_hex(32) def generate_reset_code(): # Cryptographically random 6-digit code return str(secrets.randbelow(900000) + 100000) def generate_api_key(): # URL-safe random token return secrets.token_urlsafe(32)
JavaScript -- Secure Token Generation
// SECURE: Use crypto.getRandomValues (Web Crypto API) function generateSecureToken(length = 32) { const array = new Uint8Array(length); crypto.getRandomValues(array); return Array.from(array, b => b.toString(16).padStart(2, '0') ).join(''); } // Node.js equivalent const crypto = require('crypto'); const token = crypto.randomBytes(32).toString('hex');
Prevention Checklist

Best Practices

  • Always use CSPRNG for security tokens
  • Python: secrets module (not random)
  • JavaScript: crypto.getRandomValues()
  • Java: SecureRandom (not java.util.Random)
  • Minimum 128 bits of entropy for session tokens
  • Password reset tokens: use at least 256 bits + expiry
  • Never use timestamps as sole seed for security values
  • Code review: grep for Math.random, random.seed, java.util.Random in security paths
KQL -- Token Predictability Detection
// Detect sessions with low-entropy tokens SigninLogs | extend TokenLength = strlen(SessionId) | where TokenLength < 32 | summarize Count=count() by AppDisplayName, TokenLength | where Count > 10 // Detect sequential/similar session tokens SigninLogs | sort by TimeGenerated asc | serialize | extend PrevToken = prev(SessionId) | extend Similarity = jaccard_index(SessionId, PrevToken) | where Similarity > 0.8 | project TimeGenerated, UserPrincipalName, Similarity
Static Analysis -- Code Grep
# Find insecure random usage in codebase grep -rn "Math.random\|random.seed\|random.randint\|java.util.Random" \ --include="*.py" --include="*.js" --include="*.java" \ src/ lib/ # Semgrep rule for insecure randomness rules: - id: insecure-random patterns: - pattern: random.randint(...) message: "Use secrets module for security-sensitive random values" severity: WARNING
Testing Methodology

Token Entropy Test

  • Collect 100+ tokens from target application
  • Calculate Shannon entropy per character position
  • Check for sequential patterns or time correlation
  • Attempt prediction using known PRNG algorithms
  • Verify token length meets minimum (128-bit)

Automated Tools

  • Burp Suite Sequencer (entropy analysis)
  • NIST SP 800-22 randomness tests
  • dieharder statistical test suite
Python -- Vulnerable URL Fetch
# VULNERABLE: Fetches arbitrary URLs provided by user import requests @app.route('/api/fetch-url') def fetch_url(): url = request.args.get('url') # No validation -- can access internal services! response = requests.get(url) return response.text # Attacker can request: # /api/fetch-url?url=http://169.254.169.254/latest/meta-data/ # /api/fetch-url?url=http://10.0.0.1:8080/admin # /api/fetch-url?url=file:///etc/passwd
Node.js -- Vulnerable PDF Generator
// VULNERABLE: Renders user-supplied URL to PDF app.post('/api/screenshot', async (req, res) => { const { url } = req.body; // No URL validation! const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); // SSRF vector const pdf = await page.pdf(); res.send(pdf); });
Vulnerability Analysis

What Is SSRF?

Server-Side Request Forgery tricks the server into making HTTP requests to internal resources that should not be accessible from the internet. The server acts as a proxy, bypassing firewalls and network segmentation.

Impact

Access to cloud metadata (AWS/GCP/Azure credentials), internal admin panels, port scanning, data exfiltration. OWASP A10:2021 (Server-Side Request Forgery).

Simulated Network Layout

Attacker (Internet) Web Server (192.0.2.10) Internal API (10.0.0.5:8080)

Web Server Metadata (169.254.169.254)

Web Server Database (10.0.0.20:5432)
https://app.example.com/api/fetch-url?url=

URL Preview Service

Enter a URL to generate a preview card for social sharing.

Try these targets:

  • Cloud Metadata: http://169.254.169.254/latest/meta-data/
  • Internal Admin: http://10.0.0.5:8080/admin/users
  • Internal DB: http://10.0.0.20:5432/
  • Local File: file:///etc/hostname
  • Bypass: http://0x0a000005:8080/admin (hex IP)
Cloud providers expose metadata at 169.254.169.254. This contains IAM credentials, instance details, and sometimes secrets. Internal services on 10.x.x.x are normally unreachable from the internet but the server can reach them.
Server-Side Request Log

Outbound Request

-- Submit a URL to see the server-side request --

Response Received

-- Server response will appear here --

Network Security Assessment

-- Assessment will appear after request --
Python -- Secure URL Fetch
# SECURE: URL allowlist + private IP blocking import ipaddress, urllib.parse, requests ALLOWED_SCHEMES = {'http', 'https'} ALLOWED_DOMAINS = {'example.com', 'cdn.example.com'} def is_safe_url(url): parsed = urllib.parse.urlparse(url) if parsed.scheme not in ALLOWED_SCHEMES: return False if parsed.hostname not in ALLOWED_DOMAINS: return False # Resolve hostname and check for private IPs try: ip = ipaddress.ip_address( socket.gethostbyname(parsed.hostname)) if ip.is_private or ip.is_loopback or ip.is_link_local: return False except: return False return True @app.route('/api/fetch-url') def fetch_url(): url = request.args.get('url') if not is_safe_url(url): return jsonify({'error': 'URL not allowed'}), 403 response = requests.get(url, timeout=5) return response.text
Prevention Checklist

Best Practices

  • Allowlist permitted domains and URL schemes
  • Block all private/reserved IP ranges (RFC 1918, link-local)
  • Resolve hostnames before request; re-check IP after DNS
  • Disable HTTP redirects or re-validate after redirect
  • Block file://, gopher://, dict:// schemes
  • Network-level: block metadata endpoint (169.254.169.254) from app tier
  • Use IMDSv2 (token-required) on AWS instances
  • Deploy egress firewall rules for web servers

Cloud Provider Mitigations

  • AWS: Enable IMDSv2, use VPC endpoints
  • GCP: Use metadata concealment, Workload Identity
  • Azure: Use managed identity with restricted scope
KQL -- SSRF Detection
// Detect outbound requests to metadata endpoints AzureNetworkAnalytics_CL | where DestIP_s == "169.254.169.254" | project TimeGenerated, SrcIP_s, DestIP_s, DestPort_d | extend AlertType = "SSRF - Metadata Access Attempt" // Detect outbound to private IPs from web tier AzureNetworkAnalytics_CL | where SrcIP_s has_any ("192.0.2.10") // web server | where ipv4_is_private(DestIP_s) | where DestPort_d in (80, 443, 8080, 5432, 3306) | project TimeGenerated, SrcIP_s, DestIP_s, DestPort_d
SPL -- Splunk SSRF Detection
index=web sourcetype=access_combined | where match(uri_query, "url=http://(10\.|172\.(1[6-9]|2|3[01])\.|192\.168\.|169\.254\.)") | eval attack_type="SSRF" | stats count by src_ip, uri_query, attack_type
WAF & Network Rules

WAF Rule

SecRule ARGS:url "@rx (?i)(169\.254\.|10\.\d|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.0\.0\.1|localhost|0x|file://)" \ "id:3001,phase:2,deny,msg:'SSRF Attempt'"

Network Segmentation

  • Egress firewall: web tier can only reach approved external IPs
  • Block 169.254.169.254 at iptables/security group level
  • Use separate VPC/subnet for web tier vs internal services
  • Monitor DNS queries for internal hostname resolution
IDOR -- Insecure Direct Object Reference
# VULNERABLE: No authorization check on resource access @app.route('/api/invoice/<int:invoice_id>') def get_invoice(invoice_id): # Only checks authentication, NOT authorization if not current_user.is_authenticated: return jsonify({'error': 'Login required'}), 401 # Any authenticated user can access ANY invoice! invoice = Invoice.query.get(invoice_id) return jsonify(invoice.to_dict())
Privilege Escalation -- Parameter Tampering
# VULNERABLE: Role set by client-side parameter @app.route('/api/user/update-profile', methods=['POST']) def update_profile(): data = request.get_json() user = User.query.get(current_user.id) # Accepts ANY field from request -- including 'role'! for key, value in data.items(): setattr(user, key, value) db.session.commit() return jsonify({'status': 'updated'}) # Attacker sends: {"name":"Evil","role":"admin"}
Vulnerability Analysis

What Is Broken Access Control?

Occurs when applications fail to enforce authorization rules, allowing users to access resources or perform actions beyond their intended permissions. This is OWASP #1 risk: A01:2021.

Common Patterns

  • IDOR: Changing /invoice/123 to /invoice/456 accesses another user's data
  • Privilege Escalation: Modifying role/permission parameters in requests
  • Forced Browsing: Accessing /admin without admin check
  • Missing Function-Level Access Control: API endpoints without authorization
  • Metadata Manipulation: Modifying JWT claims, cookies, hidden fields
https://app.example.com/api/invoice/1001

Invoice Portal

Logged in as: jdoe (Role: user) | Your invoices: 1001, 1002

IDOR Test

Privilege Escalation Test

Try adding "role": "admin" to the JSON body

For IDOR: Your invoices are 1001 and 1002. Try IDs like 1003-1010 to access other users' invoices. For privilege escalation: add "role":"admin" to the JSON payload and observe that the server blindly applies all fields.
Authorization Analysis

Request Details

-- Make a request to see details --

Authorization Check

-- Authorization analysis will appear here --

Access Control Matrix

ResourceOwnerjdoe Access
Invoice 1001jdoeAuthorized
Invoice 1002jdoeAuthorized
Invoice 1003asmithUnauthorized
Invoice 1004bwilsonUnauthorized
Invoice 1005cjonesUnauthorized
IDOR Fix -- Server-Side Authorization
# SECURE: Check resource ownership before access @app.route('/api/invoice/<int:invoice_id>') @login_required def get_invoice(invoice_id): invoice = Invoice.query.get_or_404(invoice_id) # Authorization: verify ownership if invoice.user_id != current_user.id \ and not current_user.has_role('admin'): abort(403) return jsonify(invoice.to_dict()) # Even better: scope queries to current user def get_invoice_safe(invoice_id): invoice = Invoice.query.filter_by( id=invoice_id, user_id=current_user.id ).first_or_404() return jsonify(invoice.to_dict())
Privilege Escalation Fix -- Field Allowlist
# SECURE: Allowlist permitted fields for update ALLOWED_PROFILE_FIELDS = {'name', 'email', 'timezone'} @app.route('/api/user/update-profile', methods=['POST']) @login_required def update_profile(): data = request.get_json() user = User.query.get(current_user.id) # Only update permitted fields for key, value in data.items(): if key in ALLOWED_PROFILE_FIELDS: setattr(user, key, value) else: # Log suspicious field manipulation attempt logger.warning(f"Mass assignment attempt: {key}") db.session.commit() return jsonify({'status': 'updated'})
Prevention Checklist

Best Practices

  • Always check authorization server-side, not just authentication
  • Use UUIDs instead of sequential IDs for external-facing resources
  • Scope database queries to current user's permissions
  • Implement RBAC/ABAC with a central authorization service
  • Allowlist fields for mass assignment (never blindly set from input)
  • Deny by default; explicitly grant access per endpoint
  • Log all authorization failures for anomaly detection
  • Automated testing: DAST tools to enumerate object IDs
KQL -- Access Control Violation Detection
// Detect IDOR: user accessing resources outside their scope AppRequests | where Url matches regex @"/api/invoice/\d+" | extend InvoiceId = extract(@"/invoice/(\d+)", 1, Url) | join kind=leftouter ( InvoiceOwnership_CL | project InvoiceId, OwnerId ) on InvoiceId | where UserId != OwnerId | project TimeGenerated, UserId, InvoiceId, OwnerId // Detect privilege escalation attempts AppRequests | where Url has "update-profile" | where RequestBody has_any ("role", "admin", "is_staff", "permission") | extend AlertType = "Mass Assignment / Privilege Escalation Attempt" | project TimeGenerated, UserId, RequestBody
SPL -- Splunk Detection
index=web sourcetype=access_combined uri_path="/api/invoice/*" | rex field=uri_path "/invoice/(?<invoice_id>\d+)" | lookup invoice_owners invoice_id OUTPUT owner_id | where user_id != owner_id | stats count by user_id, invoice_id, owner_id | where count > 3 | Threshold for alert
Behavioral Analytics

IDOR Detection Patterns

  • User accessing sequential resource IDs rapidly
  • 403/404 spike from single user (enumeration)
  • Access to resources outside user's organizational unit
  • Unusual time-of-day access patterns

Privilege Escalation Indicators

  • Role/permission fields in profile update requests
  • User accessing admin endpoints without admin role
  • Sudden change in user's API access patterns after profile update

Related Nexus SecOps Content

Chapter 30: Application Security Chapter 44: Web Application Pentesting Chapter 35: DevSecOps Pipeline Lab 20: Web App Pentesting Lab 25: API Security Testing MicroSim 40: Web App Vulnerability Lab v1 (SQLi, XSS, CSRF)