A practical OWASP-based Java security lab with vulnerable and fixed examples.
This project is designed for Java developers who want to understand application security from the inside: through code, HTTP requests, static analysis findings, and secure fixes.
The lab follows the OWASP Top 10:2025 categories and focuses on practical secure code review scenarios in Java.
- Goals
- Tech Stack
- Project Structure
- Run with Docker
- Examples
- Completed Modules
- A01:2025 Broken Access Control
- A02:2025 Security Misconfiguration
- A03:2025 Software Supply Chain Failures
- A04:2025 Cryptographic Failures
- A05:2025 Injection
- A06:2025 Insecure Design
- A07:2025 Authentication Failures
- A08:2025 Software or Data Integrity Failures
- A09:2025 Security Logging & Alerting Failures
- A10:2025 Mishandling of Exceptional Conditions
- Static Analysis with Semgrep
- Suggested Learning Flow
- Disclaimer
- Learn OWASP Top 10 vulnerabilities from a Java developer's perspective
- Practice secure code review
- Compare vulnerable and fixed implementations
- Understand exploit scenarios and mitigations
- Learn how SAST findings are produced and triaged
- Prepare for AppSec interviews
- Java 21
- Jakarta Servlet API
- Gradle
- Tomcat
- JUnit 5
- Semgrep
- GitHub Actions
- GitHub Code Scanning3
src/main/java— vulnerable and fixed Java exampleshttp— ready-to-run HTTP requestsdocs— general secure code review notessemgrep/rules— custom Semgrep SAST rules.github/workflows— GitHub Actions workflowssemgrep.bat— Windows helper script for local Semgrep scansMakefile— Linux / WSL / macOS helper commands
The demo page links in the examples table point to a local Tomcat instance. They are available only after the application has been built and started.
Build the WAR file:
./gradlew clean warStart Tomcat with Docker Compose:
docker compose upAfter startup, open:
http://localhost:8080/owasp-java-vulnerabilities/
Stop the container with Ctrl+C, or run this from another terminal:
docker compose downThe demo page URLs below are local runtime links. If Docker Compose is not running, the links will not respond.
| OWASP Category | Status | Example | Demo page |
|---|---|---|---|
| A01:2025 Broken Access Control | Completed | IDOR with document access | http://localhost:8080/owasp-java-vulnerabilities/a01/ |
| A02:2025 Security Misconfiguration | Completed | Stack trace and internal details exposure | http://localhost:8080/owasp-java-vulnerabilities/a02/ |
| A03:2025 Software Supply Chain Failures | Completed | Unsafe template interpolation | http://localhost:8080/owasp-java-vulnerabilities/a03/ |
| A04:2025 Cryptographic Failures | Completed | Weak password hashing | http://localhost:8080/owasp-java-vulnerabilities/a04/ |
| A05:2025 Injection | Completed | SQL Injection with JDBC | http://localhost:8080/owasp-java-vulnerabilities/a05/ |
| A06:2025 Insecure Design | Completed | Missing order workflow validation | http://localhost:8080/owasp-java-vulnerabilities/a06/ |
| A07:2025 Authentication Failures | Completed | Missing brute-force protection | http://localhost:8080/owasp-java-vulnerabilities/a07/ |
| A08:2025 Software or Data Integrity Failures | Completed | Unsigned payment webhook | http://localhost:8080/owasp-java-vulnerabilities/a08/ |
| A09:2025 Security Logging & Alerting Failures | Completed | Missing audit log for role changes | http://localhost:8080/owasp-java-vulnerabilities/a09/ |
| A10:2025 Mishandling of Exceptional Conditions | Completed | Partial state update after exception | http://localhost:8080/owasp-java-vulnerabilities/a10/ |
The first completed module demonstrates an IDOR vulnerability in a document access flow.
It shows how a user can access another user's document by changing the request parameter and how to fix the issue by enforcing ownership checks on the server side.
Covered topics:
- Insecure Direct Object Reference
- Server-side authorization
- Trusted authentication context
- Ownership checks
- 404 vs 403 behavior
- Negative authorization scenarios
The second completed module demonstrates a security misconfiguration caused by verbose error handling.
It shows how a vulnerable debug endpoint can expose internal implementation details when an error occurs, including exception details, internal configuration values, and stack traces.
The fixed version logs detailed errors on the server side and returns only a generic error message to the client.
Covered topics:
- Verbose error responses
- Stack trace exposure
- Internal configuration leakage
- Safe error handling
- Server-side logging
- Generic client-facing error messages
The third completed module demonstrates a software supply chain failure caused by an outdated dependency combined with unsafe API usage.
It shows how user-controlled input can be evaluated as a template expression when passed to a powerful interpolation API.
The fixed version avoids evaluating arbitrary user-controlled templates. It uses predefined server-side templates and substitutes only known placeholder values.
Covered topics:
- Vulnerable third-party dependencies
- Unsafe template interpolation
- User-controlled input flowing into dangerous APIs
- SCA and dependency review
- Dependabot alerts and dependency updates
- Secure use of predefined templates
The fourth completed module demonstrates a cryptographic failure caused by weak password hashing.
It shows why fast unsalted hashes such as SHA-256 are not suitable for password storage and how to fix the issue by using BCrypt with a cost parameter and a random salt.
The fixed version stores a BCrypt password hash. The hash string includes the algorithm marker, cost parameter, salt, and hash value.
Covered topics:
- Weak password hashing
- Fast hashes vs password hashing algorithms
- Salt and work factor
- BCrypt password storage
- Password hash verification
- Negative SAST data-flow signal: password parameter flowing into SHA-256 hashing
The fifth completed module demonstrates SQL injection in a JDBC user search.
The vulnerable version formats a user-controlled username directly into a SQL query and executes it
through Statement. An attacker can alter the WHERE clause and retrieve records outside the
intended search.
The fixed version keeps the SQL structure constant and binds the username through a
PreparedStatement parameter.
Covered topics:
- SQL injection through string-formatted queries
- JDBC
StatementandPreparedStatement - Parameter binding
- Query structure vs query data
- Exploit verification with HTTP requests
- SAST detection of formatted SQL passed to JDBC
The sixth completed module demonstrates an insecure order workflow with no model of valid state transitions.
The vulnerable version accepts any syntactically valid order status selected by the client and
writes it directly. This allows impossible business transitions such as changing a newly created
order directly from CREATED to REFUNDED.
The fixed version uses an explicit server-side state machine. It checks the current order status, allows only defined transitions, and rejects invalid workflow changes without modifying persistent state.
Covered topics:
- Business logic and workflow vulnerabilities
- Syntactic validation vs business rule validation
- Server-side state machines
- Explicit allowed state transitions
- HTTP
409 Conflictfor invalid workflow changes - Atomic check-and-update operations
- SAST review signals and the limits of automated design analysis
The seventh completed module demonstrates an authentication endpoint that does not restrict repeated password attempts.
The vulnerable version verifies every password candidate without tracking previous failures. An automated attacker can continue a password spraying sequence until a valid password is found.
The fixed version applies temporary account-based and client-based throttling before password
verification. After three failed attempts, further requests receive 429 Too Many Requests with
a Retry-After header. Both versions use the same BCrypt credential verification so the example
stays focused on authentication attempt limiting rather than password storage.
Covered topics:
- Brute-force and password spraying attacks
- Account-based and client-based attempt limiting
- Temporary cooldowns and account lockout tradeoffs
- Generic authentication error messages
- Username enumeration through response behavior and timing
- HTTP
429 Too Many RequestsandRetry-After - SAST review signals for missing authentication throttling
The eighth completed module demonstrates a payment webhook endpoint that accepts external payment confirmation data without verifying message authenticity.
The vulnerable version parses a JSON webhook and marks an order as paid when the payload looks valid. An attacker can forge the same JSON directly and change trusted payment state.
The fixed version verifies an X-Webhook-Signature HMAC-SHA256 signature over the exact raw
request body before parsing and applying the event. Unsigned requests and requests modified after
signing are rejected.
Covered topics:
- External event authenticity
- Data integrity checks before trusted state changes
- HMAC-SHA256 webhook signatures
- Constant-time signature comparison
- Raw body verification before JSON parsing
- Tampering after signing
- SAST review signals for unsigned webhook handlers
The ninth completed module demonstrates a role-management endpoint that performs a security-sensitive authorization change without recording an audit event.
The vulnerable version changes a user's role and returns a successful response, but leaves no security audit trail. After an incident, responders cannot determine who changed the role, when it happened, from which source, or whether rejected attempts preceded the successful change.
The fixed version records successful and rejected role-change attempts with actor, target account, old role, requested role, result, reason, source IP, and timestamp.
Covered topics:
- Security audit logging for sensitive actions
- Privilege-change investigation context
- Successful and rejected event recording
- Source attribution
- Audit log visibility in demos
- Alerting signals for suspicious authorization changes
- SAST review signals for missing audit events
The tenth completed module demonstrates a gift-card redemption workflow that catches an exception after several state changes have already happened.
The vulnerable version redeems a gift card and applies an order discount before sending a receipt. If receipt generation fails, the API returns a failure response but leaves the gift card consumed and the discount applied. The operation failed from the caller's perspective, but trusted domain state was partially updated.
The fixed version handles the exceptional condition at the same boundary that owns the state changes. If a later step fails, it compensates the earlier updates by removing the discount and restoring the gift card to an active state.
Covered topics:
- Partial state updates after exceptions
- Broad failure handling without compensation
- Gift card consumption and retry safety
- Explicit rollback for multi-step workflows
- Consistent state after transient failures
- HTTP
409 Conflictfor failed compensated operations - SAST review signals for missing rollback logic
This project includes custom Semgrep rules for educational SAST demonstrations.
The goal is to show how insecure coding patterns can be detected before running the application.
The repository intentionally contains vulnerable examples, so Semgrep findings are expected.
Custom rules are stored in:
semgrep/rules
Current rule coverage:
| OWASP case | Purpose |
|---|---|
| A01:2025 Broken Access Control | Detect suspicious object lookup by id inside servlet code |
| A02:2025 Security Misconfiguration | Detect unsafe exception handling patterns |
| A03:2025 Software Supply Chain Failures | Detect HTTP input flowing into unsafe template interpolation |
| A04:2025 Cryptographic Failures | Detect password parameters flowing into SHA-256 based password hashing |
| A05:2025 Injection | Detect dynamically formatted SQL executed through JDBC Statement |
| A06:2025 Insecure Design | Flag state updates without explicit workflow transition checks for review |
| A07:2025 Authentication Failures | Flag credential verification without login attempt limiting |
| A08:2025 Software or Data Integrity Failures | Flag webhook state changes without signature verification |
| A09:2025 Security Logging & Alerting Failures | Flag sensitive role changes without audit logging |
| A10:2025 Mishandling of Exceptional Conditions | Flag caught exceptional conditions after state changes without rollback |
The detailed finding messages are available directly in Semgrep output and GitHub Code Scanning reports.
Semgrep is executed through Docker, so a local Semgrep installation is not required.
Windows PowerShell:
.\semgrep.bat scanValidate rules:
.\semgrep.bat validateGenerate JSON report:
.\semgrep.bat jsonGenerate SARIF report:
.\semgrep.bat sarifLinux / WSL / macOS:
make semgrepValidate rules:
make semgrep-validateGenerate JSON report:
make semgrep-jsonGenerate SARIF report:
make semgrep-sarifLocal reports are generated in:
build/semgrep/
Expected files:
build/semgrep/semgrep.json
build/semgrep/semgrep.sarif
These files are local build artifacts and should not be committed.
This project uses GitHub Actions to run Semgrep and upload the SARIF report to GitHub Code Scanning.
Workflow file:
.github/workflows/semgrep.yml
The workflow runs on:
- push to
mainormaster - pull requests
- manual
workflow_dispatch
After the workflow runs, findings can be viewed in GitHub:
Repository → Security → Code scanning
The pipeline does not fail on Semgrep findings because this repository intentionally contains vulnerable code for educational purposes.
Dependency vulnerabilities are handled separately from custom Semgrep code rules.
This project uses:
- GitHub Dependabot alerts for vulnerable dependencies;
- Dependabot version updates for dependency upgrade pull requests;
- Dependency Review Action for pull request dependency checks.
Semgrep is used for code-level review signals, while Dependabot/SCA is used for dependency-level vulnerability detection.
For each module:
- Read the vulnerable implementation.
- Run the HTTP requests from the
httpdirectory. - Observe the insecure behavior.
- Run Semgrep locally.
- Review Semgrep findings.
- Compare vulnerable and fixed implementations.
- Study the mitigation.
- Check how the same finding appears in GitHub Code Scanning.
This project is for educational purposes only.
The vulnerable examples are intentionally insecure and must not be used in production.