diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..196def68
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+mysql-data/
+.git/
+target/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..5fbd561e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+mysql-data/
+target/
+*.war
+*.class
+.idea/
+*.iml
+.vscode/
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..a73d7d18
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,31 @@
+# Stage 1: Build Stage
+FROM eclipse-temurin:8-jdk AS build
+
+WORKDIR /app
+
+# Copy the source code into the Docker image
+COPY . .
+
+# Install Maven and JDK, then build the project
+RUN apt-get update && \
+ apt-get install -y maven && \
+ mvn clean package && \
+ mvn dependency:copy-dependencies -DincludeArtifactIds=mysql-connector-java -DoutputDirectory=/app/shared-libs
+
+# Stage 2: Runtime Stage
+FROM tomcat:7.0.82
+
+# Copy the WAR file built in the previous stage
+COPY --from=build /app/target/*.war /usr/local/tomcat/webapps/
+
+# Copy the JDBC driver into Tomcat's shared lib so the pool DataSource (declared
+# in META-INF/context.xml and loaded by the container classloader) can find it.
+COPY --from=build /app/shared-libs/*.jar /usr/local/tomcat/lib/
+
+# Copy the pre-prepared tomcat-users.xml to set up user roles
+COPY default-tomcat.xml /usr/local/tomcat/conf/tomcat-users.xml
+
+ENV CATALINA_OPTS="-Xms256m -Xmx1024m"
+
+# CMD to start Tomcat
+CMD ["catalina.sh", "run"]
diff --git a/LICENSE b/LICENSE
index 23cb7903..d6a93266 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
- GNU GENERAL PUBLIC LICENSE
+GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
@@ -337,3 +337,4 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+
diff --git a/README.md b/README.md
index 8560b23a..5e12e78a 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,132 @@
-This is Vulnerable Web Application developed for course by Cyber Security and Privacy Foundation
-(www.cysecurity.org) for Java programmers
+Java Vulnerable Lab
+===================
-Get the VulnerableSpring Project from here:
-https://github.com/breakthesec/VulnerableSpring
+A deliberately vulnerable Java web application from the [Cyber Security and Privacy Foundation](https://www.cysecurity.org). It is built for Java developers and anyone else who wants to find real web application vulnerabilities, exploit them, and then read the source to understand why they were possible and how to fix them.
-----------------------------------
-The full course on Hacking and Securing Web Java Programs is available in
------------------------------------
-https://www.udemy.com/hacking-securing-java-web-programming/
+Every challenge is mapped to a category from the [OWASP Top 10:2025](https://owasp.org/Top10/2025/), and each vulnerable page carries a comment explaining the flaw and the fix.
-----------------------------------
-VirtualBox VM can be found here:
-----------------------------------
-http://sourceforge.net/projects/javavulnerablelab/files/v0.1/JavaVulnerableLab.ova/download
+**Warning:** this application is intentionally insecure. Several challenges give remote code execution inside the container. Running it locally with Docker on your own machine is fine. Do not expose it to the internet and never deploy it on a public facing or production server.
+
+One thing to be aware of: Docker publishes port 9080 on all interfaces, so anyone on the same network can reach the lab. If you are on shared or untrusted wifi, change the port mapping in `docker-compose.yml` to `127.0.0.1:9080:8080` to keep it on your machine only.
+
+Quick start (Docker)
+--------------------
+
+This is the supported setup and the one that matches the current code.
+
+ 1. Install Docker and Docker Compose: https://docs.docker.com/engine/install/
+ 2. From this directory, run: docker compose up --build
+ 3. Wait for Tomcat to finish starting.
+ 4. Open http://localhost:9080/JavaVulnerableLab/install.jsp
+ 5. Click Install. The defaults are already correct for the Compose setup.
+ 6. Open http://localhost:9080/JavaVulnerableLab/ and start with the Vulnerability menu.
+
+The app is published on port **9080** so it does not collide with anything else you may already have on 8080. Inside the container Tomcat still listens on 8080, which matters for a couple of the challenges.
+
+MySQL data persists in `./mysql-data`. To start over from a clean database, stop the stack, delete that directory, and re-run the install step.
+
+Default logins
+--------------
+
+Created by `install.jsp`. All passwords are stored in plaintext, which is one of the lessons.
+
+| Username | Password | Role |
+| --------- | -------- | ----- |
+| admin | admin | admin |
+| victim | victim | user |
+| attacker | attacker | user |
+| NEO | trinity | user |
+| trinity | NEO | user |
+| Anderson | java | user |
+| mule | mule | user |
+
+The admin username and password are whatever you typed on the install page, and default to `admin` / `admin`.
+
+What is inside
+--------------
+
+Challenges are reachable from the **Vulnerability** menu in the top navigation, grouped by OWASP Top 10:2025 category.
+
+**A01 Broken Access Control**
+Insecure direct object references (viewing and modifying another user's profile), path traversal in the document download, missing function level access control on the admin pages, privilege escalation by tampering with an unverified JWT claim, privilege escalation via a trusted cookie, CSRF over both GET and POST, server-side request forgery, and open redirect and forward.
+
+**A02 Security Misconfiguration**
+The setup page left deployed, unchanged default admin credentials, directory listing enabled on `/backup/`, debug mode left on in production, and XML external entity processing.
+
+**A03 Software Supply Chain Failures**
+Log4Shell (CVE-2021-44228) through a bundled Log4j 2.12.1, plus a companion Spring application in [VulnerableSpring](https://github.com/CSPF-Founder/VulnerableSpring).
+
+**A04 Cryptographic Failures**
+Card data sent in cleartext, passwords stored in plaintext, credentials written to a cookie, and MD5 password hashing.
+
+**A05 Injection**
+SQL injection (error based, blind, union, and authentication bypass), OS command injection, XPath injection, XSLT injection, ORM injection, and reflected, stored and Flash-based cross-site scripting.
+
+**A06 Insecure Design**
+Account recovery through a guessable security question, unrestricted file upload leading to a web shell, and an OTP step-up flow whose result is decided by the client.
+
+**A07 Authentication Failures**
+Login with no rate limiting or lockout, username enumeration, password change without reauthentication, session fixation via URL rewriting, and backend API keys hard-coded into front-end JavaScript.
+
+**A08 Software or Data Integrity Failures**
+Insecure Java deserialization of a client-supplied view state, reachable with a ysoserial CommonsCollections gadget, and a third-party script loaded with no Subresource Integrity.
+
+**A09 Security Logging and Alerting Failures**
+Log injection that forges audit trail entries and attributes an action to another user.
+
+**A10 Mishandling of Exceptional Conditions**
+An unhandled exception leaking a stack trace, an access check that fails open when its input is missing, and a wallet transfer with no rollback that corrupts balances.
+
+Other installation methods
+--------------------------
+
+**These are not maintained and predate the OWASP Top 10:2025 rewrite.** The prebuilt VM image, the standalone JAR and the released WAR on SourceForge are all older than the current code and will not contain the newer challenges. Use Docker unless you have a specific reason not to. They are kept here for reference.
+
+
+VirtualBox VM (outdated)
+
+ 1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
+ 2. Download the VM image: http://sourceforge.net/projects/javavulnerablelab/files/v0.1/JavaVulnerableLab.ova/download
+ 3. Import JavaVulnerableLab.ova into VirtualBox.
+ 4. Set the network to Host-Only.
+ 5. Start the machine and log in (username: root, password: cspf).
+ 6. Run "service tomcat start" and "service mysql start".
+ 7. Find the IP address of the machine.
+ 8. Open http://[VM_IP]:8080/JavaVulnerableLab/install.jsp and click Install.
+
+
+
+
+Standalone JAR with embedded Tomcat (outdated)
+
+ 1. Install a JDK.
+ 2. Download: http://sourceforge.net/projects/javavulnerablelab/files/v0.2/JavaVulnerableLab.jar/download
+ 3. Run: java -jar JavaVulnerableLab.jar
+ 4. Open http://localhost:8080/JavaVulnerableLab/install.jsp and click Install.
+
+
+
+
+WAR file on your own Tomcat (outdated)
+
+ 1. Install Apache Tomcat.
+ 2. Go to http://[TOMCAT_IP]:8080/manager/ (edit tomcat-users.xml to allow manager access first).
+ 3. Download: https://sourceforge.net/projects/javavulnerablelab/files/latest/JavaVulnerableLab.war/download
+ 4. Deploy the WAR through the manager.
+ 5. Open http://[TOMCAT_IP]:8080/JavaVulnerableLab/install.jsp and click Install.
+
+
+
+To build a current WAR from source instead, run `mvn clean package` and deploy `target/JavaVulnerableLab.war`. It needs a MySQL database and a JDK 8 build environment.
+
+Learning material
+-----------------
+
+The full course content is on GitHub for free: https://github.com/CSPF-Founder/JavaSecurityCourse
+
+The full course on hacking and securing Java web programs: https://learn.cysecurity.org/course/view.php?id=3
+
+License
+-------
+
+GNU General Public License v2. See [LICENSE](LICENSE).
diff --git a/build.xml b/build.xml
deleted file mode 100644
index 841659cb..00000000
--- a/build.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Builds, tests, and runs the project JavaVulnerableLab.
-
-
-
diff --git a/default-tomcat.xml b/default-tomcat.xml
new file mode 100644
index 00000000..a225d5e4
--- /dev/null
+++ b/default-tomcat.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..fb13ef9c
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,25 @@
+
+services:
+ jvl:
+ image: cspf/jvl
+ build:
+ dockerfile: ./Dockerfile
+ context: ./
+ ports:
+ - 9080:8080
+ links:
+ - mysql
+
+ mysql:
+ image: mysql:5.7
+ environment:
+ MYSQL_ROOT_PASSWORD: root
+ MYSQL_ROOT_HOST: "%"
+ MYSQL_DATABASE: abc
+ command:
+ - "--default-authentication-plugin=mysql_native_password"
+ - "--max_connections=500"
+ - "--wait_timeout=120"
+ - "--interactive_timeout=120"
+ volumes:
+ - ./mysql-data:/var/lib/mysql
diff --git a/nbproject/ant-deploy.xml b/nbproject/ant-deploy.xml
deleted file mode 100644
index 5ca2a427..00000000
--- a/nbproject/ant-deploy.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
deleted file mode 100644
index df502d08..00000000
--- a/nbproject/build-impl.xml
+++ /dev/null
@@ -1,1448 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must set src.dir
- Must set test.src.dir
- Must set build.dir
- Must set build.web.dir
- Must set build.generated.dir
- Must set dist.dir
- Must set build.classes.dir
- Must set dist.javadoc.dir
- Must set build.test.classes.dir
- Must set build.test.results.dir
- Must set build.classes.excludes
- Must set dist.war
-
-
-
-
-
-
-
-
-
-The Java EE server classpath is not correctly set up - server home directory is missing.
-Either open the project in the IDE and assign the server or setup the server classpath manually.
-For example like this:
- ant -Dj2ee.server.home=<app_server_installation_directory>
-
-
-The Java EE server classpath is not correctly set up. Your active server type is ${j2ee.server.type}.
-Either open the project in the IDE and assign the server or setup the server classpath manually.
-For example like this:
- ant -Duser.properties.file=<path_to_property_file> (where you put the property "j2ee.platform.classpath" in a .properties file)
-or ant -Dj2ee.platform.classpath=<server_classpath> (where no properties file is used)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must set javac.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No tests executed.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-The libs.CopyLibs.classpath property is not set up.
-This property must point to
-org-netbeans-modules-java-j2seproject-copylibstask.jar file which is part
-of NetBeans IDE installation and is usually located at
-<netbeans_installation>/java<version>/ant/extra folder.
-Either open the project in the IDE and make sure CopyLibs library
-exists or setup the property manually. For example like this:
- ant -Dlibs.CopyLibs.classpath=a/path/to/org-netbeans-modules-java-j2seproject-copylibstask.jar
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must set JVM to use for profiling in profiler.info.jvm
- Must set profiler agent JVM arguments in profiler.info.jvmargs.agent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must select some files in the IDE or set javac.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must select some files in the IDE or set javac.jsp.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must select a file in the IDE or set jsp.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Browser not found, cannot launch the deployed application. Try to set the BROWSER environment variable.
-
-
- Launching ${browse.url}
-
-
-
-
-
- Must select one file in the IDE or set run.class
-
-
-
- Must select one file in the IDE or set run.class
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must select one file in the IDE or set debug.class
-
-
-
-
-
-
-
-
-
-
-
- Must select one file in the IDE or set debug.class
-
-
-
-
- Must set fix.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This target only works when run from inside the NetBeans IDE.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Must select some files in the IDE or set javac.includes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Some tests failed; see details above.
-
-
-
-
-
-
-
-
- Must select some files in the IDE or set test.includes
-
-
-
- Some tests failed; see details above.
-
-
-
- Must select some files in the IDE or set test.class
- Must select some method in the IDE or set test.method
-
-
-
- Some tests failed; see details above.
-
-
-
-
- Must select one file in the IDE or set test.class
-
-
-
- Must select one file in the IDE or set test.class
- Must select some method in the IDE or set test.method
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
deleted file mode 100644
index cb66364d..00000000
--- a/nbproject/genfiles.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-build.xml.data.CRC32=903755fa
-build.xml.script.CRC32=8f523743
-build.xml.stylesheet.CRC32=651128d4@1.67.1.1
-# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
-# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=903755fa
-nbproject/build-impl.xml.script.CRC32=084958d7
-nbproject/build-impl.xml.stylesheet.CRC32=99ea4b56@1.67.1.1
diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties
deleted file mode 100644
index a50452d9..00000000
--- a/nbproject/private/private.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-deploy.ant.properties.file=/home/breakthesec/.netbeans/8.0/tomcat80.properties
-j2ee.server.domain=/home/breakthesec/.netbeans/8.0/apache-tomcat-8.0.3.0_base
-j2ee.server.home=/home/breakthesec/apache-tomcat-8.0.3
-j2ee.server.instance=tomcat80:home=/home/breakthesec/apache-tomcat-8.0.3:base=apache-tomcat-8.0.3.0_base
-javac.debug=true
-javadoc.preview=true
-selected.browser=default
-user.properties.file=/home/breakthesec/.netbeans/8.0/build.properties
diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
deleted file mode 100644
index 6807a2ba..00000000
--- a/nbproject/private/private.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/nbproject/project.properties b/nbproject/project.properties
deleted file mode 100644
index 6ea3de55..00000000
--- a/nbproject/project.properties
+++ /dev/null
@@ -1,88 +0,0 @@
-annotation.processing.enabled=true
-annotation.processing.enabled.in.editor=true
-annotation.processing.processors.list=
-annotation.processing.run.all.processors=true
-annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
-build.classes.dir=${build.web.dir}/WEB-INF/classes
-build.classes.excludes=**/*.java,**/*.form
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-build.web.dir=${build.dir}/web
-build.web.excludes=${build.classes.excludes}
-client.urlPart=
-compile.jsps=false
-conf.dir=${source.root}/conf
-debug.classpath=${build.classes.dir}:${javac.classpath}
-debug.test.classpath=\
- ${run.test.classpath}
-display.browser=true
-# Files to be excluded from distribution war
-dist.archive.excludes=
-dist.dir=dist
-dist.ear.war=${dist.dir}/${war.ear.name}
-dist.javadoc.dir=${dist.dir}/javadoc
-dist.war=${dist.dir}/${war.name}
-endorsed.classpath=\
- ${libs.javaee-endorsed-api-6.0.classpath}
-excludes=
-file.reference.json-20090211.jar=/media/breakthesec/Extra/GuestFolder/jar libs/json-20090211.jar
-file.reference.mysql-connector-java-5.1.33-bin.jar=../mysql-connector-java-5.1.33-bin.jar
-includes=**
-j2ee.compile.on.save=true
-j2ee.copy.static.files.on.save=true
-j2ee.deploy.on.save=true
-j2ee.platform=1.7-web
-j2ee.platform.classpath=${j2ee.server.home}/lib/annotations-api.jar:${j2ee.server.home}/lib/catalina-ant.jar:${j2ee.server.home}/lib/catalina-ha.jar:${j2ee.server.home}/lib/catalina-storeconfig.jar:${j2ee.server.home}/lib/catalina-tribes.jar:${j2ee.server.home}/lib/catalina.jar:${j2ee.server.home}/lib/ecj-4.3.1.jar:${j2ee.server.home}/lib/el-api.jar:${j2ee.server.home}/lib/jasper-el.jar:${j2ee.server.home}/lib/jasper.jar:${j2ee.server.home}/lib/jsp-api.jar:${j2ee.server.home}/lib/servlet-api.jar:${j2ee.server.home}/lib/tomcat-api.jar:${j2ee.server.home}/lib/tomcat-coyote.jar:${j2ee.server.home}/lib/tomcat-dbcp.jar:${j2ee.server.home}/lib/tomcat-i18n-es.jar:${j2ee.server.home}/lib/tomcat-i18n-fr.jar:${j2ee.server.home}/lib/tomcat-i18n-ja.jar:${j2ee.server.home}/lib/tomcat-jdbc.jar:${j2ee.server.home}/lib/tomcat-jni.jar:${j2ee.server.home}/lib/tomcat-spdy.jar:${j2ee.server.home}/lib/tomcat-util-scan.jar:${j2ee.server.home}/lib/tomcat-util.jar:${j2ee.server.home}/lib/tomcat-websocket.jar:${j2ee.server.home}/lib/websocket-api.jar
-j2ee.server.type=Tomcat
-jar.compress=false
-javac.classpath=\
- ${file.reference.mysql-connector-java-5.1.33-bin.jar}:\
- ${file.reference.json-20090211.jar}:\
- ${libs.jstl.classpath}
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.debug=true
-javac.deprecation=false
-javac.processorpath=\
- ${javac.classpath}
-javac.source=1.6
-javac.target=1.6
-javac.test.classpath=\
- ${javac.classpath}:\
- ${build.classes.dir}
-javac.test.processorpath=\
- ${javac.test.classpath}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.preview=true
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=
-lib.dir=${web.docbase.dir}/WEB-INF/lib
-persistence.xml.dir=${conf.dir}
-platform.active=default_platform
-resource.dir=setup
-run.test.classpath=\
- ${javac.test.classpath}:\
- ${build.test.classes.dir}
-# Space-separated list of JVM arguments used when running a class with a main method or a unit test
-# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value):
-runmain.jvmargs=
-source.encoding=UTF-8
-source.root=src
-src.dir=${source.root}/java
-test.src.dir=test
-war.content.additional=
-war.ear.name=${war.name}
-war.name=JavaVulnerableLab.war
-web.docbase.dir=web
-webinf.dir=web/WEB-INF
diff --git a/nbproject/project.xml b/nbproject/project.xml
deleted file mode 100644
index fe0dff12..00000000
--- a/nbproject/project.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
- org.netbeans.modules.web.project
-
-
- JavaVulnerableLab
- 1.6.5
-
-
- ${file.reference.mysql-connector-java-5.1.33-bin.jar}
- WEB-INF/lib
-
-
- ${file.reference.json-20090211.jar}
- WEB-INF/lib
-
-
- ${libs.jstl.classpath}
- WEB-INF/lib
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..03b763ee
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,85 @@
+
+ 4.0.0
+ org.cysecurity
+ JavaVulnerableLab
+ war
+ 0.0.1-SNAPSHOT
+ JavaVulnerableLab Maven Webapp
+ http://maven.apache.org
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ mysql
+ mysql-connector-java
+ 5.1.26
+
+
+ org.json
+ json
+ 20090211
+
+
+ javax.servlet
+ jstl
+ 1.2
+
+
+ org.hibernate
+ hibernate-core
+ 4.0.1.Final
+
+
+ javax.servlet
+ servlet-api
+ 2.3
+ provided
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.12.1
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.12.1
+
+
+
+ commons-collections
+ commons-collections
+ 3.1
+
+
+
+ commons-fileupload
+ commons-fileupload
+ 1.3.3
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+
+ JavaVulnerableLab
+
+
+ 1.7
+ 1.7
+
+
diff --git a/src/conf/MANIFEST.MF b/src/conf/MANIFEST.MF
deleted file mode 100644
index 59499bce..00000000
--- a/src/conf/MANIFEST.MF
+++ /dev/null
@@ -1,2 +0,0 @@
-Manifest-Version: 1.0
-
diff --git a/src/java/model/DBConnect.java b/src/java/model/DBConnect.java
deleted file mode 100644
index 9dd95fdc..00000000
--- a/src/java/model/DBConnect.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
-package model;
-
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Properties;
-
-/**
- *
- * @author breakthesec
- */
-public class DBConnect {
- public Connection connect(String path) throws IOException,ClassNotFoundException,SQLException
- {
- Properties properties=new Properties();
- properties.load(new FileInputStream(path));
- String dbuser=properties.getProperty("dbuser");
- String dbpass = properties.getProperty("dbpass");
- String dbfullurl = properties.getProperty("dburl")+properties.getProperty("dbname");
- String jdbcdriver = properties.getProperty("jdbcdriver");
- Connection con=null;
- try
- {
- Class.forName(jdbcdriver);
- con= DriverManager.getConnection(dbfullurl,dbuser,dbpass);
- return con;
- }
- finally
- {
-
- }
- }
-}
diff --git a/src/java/controller/AddPage.java b/src/main/java/org/cysecurity/cspf/jvl/controller/AddPage.java
similarity index 98%
rename from src/java/controller/AddPage.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/AddPage.java
index 0b8da423..343701b3 100644
--- a/src/java/controller/AddPage.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/AddPage.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.BufferedWriter;
import java.io.File;
diff --git a/src/main/java/org/cysecurity/cspf/jvl/controller/AvatarUpload.java b/src/main/java/org/cysecurity/cspf/jvl/controller/AvatarUpload.java
new file mode 100644
index 00000000..2e9e2365
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/AvatarUpload.java
@@ -0,0 +1,59 @@
+package org.cysecurity.cspf.jvl.controller;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+/**
+ * A06 Insecure Design (CWE-434): a "profile picture" upload whose design never
+ * restricts the file type, size, or destination. Any file, including an
+ * executable JSP, is written under the web root with its original name, so it
+ * can be requested and run as a web shell.
+ */
+public class AvatarUpload extends HttpServlet {
+
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ HttpSession session = request.getSession(false);
+ if (session == null || session.getAttribute("isLoggedIn") == null) {
+ response.sendRedirect("login.jsp");
+ return;
+ }
+
+ String uploadDir = getServletContext().getRealPath("/uploads");
+ new File(uploadDir).mkdirs();
+
+ String message = "No file received.";
+ try {
+ ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
+ List items = upload.parseRequest(request);
+ for (FileItem item : items) {
+ if (!item.isFormField() && item.getName() != null && !item.getName().isEmpty()) {
+ // VULNERABLE BY DESIGN: the client filename and extension are
+ // trusted as-is. No type / extension / content validation.
+ String fileName = new File(item.getName()).getName();
+ File stored = new File(uploadDir, fileName);
+ item.write(stored);
+ message = "Uploaded to " + request.getContextPath() + "/uploads/" + fileName;
+ }
+ }
+ } catch (Exception e) {
+ message = "Upload failed: " + e.getMessage();
+ }
+ request.setAttribute("uploadMessage", message);
+ request.getRequestDispatcher("/vulnerability/upload/avatar.jsp").forward(request, response);
+ }
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ request.getRequestDispatcher("/vulnerability/upload/avatar.jsp").forward(request, response);
+ }
+}
diff --git a/src/java/controller/EmailCheck.java b/src/main/java/org/cysecurity/cspf/jvl/controller/EmailCheck.java
similarity index 97%
rename from src/java/controller/EmailCheck.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/EmailCheck.java
index cbcf1c5f..c5edb24d 100644
--- a/src/java/controller/EmailCheck.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/EmailCheck.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -15,7 +15,7 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import model.DBConnect;
+import org.cysecurity.cspf.jvl.model.DBConnect;
import org.json.JSONObject;
/**
@@ -89,7 +89,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
- @Override
+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
diff --git a/src/java/controller/ForwardMe.java b/src/main/java/org/cysecurity/cspf/jvl/controller/ForwardMe.java
similarity index 98%
rename from src/java/controller/ForwardMe.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/ForwardMe.java
index 56cca1c5..72ee696c 100644
--- a/src/java/controller/ForwardMe.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/ForwardMe.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
diff --git a/src/java/controller/install.java b/src/main/java/org/cysecurity/cspf/jvl/controller/Install.java
similarity index 96%
rename from src/java/controller/install.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/Install.java
index ad6b9cc6..249eee35 100644
--- a/src/java/controller/install.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/Install.java
@@ -1,4 +1,4 @@
- package controller;
+ package org.cysecurity.cspf.jvl.controller;
/*
* To change this license header, choose License Headers in Project Properties.
@@ -10,7 +10,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@@ -20,13 +19,13 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import model.HashMe;
+import org.cysecurity.cspf.jvl.model.HashMe;
/**
*
* @author breakthesec
*/
-public class install extends HttpServlet {
+public class Install extends HttpServlet {
static String dburl;
static String jdbcdriver;
@@ -124,13 +123,14 @@ protected boolean setup(String i) throws IOException
if(!con.isClosed())
{
//User Table creation
- stmt.executeUpdate("Create table users(ID int NOT NULL AUTO_INCREMENT, username varchar(30),email varchar(60), password varchar(60), about varchar(50),privilege varchar(20),avatar TEXT,secretquestion int,secret varchar(30),primary key (id))");
+ stmt.executeUpdate("Create table users(ID int NOT NULL AUTO_INCREMENT, username varchar(30),email varchar(60), password varchar(60), about varchar(50),privilege varchar(20),avatar TEXT,secretquestion int,secret varchar(30),balance int NOT NULL DEFAULT 1000,primary key (id))");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('"+adminuser+"','"+adminpass+"','admin@localhost','I am the admin of this application','default.jpg','admin',1,'rocky')");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('victim','victim','victim@localhost','I am the victim of this application','default.jpg','user',1,'max')");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('attacker','attacker','attacker@localhost','I am the attacker of this application','default.jpg','user',1,'bella')");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('NEO','trinity','neo@matrix','I am the NEO','default.jpg','user',1,'sentinel')");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('trinity','NEO','trinity@matrix','it is Trinity','default.jpg','user',1,'sentinel')");
stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('Anderson','java','anderson@1999','I am computer programmer','default.jpg','user',1,'C++')");
+ stmt.executeUpdate("INSERT into users(username, password, email,About,avatar, privilege,secretquestion,secret) values ('mule','mule','mule@burner','A burner account','default.jpg','user',1,'x')");
//Posts table creation
stmt.executeUpdate("create table posts(postid int NOT NULL AUTO_INCREMENT, content TEXT,title varchar(100), user varchar(30), primary key (postid))");
@@ -224,4 +224,4 @@ public String getServletInfo() {
return "Short description";
}//
-}
+}
\ No newline at end of file
diff --git a/src/java/controller/LoginValidator.java b/src/main/java/org/cysecurity/cspf/jvl/controller/LoginValidator.java
similarity index 92%
rename from src/java/controller/LoginValidator.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/LoginValidator.java
index 81993bd9..5919cb22 100644
--- a/src/java/controller/LoginValidator.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/LoginValidator.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -17,7 +17,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import model.DBConnect;
+import org.cysecurity.cspf.jvl.model.DBConnect;
@@ -58,6 +58,9 @@ protected void processRequest(HttpServletRequest request, HttpServletResponse re
session.setAttribute("avatar", rs.getString("avatar"));
Cookie privilege=new Cookie("privilege","user");
response.addCookie(privilege);
+ Cookie jwt=new Cookie("jwt",org.cysecurity.cspf.jvl.model.JwtUtil.sign(rs.getString("username"), rs.getString("privilege")));
+ jwt.setPath(request.getContextPath());
+ response.addCookie(jwt);
if(request.getParameter("RememberMe")!=null)
{
Cookie username=new Cookie("username",user);
diff --git a/src/java/controller/Logout.java b/src/main/java/org/cysecurity/cspf/jvl/controller/Logout.java
similarity index 98%
rename from src/java/controller/Logout.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/Logout.java
index 7bd07bef..986de9b3 100644
--- a/src/java/controller/Logout.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/Logout.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
diff --git a/src/java/controller/open.java b/src/main/java/org/cysecurity/cspf/jvl/controller/Open.java
similarity index 96%
rename from src/java/controller/open.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/Open.java
index b45ff108..9cf1a268 100644
--- a/src/java/controller/open.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/Open.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -17,7 +17,7 @@
*
* @author breakthesec
*/
-public class open extends HttpServlet {
+public class Open extends HttpServlet {
/**
* Processes requests for both HTTP GET and POST
diff --git a/src/java/controller/Register.java b/src/main/java/org/cysecurity/cspf/jvl/controller/Register.java
similarity index 97%
rename from src/java/controller/Register.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/Register.java
index 4b679e3c..afa2f835 100644
--- a/src/java/controller/Register.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/Register.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -17,7 +17,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import model.DBConnect;
+import org.cysecurity.cspf.jvl.model.DBConnect;
/**
*
diff --git a/src/java/controller/SendMessage.java b/src/main/java/org/cysecurity/cspf/jvl/controller/SendMessage.java
similarity index 97%
rename from src/java/controller/SendMessage.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/SendMessage.java
index 11bfe52d..73fa79aa 100644
--- a/src/java/controller/SendMessage.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/SendMessage.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -14,7 +14,7 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import model.DBConnect;
+import org.cysecurity.cspf.jvl.model.DBConnect;
/**
*
diff --git a/src/main/java/org/cysecurity/cspf/jvl/controller/Transfer.java b/src/main/java/org/cysecurity/cspf/jvl/controller/Transfer.java
new file mode 100644
index 00000000..758201f6
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/Transfer.java
@@ -0,0 +1,89 @@
+package org.cysecurity.cspf.jvl.controller;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.cysecurity.cspf.jvl.model.DBConnect;
+
+/**
+ * Wallet transfer between accounts.
+ *
+ * VULNERABLE (A10 Mishandling of Exceptional Conditions - CWE-460, CWE-703):
+ * the recipient is credited first, then the sender is debited to settle, with no
+ * transaction and no rollback. When settlement fails (insufficient funds), the code
+ * throws to abort, but the already-committed credit is never rolled back, so money is
+ * created out of nothing and the ledger is left in a corrupt, inconsistent state.
+ */
+public class Transfer extends HttpServlet {
+
+ protected void processRequest(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ response.setContentType("text/html;charset=UTF-8");
+ String ctx = request.getContextPath();
+ try {
+ HttpSession session = request.getSession();
+ String from = (String) session.getAttribute("user");
+ String to = request.getParameter("to");
+ String amountStr = request.getParameter("amount");
+
+ if (from == null || to == null || amountStr == null || request.getParameter("send") == null) {
+ response.sendRedirect(ctx + "/vulnerability/transfer/index.jsp");
+ return;
+ }
+
+ int amount = Integer.parseInt(amountStr);
+ Connection con = new DBConnect().connect(getServletContext().getRealPath("/WEB-INF/config.properties"));
+ if (con != null && !con.isClosed()) {
+ // The recipient is credited instantly, then the transfer is "settled" by
+ // debiting the sender. There is NO transaction wrapping the two steps and
+ // NO rollback, so a failed settlement leaves the credit standing.
+
+ // Step 1: credit the recipient -- commits immediately (auto-commit is on)
+ PreparedStatement credit = con.prepareStatement(
+ "UPDATE users SET balance = balance + ? WHERE username = ?");
+ credit.setInt(1, amount);
+ credit.setString(2, to);
+ credit.executeUpdate();
+
+ // Step 2: settle by debiting the sender, only if the funds are there
+ PreparedStatement debit = con.prepareStatement(
+ "UPDATE users SET balance = balance - ? WHERE username = ? AND balance >= ?");
+ debit.setInt(1, amount);
+ debit.setString(2, from);
+ debit.setInt(3, amount);
+ if (debit.executeUpdate() != 1) {
+ // Settlement failed (insufficient funds). The code detects it and aborts,
+ // but the credit in Step 1 already committed and is never rolled back.
+ throw new IllegalStateException("settlement failed: insufficient funds");
+ }
+
+ con.close();
+ }
+ response.sendRedirect(ctx + "/vulnerability/transfer/index.jsp");
+ } catch (Exception ex) {
+ response.sendRedirect(ctx + "/vulnerability/transfer/index.jsp");
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ public String getServletInfo() {
+ return "Wallet transfer (A10 incomplete-rollback demo)";
+ }
+}
diff --git a/src/java/controller/UsernameCheck.java b/src/main/java/org/cysecurity/cspf/jvl/controller/UsernameCheck.java
similarity index 97%
rename from src/java/controller/UsernameCheck.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/UsernameCheck.java
index 231f1a81..f24eccdc 100644
--- a/src/java/controller/UsernameCheck.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/UsernameCheck.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
@@ -15,7 +15,7 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import model.DBConnect;
+import org.cysecurity.cspf.jvl.model.DBConnect;
import org.json.JSONObject;
/**
diff --git a/src/java/controller/XPathQuery.java b/src/main/java/org/cysecurity/cspf/jvl/controller/XPathQuery.java
similarity index 98%
rename from src/java/controller/XPathQuery.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/XPathQuery.java
index 0a0bb606..a50856a3 100644
--- a/src/java/controller/XPathQuery.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/XPathQuery.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.PrintWriter;
diff --git a/src/java/controller/xxe.java b/src/main/java/org/cysecurity/cspf/jvl/controller/xxe.java
similarity index 95%
rename from src/java/controller/xxe.java
rename to src/main/java/org/cysecurity/cspf/jvl/controller/xxe.java
index 9e0d61c7..f8718662 100644
--- a/src/java/controller/xxe.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/controller/xxe.java
@@ -4,7 +4,7 @@
* and open the template in the editor.
*/
-package controller;
+package org.cysecurity.cspf.jvl.controller;
import java.io.IOException;
import java.io.InputStream;
@@ -44,14 +44,14 @@ protected void processRequest(HttpServletRequest request, HttpServletResponse re
InputStream xml=request.getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
- InputSource is = new InputSource(xml);
+ InputSource is = new InputSource(xml);
Document doc = builder.parse(is);
Element element = doc.getDocumentElement();
NodeList nodes = element.getChildNodes();
out.print(" Result: ");
out.print("--------------------- ");
for (int i = 0; i < nodes.getLength(); i++) {
- out.print(nodes.item(i).getNodeName()+" : " + nodes.item(i).getTextContent());
+ out.print(nodes.item(i).getNodeName()+" : " + nodes.item(i).getFirstChild().getNodeValue().toString());
out.print(" ");
}
}
diff --git a/src/main/java/org/cysecurity/cspf/jvl/model/DBConnect.java b/src/main/java/org/cysecurity/cspf/jvl/model/DBConnect.java
new file mode 100644
index 00000000..80c4229b
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/model/DBConnect.java
@@ -0,0 +1,59 @@
+package org.cysecurity.cspf.jvl.model;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+public class DBConnect {
+
+ private static volatile DataSource pooledDataSource;
+
+ public Connection connect(String path) throws IOException, ClassNotFoundException, SQLException {
+ DataSource ds = lookupPool();
+ if (ds != null) {
+ return ds.getConnection();
+ }
+ return legacyConnect(path);
+ }
+
+ private static DataSource lookupPool() {
+ DataSource ds = pooledDataSource;
+ if (ds != null) {
+ return ds;
+ }
+ synchronized (DBConnect.class) {
+ if (pooledDataSource == null) {
+ try {
+ Context envCtx = (Context) new InitialContext().lookup("java:comp/env");
+ pooledDataSource = (DataSource) envCtx.lookup("jdbc/jvl");
+ } catch (NamingException e) {
+ return null;
+ }
+ }
+ return pooledDataSource;
+ }
+ }
+
+ private static Connection legacyConnect(String path) throws IOException, ClassNotFoundException, SQLException {
+ Properties properties = new Properties();
+ FileInputStream in = new FileInputStream(path);
+ try {
+ properties.load(in);
+ } finally {
+ in.close();
+ }
+ String dbuser = properties.getProperty("dbuser");
+ String dbpass = properties.getProperty("dbpass");
+ String dbfullurl = properties.getProperty("dburl") + properties.getProperty("dbname");
+ String jdbcdriver = properties.getProperty("jdbcdriver");
+ Class.forName(jdbcdriver);
+ return DriverManager.getConnection(dbfullurl, dbuser, dbpass);
+ }
+}
diff --git a/src/java/model/HashMe.java b/src/main/java/org/cysecurity/cspf/jvl/model/HashMe.java
similarity index 78%
rename from src/java/model/HashMe.java
rename to src/main/java/org/cysecurity/cspf/jvl/model/HashMe.java
index 5cea4546..635a180d 100644
--- a/src/java/model/HashMe.java
+++ b/src/main/java/org/cysecurity/cspf/jvl/model/HashMe.java
@@ -1,10 +1,4 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
-package model;
+package org.cysecurity.cspf.jvl.model;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
diff --git a/src/main/java/org/cysecurity/cspf/jvl/model/JwtUtil.java b/src/main/java/org/cysecurity/cspf/jvl/model/JwtUtil.java
new file mode 100644
index 00000000..bd2b33e2
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/model/JwtUtil.java
@@ -0,0 +1,80 @@
+package org.cysecurity.cspf.jvl.model;
+
+import java.nio.charset.Charset;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * Minimal HS256 JWT helper for the lab.
+ *
+ * sign() produces a genuinely signed token so that a correct verifier (see the
+ * A01 slides) would reject any tampering. decodePayload() deliberately reads the
+ * claims WITHOUT checking the signature: that omission is the vulnerability the
+ * baac/ page demonstrates, not a bug in this helper.
+ *
+ * DatatypeConverter (JAXB) is used instead of java.util.Base64 so the code runs
+ * on the Java 7 target as well as the Java 8 build JDK.
+ */
+public final class JwtUtil {
+
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ // Demo secret. A real deployment keeps a strong, rotated key server-side only.
+ private static final byte[] SECRET = "jvl-demo-hs256-secret-do-not-reuse".getBytes(UTF8);
+
+ private JwtUtil() {
+ }
+
+ public static String sign(String subject, String role) {
+ String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
+ long now = System.currentTimeMillis() / 1000L;
+ String payload = "{\"sub\":\"" + escape(subject) + "\",\"role\":\"" + escape(role)
+ + "\",\"iss\":\"jvl\",\"iat\":" + now + ",\"exp\":" + (now + 3600L) + "}";
+
+ String signingInput = base64Url(header.getBytes(UTF8)) + "." + base64Url(payload.getBytes(UTF8));
+ return signingInput + "." + base64Url(hmacSha256(signingInput.getBytes(UTF8)));
+ }
+
+ /** Returns the raw claims JSON. Does NOT verify the signature (intentional for the lab). */
+ public static String decodePayload(String token) {
+ String[] parts = token.split("\\.");
+ if (parts.length < 2) {
+ return "{}";
+ }
+ return new String(base64UrlDecode(parts[1]), UTF8);
+ }
+
+ private static byte[] hmacSha256(byte[] data) {
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(SECRET, "HmacSHA256"));
+ return mac.doFinal(data);
+ } catch (Exception e) {
+ throw new RuntimeException("HMAC failure", e);
+ }
+ }
+
+ private static String base64Url(byte[] data) {
+ return DatatypeConverter.printBase64Binary(data)
+ .replace('+', '-').replace('/', '_').replace("=", "");
+ }
+
+ private static byte[] base64UrlDecode(String s) {
+ String t = s.replace('-', '+').replace('_', '/');
+ int rem = t.length() % 4;
+ if (rem == 2) {
+ t += "==";
+ } else if (rem == 3) {
+ t += "=";
+ }
+ return DatatypeConverter.parseBase64Binary(t);
+ }
+
+ private static String escape(String s) {
+ if (s == null) {
+ return "";
+ }
+ return s.replace("\\", "\\\\").replace("\"", "\\\"");
+ }
+}
diff --git a/src/main/java/org/cysecurity/cspf/jvl/model/Preferences.java b/src/main/java/org/cysecurity/cspf/jvl/model/Preferences.java
new file mode 100644
index 00000000..c914feef
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/model/Preferences.java
@@ -0,0 +1,48 @@
+package org.cysecurity.cspf.jvl.model;
+
+import java.io.Serializable;
+
+/**
+ * User-interface preferences that the app round-trips through the client as a
+ * serialized Java object (the "view state"). The blob is deserialized back on
+ * the server, which is the sink for the insecure-deserialization demo in
+ * vulnerability/deserialize/preferences.jsp (OWASP 2025 A08, CWE-502).
+ */
+public class Preferences implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String theme;
+ private String language;
+ private int itemsPerPage;
+
+ public Preferences() {
+ this.theme = "light";
+ this.language = "en";
+ this.itemsPerPage = 20;
+ }
+
+ public String getTheme() {
+ return theme;
+ }
+
+ public void setTheme(String theme) {
+ this.theme = theme;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public int getItemsPerPage() {
+ return itemsPerPage;
+ }
+
+ public void setItemsPerPage(int itemsPerPage) {
+ this.itemsPerPage = itemsPerPage;
+ }
+}
diff --git a/src/main/java/org/cysecurity/cspf/jvl/model/orm/Users.java b/src/main/java/org/cysecurity/cspf/jvl/model/orm/Users.java
new file mode 100644
index 00000000..7c94f644
--- /dev/null
+++ b/src/main/java/org/cysecurity/cspf/jvl/model/orm/Users.java
@@ -0,0 +1,36 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.cysecurity.cspf.jvl.model.orm;
+
+/**
+ *
+ * @author breakthesec
+ */
+public class Users {
+ Long id;
+ String username;
+ String about;
+
+ public Long getId() {
+ return id;
+ }
+ private void setId(Long id) {
+ this.id = id;
+ }
+ public String getUsername() {
+ return username;
+ }
+ public void setUsername(String username) {
+ this.username = username;
+ }
+ public String getAbout() {
+ return about;
+ }
+ public void setAbout(String about) {
+ this.about = about;
+ }
+}
diff --git a/src/main/resources/Users.hbm.xml b/src/main/resources/Users.hbm.xml
new file mode 100644
index 00000000..e8f5b6a2
--- /dev/null
+++ b/src/main/resources/Users.hbm.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 00000000..ab8a49fe
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/ForgotPassword.jsp b/src/main/webapp/ForgotPassword.jsp
similarity index 97%
rename from web/ForgotPassword.jsp
rename to src/main/webapp/ForgotPassword.jsp
index ea6a01fd..b56f6cba 100644
--- a/web/ForgotPassword.jsp
+++ b/src/main/webapp/ForgotPassword.jsp
@@ -1,5 +1,5 @@
- <%@page import="model.DBConnect"%>
+ <%@page import="org.cysecurity.cspf.jvl.model.DBConnect"%>
<%@page import="java.sql.Statement"%>
<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.Connection"%>
diff --git a/src/main/webapp/META-INF/context.xml b/src/main/webapp/META-INF/context.xml
new file mode 100644
index 00000000..e3d923dd
--- /dev/null
+++ b/src/main/webapp/META-INF/context.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/web/Register.jsp b/src/main/webapp/Register.jsp
similarity index 100%
rename from web/Register.jsp
rename to src/main/webapp/Register.jsp
diff --git a/web/WEB-INF/AdminPanel.jsp b/src/main/webapp/WEB-INF/AdminPanel.jsp
similarity index 100%
rename from web/WEB-INF/AdminPanel.jsp
rename to src/main/webapp/WEB-INF/AdminPanel.jsp
diff --git a/src/main/webapp/WEB-INF/config.properties b/src/main/webapp/WEB-INF/config.properties
new file mode 100644
index 00000000..ae13b151
--- /dev/null
+++ b/src/main/webapp/WEB-INF/config.properties
@@ -0,0 +1,22 @@
+# To change this license header, choose License Headers in Project Properties.
+# To change this template file, choose Tools | Templates
+# and open the template in the editor.
+
+dbuser=root
+dbpass=root
+dbname=abc
+dburl=jdbc:mysql://mysql:3306/
+jdbcdriver=com.mysql.jdbc.Driver
+siteTitle=Java Vulnerable Lab
+
+# Developer debug mode, the same switch as Django's DEBUG and Laravel's
+# APP_DEBUG. Left as true on purpose: that IS the misconfiguration demonstrated
+# by vulnerability/debugmode/orders.jsp. Toggle from admin/Configure.jsp.
+debug=true
+
+# Third-party promo widget loaded by vulnerability/integrity/checkout.jsp,
+# both set from admin/WidgetSource.jsp. Empty widgetScriptUrl means "the demo
+# widget server on whatever host the browser used", so no host is baked in.
+# widgetScriptIntegrity is empty on purpose: no Subresource Integrity check.
+widgetScriptUrl=
+widgetScriptIntegrity=
diff --git a/web/WEB-INF/users.xml b/src/main/webapp/WEB-INF/users.xml
similarity index 100%
rename from web/WEB-INF/users.xml
rename to src/main/webapp/WEB-INF/users.xml
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..a6ee66d2
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+ Install
+ org.cysecurity.cspf.jvl.controller.Install
+
+
+ loginValidator
+ org.cysecurity.cspf.jvl.controller.LoginValidator
+
+
+ Register
+ org.cysecurity.cspf.jvl.controller.Register
+
+
+ Logout
+ org.cysecurity.cspf.jvl.controller.Logout
+
+
+ Open
+ org.cysecurity.cspf.jvl.controller.Open
+
+
+ SendMessage
+ org.cysecurity.cspf.jvl.controller.SendMessage
+
+
+ ForwardMe
+ org.cysecurity.cspf.jvl.controller.ForwardMe
+
+
+ AddPage
+ org.cysecurity.cspf.jvl.controller.AddPage
+
+
+ UsernameCheck
+ org.cysecurity.cspf.jvl.controller.UsernameCheck
+
+
+ EmailCheck
+ org.cysecurity.cspf.jvl.controller.EmailCheck
+
+
+ XPathQuery
+ org.cysecurity.cspf.jvl.controller.XPathQuery
+
+
+ xxe
+ org.cysecurity.cspf.jvl.controller.xxe
+
+
+ Transfer
+ org.cysecurity.cspf.jvl.controller.Transfer
+
+
+
+ Transfer
+ /Transfer.do
+
+
+ Install
+ /Install
+
+
+ loginValidator
+ /LoginValidator
+
+
+ Register
+ /AddUser
+
+
+ Logout
+ /Logout
+
+
+ Open
+ /Open
+
+
+ SendMessage
+ /SendMessage.do
+
+
+ ForwardMe
+ /ForwardMe
+
+
+ AddPage
+ /admin/AddPage.do
+
+
+ UsernameCheck
+ /UsernameCheck.do
+
+
+ EmailCheck
+ /EmailCheck.do
+
+
+ XPathQuery
+ /XPathQuery.do
+
+
+ xxe
+ /xxe.do
+
+
+
+ AvatarUpload
+ org.cysecurity.cspf.jvl.controller.AvatarUpload
+
+
+ AvatarUpload
+ /UploadAvatar
+
+
+
+ jdbc/jvl
+ javax.sql.DataSource
+ Container
+
+
+
+
+ default
+ org.apache.catalina.servlets.DefaultServlet
+
+ debug
+ 0
+
+
+ listings
+ true
+
+ 1
+
+
+ default
+ /
+
+
+
diff --git a/src/main/webapp/WEB-INF/widget-source.jspf b/src/main/webapp/WEB-INF/widget-source.jspf
new file mode 100644
index 00000000..42aec9af
--- /dev/null
+++ b/src/main/webapp/WEB-INF/widget-source.jspf
@@ -0,0 +1,44 @@
+<%--
+ widget-source.jspf - shared config for the third-party "promo widget"
+ [OWASP 2025 A08 Software or Data Integrity Failures,
+ CWE-830 Inclusion of Web Functionality from an Untrusted Source]
+
+ The URL of the third-party script, and its optional Subresource Integrity
+ hash, are kept in WEB-INF/config.properties so the admin screen can change
+ them at runtime. In a real application the URL would simply be hard-coded
+ in the page by whoever added the vendor's snippet.
+
+ Both settings are empty by default. An empty script URL falls back to the
+ demo widget server on whatever host the browser used to reach this page,
+ so the lab works unchanged on localhost, on a VM's IP, or behind a
+ hostname. Nothing here assumes localhost.
+--%>
+<%@page import="java.util.Properties"%>
+<%!
+ static final String WIDGET_URL_KEY = "widgetScriptUrl";
+ static final String WIDGET_SRI_KEY = "widgetScriptIntegrity";
+ static final int WIDGET_DEMO_PORT = 8000;
+
+ /**
+ * Where the demo widget server is expected to be: same host the browser is
+ * already talking to, port 8000. Derived per request, never hard-coded.
+ */
+ static String defaultWidgetUrl(HttpServletRequest request) {
+ return "http://" + request.getServerName() + ":" + WIDGET_DEMO_PORT
+ + "/promo-widget.js";
+ }
+
+ /** Escape a value for use inside a double-quoted HTML attribute or as text. */
+ static String esc(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.replace("&", "&").replace("<", "<")
+ .replace(">", ">").replace("\"", """);
+ }
+
+ static String widgetSetting(Properties props, String key, String fallback) {
+ String value = props == null ? null : props.getProperty(key);
+ return (value == null || value.trim().length() == 0) ? fallback : value.trim();
+ }
+%>
diff --git a/web/admin/AddPage.jsp b/src/main/webapp/admin/AddPage.jsp
similarity index 100%
rename from web/admin/AddPage.jsp
rename to src/main/webapp/admin/AddPage.jsp
diff --git a/web/admin/Configure.jsp b/src/main/webapp/admin/Configure.jsp
similarity index 54%
rename from web/admin/Configure.jsp
rename to src/main/webapp/admin/Configure.jsp
index d60ca8b0..ee5aaf1e 100644
--- a/web/admin/Configure.jsp
+++ b/src/main/webapp/admin/Configure.jsp
@@ -5,22 +5,32 @@
{
%>
+<%
+ Properties current=new Properties();
+ FileInputStream currentIn=new FileInputStream(configPath);
+ current.load(currentIn);
+ currentIn.close();
+ boolean debugOn="true".equalsIgnoreCase(current.getProperty("debug","false"));
+ %>
-
+
<%
if(request.getParameter("save")!=null)
{
Properties props=new Properties();
-
+
props.load(new FileInputStream(configPath));
props.setProperty("siteTitle",request.getParameter("siteTitle"));
+ props.setProperty("debug", request.getParameter("debug")!=null ? "true" : "false");
FileOutputStream fileout = new FileOutputStream(configPath);
- props.store(fileout, null);
+ props.store(fileout, null);
fileout.close();
out.print(" Configuration saved ");
}
diff --git a/src/main/webapp/admin/WidgetSource.jsp b/src/main/webapp/admin/WidgetSource.jsp
new file mode 100644
index 00000000..69044d86
--- /dev/null
+++ b/src/main/webapp/admin/WidgetSource.jsp
@@ -0,0 +1,143 @@
+<%--
+ WidgetSource.jsp - demo control for the third-party promo widget
+ [supports the A08 / CWE-830 demo in vulnerability/integrity/checkout.jsp]
+
+ THIS SCREEN IS NOT THE VULNERABILITY. It is an admin-only setting that
+ writes the widget's script URL (and its optional Subresource Integrity
+ hash) into WEB-INF/config.properties, so a presenter can repoint the
+ checkout page at another host without redeploying the application.
+
+ In a real application there is no such screen: a developer pastes the
+ vendor's "); %>
+
+
+
+
+
+
+
+ <%-- The main nav is built with response.encodeURL(), which is what an app
+ with URL session tracking enabled does everywhere. For a browser that
+ returns a cookie it is a no-op; for a client whose session id arrived
+ in the URL it re-appends ;jsessionid= so the session survives the click. --%>
+
+
diff --git a/web/images/Thumbs.db b/src/main/webapp/images/Thumbs.db
similarity index 100%
rename from web/images/Thumbs.db
rename to src/main/webapp/images/Thumbs.db
diff --git a/web/images/bg.png b/src/main/webapp/images/bg.png
similarity index 100%
rename from web/images/bg.png
rename to src/main/webapp/images/bg.png
diff --git a/web/index.jsp b/src/main/webapp/index.jsp
similarity index 100%
rename from web/index.jsp
rename to src/main/webapp/index.jsp
diff --git a/web/install.jsp b/src/main/webapp/install.jsp
similarity index 93%
rename from web/install.jsp
rename to src/main/webapp/install.jsp
index 457a708e..dfbe8f73 100644
--- a/web/install.jsp
+++ b/src/main/webapp/install.jsp
@@ -3,14 +3,14 @@
<%@ include file="/header.jsp" %>
-