Compare commits

..

6 Commits

345 changed files with 3040 additions and 2525 deletions

View File

@ -11,9 +11,9 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
java-version: [ 21 ] java-version: [ 17, 21 ]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up JDK ${{ matrix.java-version }} - name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:

View File

@ -5,47 +5,30 @@ on:
- '.txt' - '.txt'
- 'LICENSE' - 'LICENSE'
- 'docs/**' - 'docs/**'
branches: [ main ] branches: [main]
push: push:
branches: branches:
- main - main
jobs: jobs:
pre-commit:
name: Pre-commit check
runs-on: ubuntu-latest
steps:
- name: Checkout git repository
uses: actions/checkout@v4.1.6
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.9"
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Pre-commit checks
uses: pre-commit/action@v3.0.1
- name: pre-commit-ci-lite
uses: pre-commit-ci/lite-action@v1.1.0
if: always()
build: build:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [ pre-commit ]
strategy: strategy:
fail-fast: true
matrix: matrix:
os: [ windows-latest, ubuntu-latest, macos-13 ] os: [ ubuntu-latest, windows-latest, macos-latest ]
max-parallel: 1
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v3
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v4.2.1 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 21 java-version: 17
architecture: x64 architecture: x64
cache: 'maven' - name: Cache Maven packages
uses: actions/cache@v3.3.1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2-
- name: Build with Maven - name: Build with Maven
run: mvn --no-transfer-progress verify run: mvn --no-transfer-progress verify

29
.github/workflows/pre-commit.yaml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Pre-commit check
on:
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
pre-commit:
name: Pre-commit check
runs-on: ubuntu-latest
steps:
- name: Checkout git repository
uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.9"
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Pre-commit checks
uses: pre-commit/action@v3.0.0
- name: pre-commit-ci-lite
uses: pre-commit-ci/lite-action@v1.0.1
if: always()

View File

@ -13,15 +13,21 @@ jobs:
environment: environment:
name: release name: release
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 21 java-version: 17
architecture: x64 architecture: x64
cache: 'maven'
- name: Cache Maven packages
uses: actions/cache@v3.3.1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: "Set labels for ${{ github.ref }}" - name: "Set labels for ${{ github.ref }}"
run: | run: |
@ -68,7 +74,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Set up QEMU" - name: "Set up QEMU"
uses: docker/setup-qemu-action@v3.1.0 uses: docker/setup-qemu-action@v2.2.0
with: with:
platforms: all platforms: all
@ -76,13 +82,13 @@ jobs:
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: "Login to dockerhub" - name: "Login to dockerhub"
uses: docker/login-action@v3.3.0 uses: docker/login-action@v3.0.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: "Build and push WebGoat" - name: "Build and push WebGoat"
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v5.1.0
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile
@ -95,7 +101,7 @@ jobs:
webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }} webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
- name: "Build and push WebGoat desktop" - name: "Build and push WebGoat desktop"
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v5.1.0
with: with:
context: ./ context: ./
file: ./Dockerfile_desktop file: ./Dockerfile_desktop
@ -112,15 +118,15 @@ jobs:
needs: [ release ] needs: [ release ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 21 java-version: 17
architecture: x64 architecture: x64
- name: Set version to next snapshot - name: Set version to next snapshot

View File

@ -21,21 +21,27 @@ jobs:
name: "Robot framework test" name: "Robot framework test"
steps: steps:
# Uses an default action to checkout the code # Uses an default action to checkout the code
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v3
# Uses an action to add Python to the VM # Uses an action to add Python to the VM
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.7' python-version: '3.7'
architecture: x64 architecture: x64
# Uses an action to add JDK 21 to the VM (and mvn?) # Uses an action to add JDK 17 to the VM (and mvn?)
- name: set up JDK 21 - name: set up JDK 17
uses: actions/setup-java@v4.2.1 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 21 java-version: 17
architecture: x64 architecture: x64
cache: 'maven' #Uses an action to set up a cache using a certain key based on the hash of the dependencies
- name: Cache Maven packages
uses: actions/cache@v3.3.1
with:
path: ~/.m2
key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ubuntu-latest-m2-
- uses: BSFishy/pip-action@v1 - uses: BSFishy/pip-action@v1
with: with:
packages: | packages: |

View File

@ -1,8 +1,6 @@
# We need JDK as some of the lessons needs to be able to compile Java code FROM docker.io/eclipse-temurin:21.0.1_12-jre
FROM docker.io/eclipse-temurin:21-jdk-jammy LABEL NAME = "WebGoat: A deliberately insecure Web Application"
MAINTAINER "WebGoat team"
LABEL name="WebGoat: A deliberately insecure Web Application"
LABEL maintainer="WebGoat team"
RUN \ RUN \
useradd -ms /bin/bash webgoat && \ useradd -ms /bin/bash webgoat && \
@ -35,6 +33,3 @@ ENTRYPOINT [ "java", \
"--add-opens", "java.base/java.io=ALL-UNNAMED", \ "--add-opens", "java.base/java.io=ALL-UNNAMED", \
"-Drunning.in.docker=true", \ "-Drunning.in.docker=true", \
"-jar", "webgoat.jar", "--server.address", "0.0.0.0" ] "-jar", "webgoat.jar", "--server.address", "0.0.0.0" ]
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl --fail http://localhost:8080/WebGoat/actuator/health || exit 1

View File

@ -1,6 +1,6 @@
FROM lscr.io/linuxserver/webtop:ubuntu-xfce FROM lscr.io/linuxserver/webtop:ubuntu-xfce
LABEL NAME = "WebGoat: A deliberately insecure Web Application" LABEL NAME = "WebGoat: A deliberately insecure Web Application"
LABEL maintainer = "WebGoat team" MAINTAINER "WebGoat team"
WORKDIR /config WORKDIR /config
@ -9,38 +9,26 @@ COPY config/desktop/start_webgoat.sh /config/start_webgoat.sh
COPY config/desktop/start_zap.sh /config/start_zap.sh COPY config/desktop/start_zap.sh /config/start_zap.sh
COPY config/desktop/WebGoat.txt /config/Desktop/ COPY config/desktop/WebGoat.txt /config/Desktop/
RUN \
apt-get update && \
apt-get --yes install vim nano gzip
RUN \ RUN \
case $(uname -m) in \ case $(uname -m) in \
x86_64) ARCH=x64;; \ x86_64) ARCH=x64;; \
aarch64) ARCH=aarch64;; \ aarch64) ARCH=aarch64;; \
*) ARCH=unknown;; \ *) ARCH=unknown;; \
esac && \ esac && \
echo ${ARCH} curl -LO https://github.com/zaproxy/zaproxy/releases/download/v2.12.0/ZAP_2.12.0_Linux.tar.gz && \
tar zfxv ZAP_2.12.0_Linux.tar.gz && \
RUN \ rm -rf ZAP_2.12.0_Linux.tar.gz && \
curl -LO https://github.com/zaproxy/zaproxy/releases/download/v2.15.0/ZAP_2.15.0_Linux.tar.gz && \ curl -LO https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/OpenJDK17U-jre_${ARCH}_linux_hotspot_17.0.6_10.tar.gz && \
tar zfxv ZAP_2.15.0_Linux.tar.gz && \ tar zfxv OpenJDK17U-jre_${ARCH}_linux_hotspot_17.0.6_10.tar.gz && \
rm -rf ZAP_2.15.0_Linux.tar.gz rm -rf OpenJDK17U-jre_${ARCH}_linux_hotspot_17.0.6_10.tar.gz && \
RUN \
case $(uname -m) in \
x86_64) ARCH=x64;; \
aarch64) ARCH=aarch64;; \
*) ARCH=unknown;; \
esac && \
echo "oeps == ${ARCH}==" && \
curl -L https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jre_"${ARCH}"_linux_hotspot_21.0.3_9.tar.gz -o java.tar.gz && \
tar zfxv java.tar.gz && \
rm -rf java.tar.gz && \
chmod +x /config/start_webgoat.sh && \ chmod +x /config/start_webgoat.sh && \
chmod +x /config/start_zap.sh && \ chmod +x /config/start_zap.sh && \
echo "JAVA_HOME=/config/jdk-21.0.3+9-jre/" >> .bash_aliases && \ apt-get update && \
apt-get --yes install vim nano && \
echo "JAVA_HOME=/config/jdk-17.0.6+10-jre/" >> .bash_aliases && \
echo "PATH=$PATH:$JAVA_HOME/bin" >> .bash_aliases echo "PATH=$PATH:$JAVA_HOME/bin" >> .bash_aliases
ENV JAVA_HOME=/config/jdk-21.0.3+9-jre
ENV JAVA_HOME=/home/webgoat/jdk-17.0.6+10-jre
WORKDIR /config/Desktop WORKDIR /config/Desktop

View File

@ -1,7 +1,7 @@
# WebGoat: A deliberately insecure Web Application # WebGoat: A deliberately insecure Web Application
[![Build](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml) [![Build](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml)
[![java-jdk](https://img.shields.io/badge/java%20jdk-21-green.svg)](https://jdk.java.net/) [![java-jdk](https://img.shields.io/badge/java%20jdk-17-green.svg)](https://jdk.java.net/)
[![OWASP Labs](https://img.shields.io/badge/OWASP-Lab%20project-f7b73c.svg)](https://owasp.org/projects/) [![OWASP Labs](https://img.shields.io/badge/OWASP-Lab%20project-f7b73c.svg)](https://owasp.org/projects/)
[![GitHub release](https://img.shields.io/github/release/WebGoat/WebGoat.svg)](https://github.com/WebGoat/WebGoat/releases/latest) [![GitHub release](https://img.shields.io/github/release/WebGoat/WebGoat.svg)](https://github.com/WebGoat/WebGoat/releases/latest)
[![Gitter](https://badges.gitter.im/OWASPWebGoat/community.svg)](https://gitter.im/OWASPWebGoat/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/OWASPWebGoat/community.svg)](https://gitter.im/OWASPWebGoat/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
@ -80,21 +80,11 @@ Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/rel
```shell ```shell
export TZ=Europe/Amsterdam # or your timezone export TZ=Europe/Amsterdam # or your timezone
java -Dfile.encoding=UTF-8 -jar webgoat-2023.8.jar java -Dfile.encoding=UTF-8 -jar webgoat-2023.5.jar
``` ```
Click the link in the log to start WebGoat. Click the link in the log to start WebGoat.
### 3.1 Running on a different port
If for some reason you want to run WebGoat on a different port, you can do so by adding the following parameter:
```shell
java -jar webgoat-2023.8.jar --webgoat.port=8001 --webwolf.port=8002
```
For a full overview of all the parameters you can use, please check the [WebGoat properties file](webgoat-container/src/main/resources/application-{webgoat, webwolf}.properties).
## 4. Run from the sources ## 4. Run from the sources
### Prerequisites: ### Prerequisites:
@ -154,7 +144,7 @@ For instance running as a jar on a Linux/macOS it will look like this:
export TZ=Europe/Amsterdam # or your timezone export TZ=Europe/Amsterdam # or your timezone
export EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE" export EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE"
export EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations" export EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations"
java -jar target/webgoat-2023.8-SNAPSHOT.jar java -jar target/webgoat-2023.6-SNAPSHOT.jar
``` ```
Or in a docker run it would (once this version is pushed into docker hub) look like this: Or in a docker run it would (once this version is pushed into docker hub) look like this:

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
/config/jdk-21.0.3+9-jre/bin/java \ /config/jdk-17.0.6+10-jre/bin/java \
-Duser.home=/config \ -Duser.home=/config \
-Dfile.encoding=UTF-8 \ -Dfile.encoding=UTF-8 \
-DTZ=Europe/Amsterdam \ -DTZ=Europe/Amsterdam \

View File

@ -1,3 +1,3 @@
#!/bin/sh #!/bin/sh
/config/jdk-21.0.3+9-jre/bin/java -jar /config/ZAP_2.15.0/zap-2.15.0.jar /config/jdk-17.0.6+10-jre/bin/java -jar /config/ZAP_2.12.0/zap-2.12.0.jar

163
pom.xml
View File

@ -5,12 +5,12 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version> <version>3.1.5</version>
</parent> </parent>
<groupId>org.owasp.webgoat</groupId> <groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat</artifactId> <artifactId>webgoat</artifactId>
<version>2024.2-SNAPSHOT</version> <version>2023.9-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>WebGoat</name> <name>WebGoat</name>
@ -29,6 +29,13 @@
</licenses> </licenses>
<developers> <developers>
<developer>
<id>mayhew64</id>
<name>Bruce Mayhew</name>
<email>webgoat@owasp.org</email>
<organization>OWASP</organization>
<organizationUrl>https://github.com/WebGoat/WebGoat</organizationUrl>
</developer>
<developer> <developer>
<id>nbaars</id> <id>nbaars</id>
<name>Nanne Baars</name> <name>Nanne Baars</name>
@ -36,6 +43,11 @@
<organizationUrl>https://github.com/nbaars</organizationUrl> <organizationUrl>https://github.com/nbaars</organizationUrl>
<timezone>Europe/Amsterdam</timezone> <timezone>Europe/Amsterdam</timezone>
</developer> </developer>
<developer>
<id>misfir3</id>
<name>Jason White</name>
<email>jason.white@owasp.org</email>
</developer>
<developer> <developer>
<id>zubcevic</id> <id>zubcevic</id>
<name>René Zubcevic</name> <name>René Zubcevic</name>
@ -46,8 +58,43 @@
<name>Àngel Ollé Blázquez</name> <name>Àngel Ollé Blázquez</name>
<email>angel@olleb.com</email> <email>angel@olleb.com</email>
</developer> </developer>
<developer>
<id>jwayman</id>
<name>Jeff Wayman</name>
<email></email>
</developer>
<developer>
<id>dcowden</id>
<name>Dave Cowden</name>
<email></email>
</developer>
<developer>
<id>lawson89</id>
<name>Richard Lawson</name>
<email></email>
</developer>
<developer>
<id>dougmorato</id>
<name>Doug Morato</name>
<email>doug.morato@owasp.org</email>
<organization>OWASP</organization>
<organizationUrl>https://github.com/dougmorato</organizationUrl>
<timezone>America/New_York</timezone>
<properties>
<picUrl>https://avatars2.githubusercontent.com/u/9654?v=3&amp;s=150</picUrl>
</properties>
</developer>
</developers> </developers>
<mailingLists>
<mailingList>
<name>OWASP WebGoat Mailing List</name>
<subscribe>https://lists.owasp.org/mailman/listinfo/owasp-webgoat</subscribe>
<unsubscribe>Owasp-webgoat-request@lists.owasp.org</unsubscribe>
<post>owasp-webgoat@lists.owasp.org</post>
<archive>http://lists.owasp.org/pipermail/owasp-webgoat/</archive>
</mailingList>
</mailingLists>
<scm> <scm>
<connection>scm:git:git@github.com:WebGoat/WebGoat.git</connection> <connection>scm:git:git@github.com:WebGoat/WebGoat.git</connection>
<developerConnection>scm:git:git@github.com:WebGoat/WebGoat.git</developerConnection> <developerConnection>scm:git:git@github.com:WebGoat/WebGoat.git</developerConnection>
@ -62,56 +109,61 @@
<properties> <properties>
<!-- Shared properties with plugins and version numbers across submodules--> <!-- Shared properties with plugins and version numbers across submodules-->
<asciidoctorj.version>3.0.0</asciidoctorj.version> <asciidoctorj.version>2.5.10</asciidoctorj.version>
<bootstrap.version>5.3.3</bootstrap.version> <bootstrap.version>5.3.2</bootstrap.version>
<cglib.version>3.3.0</cglib.version> <cglib.version>3.3.0</cglib.version>
<!-- do not update necessary for lesson --> <!-- do not update necessary for lesson -->
<checkstyle.version>3.6.0</checkstyle.version> <checkstyle.version>3.3.1</checkstyle.version>
<commons-collections.version>3.2.1</commons-collections.version> <commons-collections.version>3.2.1</commons-collections.version>
<commons-compress.version>1.27.1</commons-compress.version> <commons-io.version>2.15.1</commons-io.version>
<commons-io.version>2.17.0</commons-io.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<commons-lang3.version>3.14.0</commons-lang3.version> <commons-text.version>1.10.0</commons-text.version>
<commons-text.version>1.12.0</commons-text.version> <guava.version>32.1.3-jre</guava.version>
<guava.version>33.3.1-jre</guava.version>
<jacoco.version>0.8.11</jacoco.version> <jacoco.version>0.8.11</jacoco.version>
<java.version>21</java.version> <java.version>17</java.version>
<jaxb.version>2.3.1</jaxb.version> <jaxb.version>2.3.1</jaxb.version>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.9.1</jjwt.version>
<jose4j.version>0.9.3</jose4j.version> <jose4j.version>0.9.3</jose4j.version>
<jquery.version>3.7.1</jquery.version> <jquery.version>3.7.0</jquery.version>
<jsoup.version>1.18.1</jsoup.version> <jsoup.version>1.16.1</jsoup.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version> <maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
<maven-jar-plugin.version>3.1.2</maven-jar-plugin.version> <maven-jar-plugin.version>3.1.2</maven-jar-plugin.version>
<maven-javadoc-plugin.version>3.1.1</maven-javadoc-plugin.version> <maven-javadoc-plugin.version>3.1.1</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.1.0</maven-source-plugin.version> <maven-source-plugin.version>3.1.0</maven-source-plugin.version>
<maven-surefire-plugin.version>3.5.2</maven-surefire-plugin.version> <maven-surefire-plugin.version>3.2.1</maven-surefire-plugin.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<pmd.version>3.15.0</pmd.version> <pmd.version>3.15.0</pmd.version>
<!-- Use UTF-8 Encoding --> <!-- Use UTF-8 Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version> <thymeleaf.version>3.1.1.RELEASE</thymeleaf.version>
<waittimeForServerStart>60</waittimeForServerStart> <webdriver.version>5.3.3</webdriver.version>
<webdriver.version>5.9.2</webdriver.version>
<webgoat.context>/</webgoat.context> <webgoat.context>/</webgoat.context>
<webgoat.sslenabled>false</webgoat.sslenabled> <webgoat.sslenabled>false</webgoat.sslenabled>
<webjars-locator-core.version>0.59</webjars-locator-core.version> <webjars-locator-core.version>0.53</webjars-locator-core.version>
<webwolf.context>/</webwolf.context> <webwolf.context>/</webwolf.context>
<wiremock.version>3.9.2</wiremock.version> <wiremock.version>2.27.2</wiremock.version>
<xml-resolver.version>1.2</xml-resolver.version> <xml-resolver.version>1.2</xml-resolver.version>
<xstream.version>1.4.5</xstream.version> <xstream.version>1.4.5</xstream.version>
<!-- do not update necessary for lesson --> <!-- do not update necessary for lesson -->
<zxcvbn.version>1.9.0</zxcvbn.version> <zxcvbn.version>1.8.0</zxcvbn.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.5</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId> <artifactId>commons-exec</artifactId>
<version>1.4.0</version> <version>1.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.asciidoctor</groupId> <groupId>org.asciidoctor</groupId>
@ -196,8 +248,8 @@
<version>${webjars-locator-core.version}</version> <version>${webjars-locator-core.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.wiremock</groupId> <groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId> <artifactId>wiremock</artifactId>
<version>${wiremock.version}</version> <version>${wiremock.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -208,12 +260,12 @@
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId> <artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version> <version>1.25.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jruby</groupId> <groupId>org.jruby</groupId>
<artifactId>jruby</artifactId> <artifactId>jruby</artifactId>
<version>9.4.9.0</version> <version>9.4.3.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -232,26 +284,24 @@
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.20.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.3</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version> <version>${jaxb.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -261,10 +311,6 @@
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId> <artifactId>flyway-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-hsqldb</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.asciidoctor</groupId> <groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId> <artifactId>asciidoctorj</artifactId>
@ -371,12 +417,6 @@
<artifactId>jaxb-impl</artifactId> <artifactId>jaxb-impl</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.terma</groupId>
<artifactId>javaniotcpproxy</artifactId>
<version>1.6</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -389,8 +429,10 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.wiremock</groupId> <groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId> <artifactId>wiremock</artifactId>
<version>3.0.0-beta-2</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.rest-assured</groupId> <groupId>io.rest-assured</groupId>
@ -506,7 +548,6 @@
<version>${maven-surefire-plugin.version}</version> <version>${maven-surefire-plugin.version}</version>
<configuration> <configuration>
<forkedProcessTimeoutInSeconds>600</forkedProcessTimeoutInSeconds> <forkedProcessTimeoutInSeconds>600</forkedProcessTimeoutInSeconds>
<!-- Necessary for vulnerable components lesson -->
<argLine>--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED <argLine>--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
@ -514,6 +555,8 @@
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED</argLine> --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED</argLine>
<excludes> <excludes>
<exclude>**/*IntegrationTest.java</exclude> <exclude>**/*IntegrationTest.java</exclude>
<exclude>src/it/java</exclude>
<exclude>org/owasp/webgoat/*Test</exclude>
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>
@ -593,7 +636,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId> <artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version> <version>3.3.0</version>
<executions> <executions>
<execution> <execution>
<id>restrict-log4j-versions</id> <id>restrict-log4j-versions</id>
@ -617,6 +660,10 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
@ -688,18 +735,24 @@
<argument>--add-opens</argument> <argument>--add-opens</argument>
<argument>java.base/java.lang.reflect=ALL-UNNAMED</argument> <argument>java.base/java.lang.reflect=ALL-UNNAMED</argument>
<argument>--add-opens</argument> <argument>--add-opens</argument>
<argument>java.base/java.text=ALL-UNNAMED</argument>
<argument>--add-opens</argument>
<argument>java.desktop/java.beans=ALL-UNNAMED</argument> <argument>java.desktop/java.beans=ALL-UNNAMED</argument>
<argument>--add-opens</argument> <argument>--add-opens</argument>
<argument>java.desktop/java.awt.font=ALL-UNNAMED</argument>
<argument>--add-opens</argument>
<argument>java.base/sun.nio.ch=ALL-UNNAMED</argument> <argument>java.base/sun.nio.ch=ALL-UNNAMED</argument>
<argument>--add-opens</argument> <argument>--add-opens</argument>
<argument>java.base/java.io=ALL-UNNAMED</argument> <argument>java.base/java.io=ALL-UNNAMED</argument>
<argument>--add-opens</argument> <argument>--add-opens</argument>
<argument>java.base/java.util=ALL-UNNAMED</argument> <argument>java.base/java.util=ALL-UNNAMED</argument>
<argument>--add-opens</argument>
<argument>java.base/sun.nio.ch=ALL-UNNAMED</argument>
<argument>--add-opens</argument>
<argument>java.base/java.io=ALL-UNNAMED</argument>
<argument>${project.build.directory}/webgoat-${project.version}.jar</argument> <argument>${project.build.directory}/webgoat-${project.version}.jar</argument>
</arguments> </arguments>
<waitForInterrupt>false</waitForInterrupt> <waitForInterrupt>false</waitForInterrupt>
<waitAfterLaunch>${waittimeForServerStart}</waitAfterLaunch>
<healthCheckUrl>http://127.0.0.1:${webgoat.port}${webgoat.context}login</healthCheckUrl>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

View File

@ -15,7 +15,7 @@ class AccessControlIntegrationTest extends IntegrationTest {
assignment2(); assignment2();
assignment3(); assignment3();
checkResults("MissingFunctionAC"); checkResults("/access-control");
} }
private void assignment3() { private void assignment3() {

View File

@ -86,7 +86,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
// logout(); // logout();
login(); // because old cookie got replaced and invalidated login(); // because old cookie got replaced and invalidated
startLesson("CSRF", false); startLesson("CSRF", false);
checkResults("CSRF"); checkResults("/csrf");
} }
private void uploadTrickHtml(String htmlName, String htmlContent) throws IOException { private void uploadTrickHtml(String htmlName, String htmlContent) throws IOException {
@ -103,7 +103,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", htmlName, htmlContent.getBytes()) .multiPart("file", htmlName, htmlContent.getBytes())
.post(new WebWolfUrlBuilder().path("fileupload").build()) .post(webWolfUrl("fileupload"))
.then() .then()
.extract() .extract()
.response() .response()
@ -118,7 +118,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("files/%s/%s", this.getUser(), htmlName).build()) .get(webWolfUrl("files/" + this.getUser() + "/" + htmlName))
.then() .then()
.extract() .extract()
.response() .response()
@ -136,7 +136,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build()) .header("Referer", webWolfUrl("files/fake.html"))
.post(goatURL) .post(goatURL)
.then() .then()
.extract() .extract()
@ -163,7 +163,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build()) .header("Referer", webWolfUrl("files/fake.html"))
.formParams(params) .formParams(params)
.post(goatURL) .post(goatURL)
.then() .then()
@ -184,7 +184,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build()) .header("Referer", webWolfUrl("files/fake.html"))
.contentType(ContentType.TEXT) .contentType(ContentType.TEXT)
.body( .body(
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is" "{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is"
@ -217,7 +217,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build()) .header("Referer", webWolfUrl("files/fake.html"))
.params(params) .params(params)
.post(goatURL) .post(goatURL)
.then() .then()
@ -254,15 +254,15 @@ public class CSRFIntegrationTest extends IntegrationTest {
RestAssured.given() RestAssured.given()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.get(url("service/lessonoverview.mvc/CSRF")) .get(url("service/lessonoverview.mvc"))
.then() .then()
.extract() .extract()
.jsonPath() .jsonPath()
.getObject("$", Overview[].class); .getObject("$", Overview[].class);
assertThat(assignments) // assertThat(assignments)
.filteredOn(a -> a.getAssignment().getName().equals("CSRFLogin")) // .filteredOn(a -> a.getAssignment().getName().equals("CSRFLogin"))
.extracting(o -> o.solved) // .extracting(o -> o.solved)
.containsExactly(true); // .containsExactly(true);
} }
@Data @Data

View File

@ -50,9 +50,9 @@ public class ChallengeIntegrationTest extends IntegrationTest {
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42); String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear(); params.clear();
params.put("flag", flag); params.put("flag", flag);
checkAssignment(url("challenge/flag/1"), params, true); checkAssignment(url("challenge/flag"), params, true);
checkResults("Challenge1"); checkResults("/challenge/1");
List<String> capturefFlags = List<String> capturefFlags =
RestAssured.given() RestAssured.given()
@ -92,9 +92,9 @@ public class ChallengeIntegrationTest extends IntegrationTest {
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42); String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear(); params.clear();
params.put("flag", flag); params.put("flag", flag);
checkAssignment(url("challenge/flag/5"), params, true); checkAssignment(url("challenge/flag"), params, true);
checkResults("Challenge5"); checkResults("/challenge/5");
List<String> capturefFlags = List<String> capturefFlags =
RestAssured.given() RestAssured.given()
@ -126,7 +126,7 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.extract() .extract()
.asString(); .asString();
// Should email WebWolf inbox this should give a hint to the link being static // Should send an email to WebWolf inbox this should give a hint to the link being static
RestAssured.given() RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
@ -144,7 +144,7 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("mail").build()) .get(webWolfUrl("mail"))
.then() .then()
.extract() .extract()
.response() .response()
@ -165,6 +165,6 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.asString(); .asString();
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42); String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
checkAssignment(url("challenge/flag/7"), Map.of("flag", flag), true); checkAssignment(url("challenge/flag"), Map.of("flag", flag), true);
} }
} }

View File

@ -42,7 +42,7 @@ public class CryptoIntegrationTest extends IntegrationTest {
checkAssignmentDefaults(); checkAssignmentDefaults();
checkResults("Cryptography"); checkResults("/crypto");
} }
private void checkAssignment2() { private void checkAssignment2() {

View File

@ -28,6 +28,6 @@ public class DeserializationIntegrationTest extends IntegrationTest {
} }
checkAssignment(url("InsecureDeserialization/task"), params, true); checkAssignment(url("InsecureDeserialization/task"), params, true);
checkResults("InsecureDeserialization"); checkResults("/InsecureDeserialization/");
} }
} }

View File

@ -31,17 +31,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("magic_num", "33"); params.put("magic_num", "33");
checkAssignment(url("HttpBasics/attack2"), params, true); checkAssignment(url("HttpBasics/attack2"), params, true);
checkResults("HttpBasics"); checkResults("/HttpBasics/");
}
@Test
public void solveAsOtherUserHttpBasics() {
login("steven");
startLesson("HttpBasics");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("person", "goatuser");
checkAssignment(url("HttpBasics/attack1"), params, true);
} }
@Test @Test
@ -61,7 +51,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
.path("lessonCompleted"), .path("lessonCompleted"),
CoreMatchers.is(true)); CoreMatchers.is(true));
checkResults("HttpProxies"); checkResults("/HttpProxies/");
} }
@Test @Test
@ -83,7 +73,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
"question_3_solution", "question_3_solution",
"Solution 2: The systems security is compromised even if only one goal is harmed."); "Solution 2: The systems security is compromised even if only one goal is harmed.");
checkAssignment(url("cia/quiz"), params, true); checkAssignment(url("cia/quiz"), params, true);
checkResults("CIA"); checkResults("/cia/");
} }
@Test @Test
@ -106,7 +96,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear(); params.clear();
params.put("payload", solution); params.put("payload", solution);
checkAssignment(url("VulnerableComponents/attack1"), params, true); checkAssignment(url("VulnerableComponents/attack1"), params, true);
checkResults("VulnerableComponents"); checkResults("/VulnerableComponents/");
} }
} }
@ -118,7 +108,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("username", "CaptainJack"); params.put("username", "CaptainJack");
params.put("password", "BlackPearl"); params.put("password", "BlackPearl");
checkAssignment(url("InsecureLogin/task"), params, true); checkAssignment(url("InsecureLogin/task"), params, true);
checkResults("InsecureLogin"); checkResults("/InsecureLogin/");
} }
@Test @Test
@ -128,7 +118,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear(); params.clear();
params.put("password", "ajnaeliclm^&&@kjn."); params.put("password", "ajnaeliclm^&&@kjn.");
checkAssignment(url("SecurePasswords/assignment"), params, true); checkAssignment(url("SecurePasswords/assignment"), params, true);
checkResults("SecurePasswords"); checkResults("SecurePasswords/");
startLesson("AuthBypass"); startLesson("AuthBypass");
params.clear(); params.clear();
@ -138,7 +128,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("verifyMethod", "SEC_QUESTIONS"); params.put("verifyMethod", "SEC_QUESTIONS");
params.put("userId", "12309746"); params.put("userId", "12309746");
checkAssignment(url("auth-bypass/verify-account"), params, true); checkAssignment(url("auth-bypass/verify-account"), params, true);
checkResults("AuthBypass"); checkResults("/auth-bypass/");
startLesson("HttpProxies"); startLesson("HttpProxies");
MatcherAssert.assertThat( MatcherAssert.assertThat(
@ -154,7 +144,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
.extract() .extract()
.path("lessonCompleted"), .path("lessonCompleted"),
CoreMatchers.is(true)); CoreMatchers.is(true));
checkResults("HttpProxies"); checkResults("/HttpProxies/");
} }
@Test @Test
@ -190,7 +180,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("network_num", "24"); params.put("network_num", "24");
checkAssignment(url("ChromeDevTools/network"), params, true); checkAssignment(url("ChromeDevTools/network"), params, true);
checkResults("ChromeDevTools"); checkResults("/ChromeDevTools/");
} }
@Test @Test
@ -204,7 +194,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("verifyMethod", "SEC_QUESTIONS"); params.put("verifyMethod", "SEC_QUESTIONS");
params.put("userId", "12309746"); params.put("userId", "12309746");
checkAssignment(url("auth-bypass/verify-account"), params, true); checkAssignment(url("auth-bypass/verify-account"), params, true);
checkResults("AuthBypass"); checkResults("/auth-bypass/");
} }
@Test @Test
@ -215,6 +205,6 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("param1", "secr37Value"); params.put("param1", "secr37Value");
params.put("param2", "Main"); params.put("param2", "Main");
checkAssignment(url("lesson-template/sample-attack"), params, true); checkAssignment(url("lesson-template/sample-attack"), params, true);
checkResults("LessonTemplate"); checkResults("/lesson-template/");
} }
} }

View File

@ -30,7 +30,7 @@ public class IDORIntegrationTest extends IntegrationTest {
@AfterEach @AfterEach
public void shutdown() { public void shutdown() {
checkResults("IDOR"); checkResults("/IDOR");
} }
private void loginIDOR() { private void loginIDOR() {

View File

@ -3,7 +3,6 @@ package org.owasp.webgoat;
import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.given;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.filter.log.LogDetail;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import java.util.Map; import java.util.Map;
import lombok.Getter; import lombok.Getter;
@ -16,80 +15,43 @@ import org.springframework.http.HttpStatus;
public abstract class IntegrationTest { public abstract class IntegrationTest {
private static String webGoatPort = System.getenv().getOrDefault("WEBGOAT_PORT", "8080"); private static String webGoatPort = System.getenv().getOrDefault("WEBGOAT_PORT", "8080");
private static String webGoatContext =
System.getenv().getOrDefault("WEBGOAT_CONTEXT", "/WebGoat/");
@Getter private static String webWolfPort = System.getenv().getOrDefault("WEBWOLF_PORT", "9090"); @Getter private static String webWolfPort = System.getenv().getOrDefault("WEBWOLF_PORT", "9090");
@Getter @Getter
private static String webWolfHost = System.getenv().getOrDefault("WEBWOLF_HOST", "127.0.0.1"); private static String webWolfHost = System.getenv().getOrDefault("WEBWOLF_HOST", "127.0.0.1");
private static String webGoatContext = @Getter
System.getenv().getOrDefault("WEBGOAT_CONTEXT", "/WebGoat/"); private static String webGoatHost = System.getenv().getOrDefault("WEBGOAT_HOST", "127.0.0.1");
private static String webWolfContext = private static String webWolfContext =
System.getenv().getOrDefault("WEBWOLF_CONTEXT", "/WebWolf/"); System.getenv().getOrDefault("WEBWOLF_CONTEXT", "/WebWolf/");
private static boolean useSSL =
Boolean.valueOf(System.getenv().getOrDefault("WEBGOAT_SSLENABLED", "false"));
private static String webgoatUrl =
(useSSL ? "https://" : "http://") + webGoatHost + ":" + webGoatPort + webGoatContext;
private static String webWolfUrl = "http://" + webWolfHost + ":" + webWolfPort + webWolfContext;
@Getter private String webGoatCookie; @Getter private String webGoatCookie;
@Getter private String webWolfCookie; @Getter private String webWolfCookie;
@Getter private final String user = "webgoat"; @Getter private final String user = "webgoat";
protected String url(String url) { protected String url(String url) {
return "http://localhost:%s%s%s".formatted(webGoatPort, webGoatContext, url); return webgoatUrl + url;
} }
protected class WebWolfUrlBuilder { protected String webWolfUrl(String url) {
return webWolfUrl + url;
private boolean attackMode = false;
private String path = null;
protected String build() {
return "http://localhost:%s%s%s"
.formatted(webWolfPort, webWolfContext, path != null ? path : "");
}
/**
* In attack mode it means WebGoat calls WebWolf to perform an attack. In this case we need to
* use port 9090 in a Docker environment.
*/
protected WebWolfUrlBuilder attackMode() {
attackMode = true;
return this;
}
protected WebWolfUrlBuilder path(String path) {
this.path = path;
return this;
}
protected WebWolfUrlBuilder path(String path, String... uriVariables) {
this.path = path.formatted(uriVariables);
return this;
}
} }
/** protected String webWolfFileUrl(String fileName) {
* Debugging options: install TestContainers Desktop and map port 5005 to the host machine with return webWolfUrl("files") + "/" + getUser() + "/" + fileName;
* https://newsletter.testcontainers.com/announcements/set-fixed-ports-to-easily-debug-development-services }
*
* <p>Start the test and connect a remote debugger in IntelliJ to localhost:5005 and attach it.
*/
// private static GenericContainer<?> webGoatContainer =
// new GenericContainer(new ImageFromDockerfile("webgoat").withFileFromPath("/",
// Paths.get(".")))
// .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("webgoat")))
// .withExposedPorts(8080, 9090, 5005)
// .withEnv(
// "_JAVA_OPTIONS",
// "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005")
// .waitingFor(Wait.forHealthcheck());
//
// static {
// webGoatContainer.start();
// }
@BeforeEach @BeforeEach
public void login() { public void login() {
login("webgoat");
}
protected void login(String user) {
String location = String location =
given() given()
.when() .when()
@ -98,8 +60,6 @@ public abstract class IntegrationTest {
.formParam("password", "password") .formParam("password", "password")
.post(url("login")) .post(url("login"))
.then() .then()
.log()
.ifValidationFails(LogDetail.ALL) // Log the response details if validation fails
.cookie("JSESSIONID") .cookie("JSESSIONID")
.statusCode(302) .statusCode(302)
.extract() .extract()
@ -140,7 +100,7 @@ public abstract class IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.formParam("username", user) .formParam("username", user)
.formParam("password", "password") .formParam("password", "password")
.post(new WebWolfUrlBuilder().path("login").build()) .post(webWolfUrl("login"))
.then() .then()
.statusCode(302) .statusCode(302)
.cookie("WEBWOLFSESSION") .cookie("WEBWOLFSESSION")
@ -171,7 +131,7 @@ public abstract class IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/restartlesson.mvc/%s.lesson".formatted(lessonName))) .get(url("service/restartlesson.mvc"))
.then() .then()
.statusCode(200); .statusCode(200);
} }
@ -207,18 +167,23 @@ public abstract class IntegrationTest {
CoreMatchers.is(expectedResult)); CoreMatchers.is(expectedResult));
} }
public void checkResults(String lesson) { // TODO is prefix useful? not every lesson endpoint needs to start with a certain prefix (they are
var result = // only required to be in the same package)
public void checkResults(String prefix) {
checkResults();
MatcherAssert.assertThat(
RestAssured.given() RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc/%s.lesson".formatted(lesson))) .get(url("service/lessonoverview.mvc"))
.andReturn(); .then()
.statusCode(200)
MatcherAssert.assertThat( .extract()
result.then().statusCode(200).extract().jsonPath().getList("solved"), .jsonPath()
CoreMatchers.everyItem(CoreMatchers.is(true))); .getList("assignment.path"),
CoreMatchers.everyItem(CoreMatchers.startsWith(prefix)));
} }
public void checkResults() { public void checkResults() {
@ -273,7 +238,7 @@ public abstract class IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("file-server-location").build()) .get(webWolfUrl("file-server-location"))
.then() .then()
.extract() .extract()
.response() .response()
@ -301,7 +266,7 @@ public abstract class IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.delete(new WebWolfUrlBuilder().path("mail").build()) .delete(webWolfUrl("mail"))
.then() .then()
.statusCode(HttpStatus.ACCEPTED.value()); .statusCode(HttpStatus.ACCEPTED.value());
} }

View File

@ -13,6 +13,7 @@ import io.jsonwebtoken.impl.TextCodec;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -33,7 +34,7 @@ import org.owasp.webgoat.lessons.jwt.JWTSecretKeyEndpoint;
public class JWTLessonIntegrationTest extends IntegrationTest { public class JWTLessonIntegrationTest extends IntegrationTest {
@Test @Test
public void solveAssignment() throws IOException, NoSuchAlgorithmException { public void solveAssignment() throws IOException, InvalidKeyException, NoSuchAlgorithmException {
startLesson("JWT"); startLesson("JWT");
decodingToken(); decodingToken();
@ -50,10 +51,11 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
quiz(); quiz();
checkResults("JWT"); checkResults("/JWT/");
} }
private String generateToken(String key) { private String generateToken(String key) {
return Jwts.builder() return Jwts.builder()
.setIssuer("WebGoat Token Builder") .setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org") .setAudience("webgoat.org")
@ -94,7 +96,7 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
CoreMatchers.is(true)); CoreMatchers.is(true));
} }
private void findPassword() { private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
String accessToken = String accessToken =
RestAssured.given() RestAssured.given()
@ -254,7 +256,7 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", "jwks.json", jwks.toJson().getBytes()) .multiPart("file", "jwks.json", jwks.toJson().getBytes())
.post(new WebWolfUrlBuilder().path("fileupload").build()) .post(webWolfUrl("fileupload"))
.then() .then()
.extract() .extract()
.response() .response()
@ -263,10 +265,7 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
Map<String, Object> header = new HashMap(); Map<String, Object> header = new HashMap();
header.put(Header.TYPE, Header.JWT_TYPE); header.put(Header.TYPE, Header.JWT_TYPE);
header.put( header.put(JwsHeader.JWK_SET_URL, webWolfFileUrl("jwks.json"));
JwsHeader.JWK_SET_URL,
new WebWolfUrlBuilder().attackMode().path("files/%s/jwks.json", getUser()).build());
String token = String token =
Jwts.builder() Jwts.builder()
.setHeader(header) .setHeader(header)

View File

@ -151,6 +151,7 @@ public class LabelAndHintIntegrationTest extends IntegrationTest {
checkLang(propsDefault, "nl"); checkLang(propsDefault, "nl");
checkLang(propsDefault, "de"); checkLang(propsDefault, "de");
checkLang(propsDefault, "fr"); checkLang(propsDefault, "fr");
checkLang(propsDefault, "ru");
} }
private Properties getProperties(String lang) { private Properties getProperties(String lang) {

View File

@ -85,7 +85,7 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("mail").build()) .get(webWolfUrl("mail"))
.then() .then()
.extract() .extract()
.response() .response()
@ -99,7 +99,7 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
public void shutdown() { public void shutdown() {
// this will run only once after the list of dynamic tests has run, this is to test if the // this will run only once after the list of dynamic tests has run, this is to test if the
// lesson is marked complete // lesson is marked complete
checkResults("PasswordReset"); checkResults("/PasswordReset");
} }
private void changePassword(String link) { private void changePassword(String link) {
@ -119,7 +119,7 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("requests").build()) .get(webWolfUrl("requests"))
.then() .then()
.extract() .extract()
.response() .response()

View File

@ -147,6 +147,6 @@ class PathTraversalIT extends IntegrationTest {
void shutdown() { void shutdown() {
// this will run only once after the list of dynamic tests has run, this is to test if the // this will run only once after the list of dynamic tests has run, this is to test if the
// lesson is marked complete // lesson is marked complete
checkResults("PathTraversal"); checkResults("/PathTraversal");
} }
} }

View File

@ -29,7 +29,7 @@ public class ProgressRaceConditionIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.formParams(Map.of("flag", "test")) .formParams(Map.of("flag", "test"))
.post(url("challenge/flag/1")); .post(url("challenge/flag"));
}; };
ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_PARALLEL_THREADS); ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_PARALLEL_THREADS);
List<? extends Callable<Response>> flagCalls = List<? extends Callable<Response>> flagCalls =

View File

@ -1,5 +1,6 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -7,7 +8,7 @@ import org.junit.jupiter.api.Test;
public class SSRFIntegrationTest extends IntegrationTest { public class SSRFIntegrationTest extends IntegrationTest {
@Test @Test
public void runTests() { public void runTests() throws IOException {
startLesson("SSRF"); startLesson("SSRF");
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
@ -20,6 +21,6 @@ public class SSRFIntegrationTest extends IntegrationTest {
checkAssignment(url("SSRF/task2"), params, true); checkAssignment(url("SSRF/task2"), params, true);
checkResults("SSRF"); checkResults("/SSRF/");
} }
} }

View File

@ -56,6 +56,6 @@ public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest {
"Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'."); "Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'.");
checkAssignment(url("SqlInjectionAdvanced/quiz"), params, true); checkAssignment(url("SqlInjectionAdvanced/quiz"), params, true);
checkResults("SqlInjectionAdvanced"); checkResults("/SqlInjectionAdvanced/");
} }
} }

View File

@ -73,6 +73,6 @@ public class SqlInjectionLessonIntegrationTest extends IntegrationTest {
params.put("action_string", sql_13); params.put("action_string", sql_13);
checkAssignment(url("SqlInjection/attack10"), params, true); checkAssignment(url("SqlInjection/attack10"), params, true);
checkResults("SqlInjection"); checkResults("/SqlInjection/");
} }
} }

View File

@ -80,6 +80,6 @@ public class SqlInjectionMitigationIntegrationTest extends IntegrationTest {
params.put("ip", "104.130.219.202"); params.put("ip", "104.130.219.202");
checkAssignment(url("SqlInjectionMitigations/attack12a"), params, true); checkAssignment(url("SqlInjectionMitigations/attack12a"), params, true);
checkResults("SqlInjectionMitigations"); checkResults();
} }
} }

View File

@ -23,7 +23,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("mail").build()) .get(webWolfUrl("mail"))
.then() .then()
.extract() .extract()
.response() .response()
@ -53,7 +53,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.queryParams(params) .queryParams(params)
.get(new WebWolfUrlBuilder().path("landing").build()) .get(webWolfUrl("landing"))
.then() .then()
.statusCode(200); .statusCode(200);
responseBody = responseBody =
@ -61,7 +61,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("requests").build()) .get(webWolfUrl("requests"))
.then() .then()
.extract() .extract()
.response() .response()
@ -72,6 +72,6 @@ public class WebWolfIntegrationTest extends IntegrationTest {
params.put("uniqueCode", uniqueCode); params.put("uniqueCode", uniqueCode);
checkAssignment(url("WebWolf/landing"), params, true); checkAssignment(url("WebWolf/landing"), params, true);
checkResults("WebWolfIntroduction"); checkResults("/WebWolf");
} }
} }

View File

@ -111,6 +111,6 @@ public class XSSIntegrationTest extends IntegrationTest {
+ "MyCommentDAO.addComment(threadID, userID).getCleanHTML());"); + "MyCommentDAO.addComment(threadID, userID).getCleanHTML());");
checkAssignment(url("CrossSiteScripting/attack4"), params, true); checkAssignment(url("CrossSiteScripting/attack4"), params, true);
checkResults("CrossSiteScripting"); checkResults("/CrossSiteScripting");
} }
} }

View File

@ -3,6 +3,9 @@ package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class XXEIntegrationTest extends IntegrationTest { public class XXEIntegrationTest extends IntegrationTest {
@ -25,40 +28,47 @@ public class XXEIntegrationTest extends IntegrationTest {
"""; """;
private String webGoatHomeDirectory; private String webGoatHomeDirectory;
private String webWolfFileServerLocation;
// TODO fix me /*
// /* * This test is to verify that all is secure when XXE security patch is applied.
// * This test is to verify that all is secure when XXE security patch is applied. */
// */ @Test
// @Test public void xxeSecure() throws IOException {
// public void xxeSecure() throws IOException { startLesson("XXE");
// startLesson("XXE"); webGoatHomeDirectory = webGoatServerDirectory();
// webGoatHomeDirectory = webGoatServerDirectory(); webWolfFileServerLocation = getWebWolfFileServerLocation();
// RestAssured.given() RestAssured.given()
// .when() .when()
// .relaxedHTTPSValidation() .relaxedHTTPSValidation()
// .cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
// .get(url("service/enable-security.mvc")) .get(url("service/enable-security.mvc"))
// .then() .then()
// .statusCode(200); .statusCode(200);
// checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, false); checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, false);
// checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, false); checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, false);
// checkAssignment( checkAssignment(
// url("xxe/blind"), url("xxe/blind"),
// ContentType.XML, ContentType.XML,
// "<comment><text>" + getSecret() + "</text></comment>", "<comment><text>" + getSecret() + "</text></comment>",
// false); false);
// } }
/** /**
* This performs the steps of the exercise before the secret can be committed in the final step. * This performs the steps of the exercise before the secret can be committed in the final step.
* *
* @return * @return
* @throws IOException
*/ */
private String getSecret() { private String getSecret() throws IOException {
// remove any left over DTD
Path webWolfFilePath = Paths.get(webWolfFileServerLocation);
if (webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")).toFile().exists()) {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")));
}
String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt"); String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt");
String webWolfCallback = new WebWolfUrlBuilder().path("landing").attackMode().build(); String dtd7String =
String dtd7String = dtd7.replace("WEBWOLFURL", webWolfCallback).replace("SECRET", secretFile); dtd7.replace("WEBWOLFURL", webWolfUrl("landing")).replace("SECRET", secretFile);
// upload DTD // upload DTD
RestAssured.given() RestAssured.given()
@ -66,17 +76,15 @@ public class XXEIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", "blind.dtd", dtd7String.getBytes()) .multiPart("file", "blind.dtd", dtd7String.getBytes())
.post(new WebWolfUrlBuilder().path("fileupload").build()) .post(webWolfUrl("fileupload"))
.then() .then()
.extract() .extract()
.response() .response()
.getBody() .getBody()
.asString(); .asString();
// upload attack // upload attack
String xxe7String = String xxe7String =
xxe7.replace("WEBWOLFURL", new WebWolfUrlBuilder().attackMode().path("files").build()) xxe7.replace("WEBWOLFURL", webWolfUrl("files")).replace("USERNAME", this.getUser());
.replace("USERNAME", this.getUser());
checkAssignment(url("xxe/blind"), ContentType.XML, xxe7String, false); checkAssignment(url("xxe/blind"), ContentType.XML, xxe7String, false);
// read results from WebWolf // read results from WebWolf
@ -85,7 +93,7 @@ public class XXEIntegrationTest extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("requests").build()) .get(webWolfUrl("requests"))
.then() .then()
.extract() .extract()
.response() .response()
@ -105,6 +113,7 @@ public class XXEIntegrationTest extends IntegrationTest {
public void runTests() throws IOException { public void runTests() throws IOException {
startLesson("XXE", true); startLesson("XXE", true);
webGoatHomeDirectory = webGoatServerDirectory(); webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, true); checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, true);
checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, true); checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, true);
checkAssignment( checkAssignment(
@ -112,6 +121,6 @@ public class XXEIntegrationTest extends IntegrationTest {
ContentType.XML, ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>", "<comment><text>" + getSecret() + "</text></comment>",
true); true);
checkResults("XXE"); checkResults("xxe/");
} }
} }

View File

@ -32,23 +32,22 @@ package org.owasp.webgoat.container;
import static org.asciidoctor.Asciidoctor.Factory.create; import static org.asciidoctor.Asciidoctor.Factory.create;
import io.undertow.util.Headers;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.asciidoctor.Asciidoctor; import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Attributes;
import org.asciidoctor.Options;
import org.asciidoctor.extension.JavaExtensionRegistry; import org.asciidoctor.extension.JavaExtensionRegistry;
import org.owasp.webgoat.container.asciidoc.*; import org.owasp.webgoat.container.asciidoc.*;
import org.owasp.webgoat.container.i18n.Language; import org.owasp.webgoat.container.i18n.Language;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.i18n.SessionLocaleResolver; import org.springframework.web.servlet.i18n.SessionLocaleResolver;
@ -136,17 +135,17 @@ public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
return computedResourceName; return computedResourceName;
} }
private Options createAttributes() { private Map<String, Object> createAttributes() {
Map<String, Object> attributes = new HashMap<>();
attributes.put("source-highlighter", "coderay");
attributes.put("backend", "xhtml");
attributes.put("lang", determineLanguage());
attributes.put("icons", org.asciidoctor.Attributes.FONT_ICONS);
return Options.builder() Map<String, Object> options = new HashMap<>();
.attributes( options.put("attributes", attributes);
Attributes.builder()
.attribute("source-highlighter", "coderay") return options;
.attribute("backend", "xhtml")
.attribute("lang", determineLanguage())
.attribute("icons", org.asciidoctor.Attributes.FONT_ICONS)
.build())
.build();
} }
private String determineLanguage() { private String determineLanguage() {
@ -160,7 +159,7 @@ public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
log.debug("browser locale {}", browserLocale); log.debug("browser locale {}", browserLocale);
return browserLocale.getLanguage(); return browserLocale.getLanguage();
} else { } else {
String langHeader = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE); String langHeader = request.getHeader(Headers.ACCEPT_LANGUAGE_STRING);
if (null != langHeader) { if (null != langHeader) {
log.debug("browser locale {}", langHeader); log.debug("browser locale {}", langHeader);
return langHeader.substring(0, 2); return langHeader.substring(0, 2);

View File

@ -1,14 +0,0 @@
package org.owasp.webgoat.container;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}

View File

@ -1,14 +0,0 @@
package org.owasp.webgoat.container;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal(expression = "#this.getUsername()")
public @interface CurrentUsername {}

View File

@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.service.RestartLessonService; import org.owasp.webgoat.container.service.RestartLessonService;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -35,8 +34,8 @@ public class DatabaseConfiguration {
/** /**
* Define 2 Flyway instances, 1 for WebGoat itself which it uses for internal storage like users * Define 2 Flyway instances, 1 for WebGoat itself which it uses for internal storage like users
* and 1 for lesson specific tables we use. This way we clean the data in the lesson database * and 1 for lesson specific tables we use. This way we clean the data in the lesson database
* quite easily see {@link RestartLessonService#restartLesson(String, WebGoatUser)} for how we * quite easily see {@link RestartLessonService#restartLesson()} for how we clean the lesson
* clean the lesson related tables. * related tables.
*/ */
@Bean(initMethod = "migrate") @Bean(initMethod = "migrate")
public Flyway flyWayContainer() { public Flyway flyWayContainer() {
@ -61,7 +60,7 @@ public class DatabaseConfiguration {
} }
@Bean @Bean
public LessonDataSource lessonDataSource(DataSource dataSource) { public LessonDataSource lessonDataSource() {
return new LessonDataSource(dataSource); return new LessonDataSource(dataSource());
} }
} }

View File

@ -55,8 +55,8 @@ import org.thymeleaf.templateresource.StringTemplateResource;
public class LessonTemplateResolver extends FileTemplateResolver { public class LessonTemplateResolver extends FileTemplateResolver {
private static final String PREFIX = "lesson:"; private static final String PREFIX = "lesson:";
private final ResourceLoader resourceLoader; private ResourceLoader resourceLoader;
private final Map<String, byte[]> resources = new HashMap<>(); private Map<String, byte[]> resources = new HashMap<>();
public LessonTemplateResolver(ResourceLoader resourceLoader) { public LessonTemplateResolver(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;

View File

@ -40,6 +40,7 @@ import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.i18n.Language; import org.owasp.webgoat.container.i18n.Language;
import org.owasp.webgoat.container.i18n.Messages; import org.owasp.webgoat.container.i18n.Messages;
import org.owasp.webgoat.container.i18n.PluginMessages; import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.lessons.LessonScanner;
import org.owasp.webgoat.container.session.LabelDebugger; import org.owasp.webgoat.container.session.LabelDebugger;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -73,6 +74,8 @@ public class MvcConfiguration implements WebMvcConfigurer {
private static final String UTF8 = "UTF-8"; private static final String UTF8 = "UTF-8";
private final LessonScanner lessonScanner;
@Override @Override
public void addViewControllers(ViewControllerRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login"); registry.addViewController("/login").setViewName("login");
@ -184,6 +187,28 @@ public class MvcConfiguration implements WebMvcConfigurer {
registry registry
.addResourceHandler("/fonts/**") .addResourceHandler("/fonts/**")
.addResourceLocations("classpath:/webgoat/static/fonts/"); .addResourceLocations("classpath:/webgoat/static/fonts/");
// WebGoat lessons
registry
.addResourceHandler("/images/**")
.addResourceLocations(
lessonScanner.applyPattern("classpath:/lessons/%s/images/").toArray(String[]::new));
registry
.addResourceHandler("/lesson_js/**")
.addResourceLocations(
lessonScanner.applyPattern("classpath:/lessons/%s/js/").toArray(String[]::new));
registry
.addResourceHandler("/lesson_css/**")
.addResourceLocations(
lessonScanner.applyPattern("classpath:/lessons/%s/css/").toArray(String[]::new));
registry
.addResourceHandler("/lesson_templates/**")
.addResourceLocations(
lessonScanner.applyPattern("classpath:/lessons/%s/templates/").toArray(String[]::new));
registry
.addResourceHandler("/video/**")
.addResourceLocations(
lessonScanner.applyPattern("classpath:/lessons/%s/video/").toArray(String[]::new));
} }
@Bean @Bean

View File

@ -32,27 +32,31 @@
package org.owasp.webgoat.container; package org.owasp.webgoat.container;
import java.io.File; import java.io.File;
import org.owasp.webgoat.container.session.LessonSession; import org.owasp.webgoat.container.session.UserSessionData;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@Configuration @Configuration
@ComponentScan(basePackages = {"org.owasp.webgoat.container", "org.owasp.webgoat.lessons"}) @ComponentScan(basePackages = {"org.owasp.webgoat.container", "org.owasp.webgoat.lessons"})
@PropertySource("classpath:application-webgoat.properties") @PropertySource("classpath:application-webgoat.properties")
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableJpaRepositories(basePackages = {"org.owasp.webgoat.container"})
@EntityScan(basePackages = "org.owasp.webgoat.container")
public class WebGoat { public class WebGoat {
@Autowired private UserRepository userRepository;
@Bean(name = "pluginTargetDirectory") @Bean(name = "pluginTargetDirectory")
public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) { public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) {
return new File(webgoatHome); return new File(webgoatHome);
@ -60,8 +64,21 @@ public class WebGoat {
@Bean @Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public LessonSession userSessionData() { public WebSession webSession() {
return new LessonSession(); WebGoatUser webGoatUser = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof WebGoatUser) {
webGoatUser = (WebGoatUser) principal;
} else if (principal instanceof DefaultOAuth2User) {
webGoatUser = userRepository.findByUsername(((DefaultOAuth2User) principal).getName());
}
return new WebSession(webGoatUser);
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserSessionData userSessionData() {
return new UserSessionData("test", "data");
} }
@Bean @Bean

View File

@ -35,7 +35,6 @@ import org.owasp.webgoat.container.users.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@ -58,6 +57,7 @@ public class WebSecurityConfig {
return http.authorizeHttpRequests( return http.authorizeHttpRequests(
auth -> auth ->
auth.requestMatchers( auth.requestMatchers(
"/",
"/favicon.ico", "/favicon.ico",
"/css/**", "/css/**",
"/images/**", "/images/**",
@ -65,8 +65,7 @@ public class WebSecurityConfig {
"fonts/**", "fonts/**",
"/plugins/**", "/plugins/**",
"/registration", "/registration",
"/register.mvc", "/register.mvc")
"/actuator/**")
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated()) .authenticated())
@ -98,7 +97,6 @@ public class WebSecurityConfig {
} }
@Bean @Bean
@Primary
public UserDetailsService userDetailsServiceBean() { public UserDetailsService userDetailsServiceBean() {
return userDetailsService; return userDetailsService;
} }

View File

@ -16,7 +16,7 @@ public class EnvironmentExposure implements ApplicationContextAware {
private static ApplicationContext context; private static ApplicationContext context;
public static Environment getEnv() { public static Environment getEnv() {
return null != context ? context.getEnvironment() : null; return (null != context) ? context.getEnvironment() : null;
} }
@Override @Override

View File

@ -1,8 +1,7 @@
package org.owasp.webgoat.container.asciidoc; package org.owasp.webgoat.container.asciidoc;
import java.util.Map; import java.util.Map;
import org.asciidoctor.ast.PhraseNode; import org.asciidoctor.ast.ContentNode;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.extension.InlineMacroProcessor; import org.asciidoctor.extension.InlineMacroProcessor;
public class OperatingSystemMacro extends InlineMacroProcessor { public class OperatingSystemMacro extends InlineMacroProcessor {
@ -16,8 +15,7 @@ public class OperatingSystemMacro extends InlineMacroProcessor {
} }
@Override @Override
public PhraseNode process( public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
StructuralNode contentNode, String target, Map<String, Object> attributes) {
var osName = System.getProperty("os.name"); var osName = System.getProperty("os.name");
// see // see

View File

@ -1,8 +1,7 @@
package org.owasp.webgoat.container.asciidoc; package org.owasp.webgoat.container.asciidoc;
import java.util.Map; import java.util.Map;
import org.asciidoctor.ast.PhraseNode; import org.asciidoctor.ast.ContentNode;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.extension.InlineMacroProcessor; import org.asciidoctor.extension.InlineMacroProcessor;
import org.owasp.webgoat.container.users.WebGoatUser; import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -18,8 +17,7 @@ public class UsernameMacro extends InlineMacroProcessor {
} }
@Override @Override
public PhraseNode process( public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
StructuralNode contentNode, String target, Map<String, Object> attributes) {
var auth = SecurityContextHolder.getContext().getAuthentication(); var auth = SecurityContextHolder.getContext().getAuthentication();
var username = "unknown"; var username = "unknown";
if (auth.getPrincipal() instanceof WebGoatUser webGoatUser) { if (auth.getPrincipal() instanceof WebGoatUser webGoatUser) {

View File

@ -1,8 +1,7 @@
package org.owasp.webgoat.container.asciidoc; package org.owasp.webgoat.container.asciidoc;
import java.util.Map; import java.util.Map;
import org.asciidoctor.ast.PhraseNode; import org.asciidoctor.ast.ContentNode;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.extension.InlineMacroProcessor; import org.asciidoctor.extension.InlineMacroProcessor;
public class WebGoatTmpDirMacro extends InlineMacroProcessor { public class WebGoatTmpDirMacro extends InlineMacroProcessor {
@ -16,12 +15,11 @@ public class WebGoatTmpDirMacro extends InlineMacroProcessor {
} }
@Override @Override
public PhraseNode process( public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
StructuralNode structuralNode, String target, Map<String, Object> attributes) {
var env = EnvironmentExposure.getEnv().getProperty("webgoat.server.directory"); var env = EnvironmentExposure.getEnv().getProperty("webgoat.server.directory");
// see // see
// https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used // https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used
return createPhraseNode(structuralNode, "quoted", env); return createPhraseNode(contentNode, "quoted", env);
} }
} }

View File

@ -1,8 +1,7 @@
package org.owasp.webgoat.container.asciidoc; package org.owasp.webgoat.container.asciidoc;
import java.util.Map; import java.util.Map;
import org.asciidoctor.ast.PhraseNode; import org.asciidoctor.ast.ContentNode;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.extension.InlineMacroProcessor; import org.asciidoctor.extension.InlineMacroProcessor;
public class WebGoatVersionMacro extends InlineMacroProcessor { public class WebGoatVersionMacro extends InlineMacroProcessor {
@ -16,8 +15,7 @@ public class WebGoatVersionMacro extends InlineMacroProcessor {
} }
@Override @Override
public PhraseNode process( public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
StructuralNode contentNode, String target, Map<String, Object> attributes) {
var webgoatVersion = EnvironmentExposure.getEnv().getProperty("webgoat.build.version"); var webgoatVersion = EnvironmentExposure.getEnv().getProperty("webgoat.build.version");
// see // see

View File

@ -2,8 +2,7 @@ package org.owasp.webgoat.container.asciidoc;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.asciidoctor.ast.PhraseNode; import org.asciidoctor.ast.ContentNode;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.extension.InlineMacroProcessor; import org.asciidoctor.extension.InlineMacroProcessor;
/** /**
@ -22,8 +21,7 @@ public class WebWolfMacro extends InlineMacroProcessor {
} }
@Override @Override
public PhraseNode process( public Object process(ContentNode contentNode, String linkText, Map<String, Object> attributes) {
StructuralNode contentNode, String linkText, Map<String, Object> attributes) {
var env = EnvironmentExposure.getEnv(); var env = EnvironmentExposure.getEnv();
var hostname = env.getProperty("webwolf.url"); var hostname = env.getProperty("webwolf.url");
var target = (String) attributes.getOrDefault("target", "home"); var target = (String) attributes.getOrDefault("target", "home");
@ -38,7 +36,7 @@ public class WebWolfMacro extends InlineMacroProcessor {
options.put("type", ":link"); options.put("type", ":link");
options.put("target", href); options.put("target", href);
attributes.put("window", "_blank"); attributes.put("window", "_blank");
return createPhraseNode(contentNode, "anchor", linkText, attributes, options); return createPhraseNode(contentNode, "anchor", linkText, attributes, options).convert();
} }
private boolean displayCompleteLinkNoFormatting(Map<String, Object> attributes) { private boolean displayCompleteLinkNoFormatting(Map<String, Object> attributes) {

View File

@ -25,4 +25,68 @@
package org.owasp.webgoat.container.assignments; package org.owasp.webgoat.container.assignments;
public interface AssignmentEndpoint {} import lombok.Getter;
import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.lessons.Initializeable;
import org.owasp.webgoat.container.session.UserSessionData;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AssignmentEndpoint implements Initializeable {
@Autowired private WebSession webSession;
@Autowired private UserSessionData userSessionData;
@Getter @Autowired private PluginMessages messages;
protected WebSession getWebSession() {
return webSession;
}
protected UserSessionData getUserSessionData() {
return userSessionData;
}
/**
* Convenience method for create a successful result:
*
* <p>- Assignment is set to solved - Feedback message is set to 'assignment.solved'
*
* <p>Of course you can overwrite these values in a specific lesson
*
* @return a builder for creating a result from a lesson
* @param assignment
*/
protected AttackResult.AttackResultBuilder success(AssignmentEndpoint assignment) {
return AttackResult.builder(messages)
.lessonCompleted(true)
.attemptWasMade()
.feedback("assignment.solved")
.assignment(assignment);
}
/**
* Convenience method for create a failed result:
*
* <p>- Assignment is set to not solved - Feedback message is set to 'assignment.not.solved'
*
* <p>Of course you can overwrite these values in a specific lesson
*
* @return a builder for creating a result from a lesson
* @param assignment
*/
protected AttackResult.AttackResultBuilder failed(AssignmentEndpoint assignment) {
return AttackResult.builder(messages)
.lessonCompleted(false)
.attemptWasMade()
.feedback("assignment.not.solved")
.assignment(assignment);
}
protected AttackResult.AttackResultBuilder informationMessage(AssignmentEndpoint assignment) {
return AttackResult.builder(messages).lessonCompleted(false).assignment(assignment);
}
@Override
public void initialize(WebGoatUser user) {}
}

View File

@ -0,0 +1,19 @@
package org.owasp.webgoat.container.assignments;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMethod;
/** Created by nbaars on 1/14/17. */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AssignmentPath {
String[] path() default {};
RequestMethod[] method() default {};
String value() default "";
}

View File

@ -30,18 +30,82 @@ import static org.apache.commons.text.StringEscapeUtils.escapeJson;
import lombok.Getter; import lombok.Getter;
import org.owasp.webgoat.container.i18n.PluginMessages; import org.owasp.webgoat.container.i18n.PluginMessages;
@Getter
public class AttackResult { public class AttackResult {
private boolean lessonCompleted; public static class AttackResultBuilder {
private String feedback;
private Object[] feedbackArgs;
private String output;
private Object[] outputArgs;
private final String assignment;
private boolean attemptWasMade;
private AttackResult( private boolean lessonCompleted;
private PluginMessages messages;
private Object[] feedbackArgs;
private String feedbackResourceBundleKey;
private String output;
private Object[] outputArgs;
private AssignmentEndpoint assignment;
private boolean attemptWasMade = false;
public AttackResultBuilder(PluginMessages messages) {
this.messages = messages;
}
public AttackResultBuilder lessonCompleted(boolean lessonCompleted) {
this.lessonCompleted = lessonCompleted;
this.feedbackResourceBundleKey = "lesson.completed";
return this;
}
public AttackResultBuilder lessonCompleted(boolean lessonCompleted, String resourceBundleKey) {
this.lessonCompleted = lessonCompleted;
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder feedbackArgs(Object... args) {
this.feedbackArgs = args;
return this;
}
public AttackResultBuilder feedback(String resourceBundleKey) {
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder output(String output) {
this.output = output;
return this;
}
public AttackResultBuilder outputArgs(Object... args) {
this.outputArgs = args;
return this;
}
public AttackResultBuilder attemptWasMade() {
this.attemptWasMade = true;
return this;
}
public AttackResult build() {
return new AttackResult(
lessonCompleted,
messages.getMessage(feedbackResourceBundleKey, feedbackArgs),
messages.getMessage(output, output, outputArgs),
assignment.getClass().getSimpleName(),
attemptWasMade);
}
public AttackResultBuilder assignment(AssignmentEndpoint assignment) {
this.assignment = assignment;
return this;
}
}
@Getter private boolean lessonCompleted;
@Getter private String feedback;
@Getter private String output;
@Getter private final String assignment;
@Getter private boolean attemptWasMade;
public AttackResult(
boolean lessonCompleted, boolean lessonCompleted,
String feedback, String feedback,
String output, String output,
@ -54,33 +118,11 @@ public class AttackResult {
this.attemptWasMade = attemptWasMade; this.attemptWasMade = attemptWasMade;
} }
public AttackResult( public static AttackResultBuilder builder(PluginMessages messages) {
boolean lessonCompleted, return new AttackResultBuilder(messages);
String feedback,
Object[] feedbackArgs,
String output,
Object[] outputArgs,
String assignment,
boolean attemptWasMade) {
this.lessonCompleted = lessonCompleted;
this.feedback = feedback;
this.feedbackArgs = feedbackArgs;
this.output = output;
this.outputArgs = outputArgs;
this.assignment = assignment;
this.attemptWasMade = attemptWasMade;
} }
public boolean assignmentSolved() { public boolean assignmentSolved() {
return lessonCompleted; return lessonCompleted;
} }
public AttackResult apply(PluginMessages pluginMessages) {
return new AttackResult(
lessonCompleted,
pluginMessages.getMessage(feedback, feedback, feedbackArgs),
pluginMessages.getMessage(output, output, outputArgs),
assignment,
attemptWasMade);
}
} }

View File

@ -1,130 +0,0 @@
package org.owasp.webgoat.container.assignments;
import org.owasp.webgoat.container.i18n.PluginMessages;
public class AttackResultBuilder {
private PluginMessages messages;
private boolean lessonCompleted;
private Object[] feedbackArgs;
private String feedbackResourceBundleKey;
private String output;
private Object[] outputArgs;
private AssignmentEndpoint assignment;
private boolean attemptWasMade = false;
private boolean assignmentCompleted;
public AttackResultBuilder(PluginMessages messages) {
this.messages = messages;
}
public AttackResultBuilder() {}
public AttackResultBuilder lessonCompleted(boolean lessonCompleted) {
this.lessonCompleted = lessonCompleted;
this.feedbackResourceBundleKey = "lesson.completed";
return this;
}
public AttackResultBuilder lessonCompleted(boolean lessonCompleted, String resourceBundleKey) {
this.lessonCompleted = lessonCompleted;
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder assignmentCompleted(boolean assignmentCompleted) {
this.assignmentCompleted = assignmentCompleted;
this.feedbackResourceBundleKey = "assignment.completed";
return this;
}
public AttackResultBuilder assignmentCompleted(
boolean assignmentCompleted, String resourceBundleKey) {
this.assignmentCompleted = assignmentCompleted;
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder feedbackArgs(Object... args) {
this.feedbackArgs = args;
return this;
}
public AttackResultBuilder feedback(String resourceBundleKey) {
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder output(String output) {
this.output = output;
return this;
}
public AttackResultBuilder outputArgs(Object... args) {
this.outputArgs = args;
return this;
}
public AttackResultBuilder attemptWasMade() {
this.attemptWasMade = true;
return this;
}
public AttackResult build() {
return new AttackResult(
lessonCompleted,
feedbackResourceBundleKey,
feedbackArgs,
output,
outputArgs,
assignment.getClass().getSimpleName(),
attemptWasMade);
}
public AttackResultBuilder assignment(AssignmentEndpoint assignment) {
this.assignment = assignment;
return this;
}
/**
* Convenience method for create a successful result:
*
* <p>- Assignment is set to solved - Feedback message is set to 'assignment.solved'
*
* <p>Of course you can overwrite these values in a specific lesson
*
* @return a builder for creating a result from a lesson
* @param assignment
*/
public static AttackResultBuilder success(AssignmentEndpoint assignment) {
return new AttackResultBuilder()
.lessonCompleted(true)
.assignmentCompleted(true)
.attemptWasMade()
.feedback("assignment.solved")
.assignment(assignment);
}
/**
* Convenience method for create a failed result:
*
* <p>- Assignment is set to not solved - Feedback message is set to 'assignment.not.solved'
*
* <p>Of course you can overwrite these values in a specific lesson
*
* @return a builder for creating a result from a lesson
* @param assignment
*/
public static AttackResultBuilder failed(AssignmentEndpoint assignment) {
return new AttackResultBuilder()
.lessonCompleted(false)
.assignmentCompleted(true)
.attemptWasMade()
.feedback("assignment.not.solved")
.assignment(assignment);
}
public static AttackResultBuilder informationMessage(AssignmentEndpoint assignment) {
return new AttackResultBuilder().lessonCompleted(false).assignment(assignment);
}
}

View File

@ -1,41 +0,0 @@
package org.owasp.webgoat.container.assignments;
import org.owasp.webgoat.container.i18n.PluginMessages;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/** This class intercepts the response body and applies the plugin messages to the attack result. */
@RestControllerAdvice
public class AttackResultMessageResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final PluginMessages pluginMessages;
public AttackResultMessageResponseBodyAdvice(PluginMessages pluginMessages) {
this.pluginMessages = pluginMessages;
}
@Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof AttackResult a) {
return a.apply(pluginMessages);
}
return body;
}
}

View File

@ -22,30 +22,27 @@
package org.owasp.webgoat.container.assignments; package org.owasp.webgoat.container.assignments;
import org.owasp.webgoat.container.lessons.Lesson; import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserProgress; import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice @RestControllerAdvice
public class LessonTrackerInterceptor implements ResponseBodyAdvice<Object> { public class LessonTrackerInterceptor implements ResponseBodyAdvice<Object> {
private final Course course; private UserTrackerRepository userTrackerRepository;
private final UserProgressRepository userProgressRepository; private WebSession webSession;
public LessonTrackerInterceptor(Course course, UserProgressRepository userProgressRepository) { public LessonTrackerInterceptor(
this.course = course; UserTrackerRepository userTrackerRepository, WebSession webSession) {
this.userProgressRepository = userProgressRepository; this.userTrackerRepository = userTrackerRepository;
this.webSession = webSession;
} }
@Override @Override
@ -68,30 +65,18 @@ public class LessonTrackerInterceptor implements ResponseBodyAdvice<Object> {
return o; return o;
} }
private void trackProgress(AttackResult attackResult) { protected AttackResult trackProgress(AttackResult attackResult) {
var user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
Assert.notNull(user, "User not found in SecurityContext"); if (userTracker == null) {
var username = realUsername(user); userTracker = new UserTracker(webSession.getUserName());
var userProgress = userProgressRepository.findByUser(username);
if (userProgress == null) {
userProgress = new UserProgress(username);
} }
Lesson lesson = course.getLessonByAssignment(attackResult.getAssignment());
Assert.notNull(lesson, "Lesson not found for assignment " + attackResult.getAssignment());
if (attackResult.assignmentSolved()) { if (attackResult.assignmentSolved()) {
userProgress.assignmentSolved(lesson, attackResult.getAssignment()); userTracker.assignmentSolved(webSession.getCurrentLesson(), attackResult.getAssignment());
} else { } else {
userProgress.assignmentFailed(lesson); userTracker.assignmentFailed(webSession.getCurrentLesson());
} }
userProgressRepository.save(userProgress); userTrackerRepository.save(userTracker);
}
private String realUsername(WebGoatUser user) { return attackResult;
// maybe we shouldn't hard code this with just csrf- prefix for now it works
return user.getUsername().startsWith("csrf-")
? user.getUsername().substring("csrf-".length())
: user.getUsername();
} }
} }

View File

@ -33,20 +33,42 @@ package org.owasp.webgoat.container.controller;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@Controller @Controller
public class StartLesson { public class StartLesson {
private final WebSession ws;
private final Course course; private final Course course;
public StartLesson(Course course) { public StartLesson(WebSession ws, Course course) {
this.ws = ws;
this.course = course; this.course = course;
} }
@GetMapping( /**
* start.
*
* @return a {@link ModelAndView} object.
*/
@RequestMapping(
path = "startlesson.mvc",
method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView start() {
var model = new ModelAndView();
model.addObject("course", course);
model.addObject("lesson", ws.getCurrentLesson());
model.setViewName("lesson_content");
return model;
}
@RequestMapping(
value = {"*.lesson"}, value = {"*.lesson"},
produces = "text/html") produces = "text/html")
public ModelAndView lessonPage(HttpServletRequest request) { public ModelAndView lessonPage(HttpServletRequest request) {
@ -59,7 +81,8 @@ public class StartLesson {
.findFirst() .findFirst()
.ifPresent( .ifPresent(
lesson -> { lesson -> {
request.setAttribute("lesson", lesson); ws.setCurrentLesson(lesson);
model.addObject("lesson", lesson);
}); });
return model; return model;

View File

@ -51,11 +51,10 @@ public class Assignment {
private String name; private String name;
private String path; private String path;
private boolean solved = false;
@Transient private List<String> hints; @Transient private List<String> hints;
protected Assignment() { private Assignment() {
// Hibernate // Hibernate
} }
@ -75,8 +74,4 @@ public class Assignment {
this.path = path; this.path = path;
this.hints = hints; this.hints = hints;
} }
public void solved() {
this.solved = true;
}
} }

View File

@ -34,28 +34,30 @@ import lombok.Getter;
* @since October 28, 2003 * @since October 28, 2003
*/ */
public enum Category { public enum Category {
INTRODUCTION("Introduction"), INTRODUCTION("Introduction", 5),
GENERAL("General"), GENERAL("General", 100),
A1("(A1) Broken Access Control"), A1("(A1) Broken Access Control", 301),
A2("(A2) Cryptographic Failures"), A2("(A2) Cryptographic Failures", 302),
A3("(A3) Injection"), A3("(A3) Injection", 303),
A5("(A5) Security Misconfiguration"), A5("(A5) Security Misconfiguration", 305),
A6("(A6) Vuln & Outdated Components"), A6("(A6) Vuln & Outdated Components", 306),
A7("(A7) Identity & Auth Failure"), A7("(A7) Identity & Auth Failure", 307),
A8("(A8) Software & Data Integrity"), A8("(A8) Software & Data Integrity", 308),
A9("(A9) Security Logging Failures"), A9("(A9) Security Logging Failures", 309),
A10("(A10) Server-side Request Forgery"), A10("(A10) Server-side Request Forgery", 310),
CLIENT_SIDE("Client side"), CLIENT_SIDE("Client side", 1700),
CHALLENGE("Challenges"); CHALLENGE("Challenges", 3000);
@Getter private String name; @Getter private String name;
@Getter private Integer ranking;
Category(String name) { Category(String name, Integer ranking) {
this.name = name; this.name = name;
this.ranking = ranking;
} }
@Override @Override

View File

@ -22,107 +22,58 @@
package org.owasp.webgoat.container.lessons; package org.owasp.webgoat.container.lessons;
import static java.util.stream.Collectors.groupingBy;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.*; import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.session.Course;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert; import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@Slf4j
@Configuration @Configuration
public class CourseConfiguration { public class CourseConfiguration {
private final List<Lesson> lessons; private final List<Lesson> lessons;
private final List<AssignmentEndpoint> assignments; private final List<AssignmentEndpoint> assignments;
private final String contextPath; private final Map<String, List<AssignmentEndpoint>> assignmentsByPackage;
public CourseConfiguration( public CourseConfiguration(List<Lesson> lessons, List<AssignmentEndpoint> assignments) {
List<Lesson> lessons,
List<AssignmentEndpoint> assignments,
@Value("${server.servlet.context-path}") String contextPath) {
this.lessons = lessons; this.lessons = lessons;
this.assignments = assignments; this.assignments = assignments;
this.contextPath = contextPath.equals("/") ? "" : contextPath; assignmentsByPackage =
} this.assignments.stream().collect(groupingBy(a -> a.getClass().getPackageName()));
private void attachToLessonInParentPackage(
AssignmentEndpoint assignmentEndpoint, String packageName) {
if (packageName.equals("org.owasp.webgoat.lessons")) {
throw new IllegalStateException(
"No lesson found for assignment: '%s'"
.formatted(assignmentEndpoint.getClass().getSimpleName()));
}
lessons.stream()
.filter(l -> l.getClass().getPackageName().equals(packageName))
.findFirst()
.ifPresentOrElse(
l -> l.addAssignment(toAssignment(assignmentEndpoint)),
() ->
attachToLessonInParentPackage(
assignmentEndpoint, packageName.substring(0, packageName.lastIndexOf("."))));
}
/**
* For each assignment endpoint, find the lesson in the same package or if not found, find the
* lesson in the parent package
*/
private void attachToLesson(AssignmentEndpoint assignmentEndpoint) {
lessons.stream()
.filter(
l ->
l.getClass()
.getPackageName()
.equals(assignmentEndpoint.getClass().getPackageName()))
.findFirst()
.ifPresentOrElse(
l -> l.addAssignment(toAssignment(assignmentEndpoint)),
() -> {
var assignmentPackageName = assignmentEndpoint.getClass().getPackageName();
attachToLessonInParentPackage(
assignmentEndpoint,
assignmentPackageName.substring(0, assignmentPackageName.lastIndexOf(".")));
});
}
private Assignment toAssignment(AssignmentEndpoint endpoint) {
return new Assignment(
endpoint.getClass().getSimpleName(),
getPath(endpoint.getClass()),
getHints(endpoint.getClass()));
} }
@Bean @Bean
public Course course() { public Course course() {
assignments.stream().forEach(this::attachToLesson); lessons.stream().forEach(l -> l.setAssignments(createAssignment(l)));
// Check if all assignments are attached to a lesson
var assignmentsAttachedToLessons =
lessons.stream().mapToInt(l -> l.getAssignments().size()).sum();
Assert.isTrue(
assignmentsAttachedToLessons == assignments.size(),
"Not all assignments are attached to a lesson, please check the configuration. The"
+ " following assignments are not attached to any lesson: "
+ findDiff());
return new Course(lessons); return new Course(lessons);
} }
private List<String> findDiff() { private List<Assignment> createAssignment(Lesson lesson) {
var matchedToLessons = var endpoints = assignmentsByPackage.get(lesson.getClass().getPackageName());
lessons.stream().flatMap(l -> l.getAssignments().stream()).map(a -> a.getName()).toList(); if (CollectionUtils.isEmpty(endpoints)) {
var allAssignments = assignments.stream().map(a -> a.getClass().getSimpleName()).toList(); log.warn("Lesson: {} has no endpoints, is this intentionally?", lesson.getTitle());
return new ArrayList<>();
var diff = new ArrayList<>(allAssignments); }
diff.removeAll(matchedToLessons); return endpoints.stream()
return diff; .map(
e ->
new Assignment(
e.getClass().getSimpleName(), getPath(e.getClass()), getHints(e.getClass())))
.toList();
} }
private String getPath(Class<? extends AssignmentEndpoint> e) { private String getPath(Class<? extends AssignmentEndpoint> e) {
@ -130,7 +81,7 @@ public class CourseConfiguration {
if (methodReturnTypeIsOfTypeAttackResult(m)) { if (methodReturnTypeIsOfTypeAttackResult(m)) {
var mapping = getMapping(m); var mapping = getMapping(m);
if (mapping != null) { if (mapping != null) {
return contextPath + mapping; return mapping;
} }
} }
} }

View File

@ -6,7 +6,7 @@ import org.owasp.webgoat.container.users.WebGoatUser;
* Interface for initialization of a lesson. It is called when a new user is added to WebGoat and * Interface for initialization of a lesson. It is called when a new user is added to WebGoat and
* when a users reset a lesson. Make sure to clean beforehand and then re-initialize the lesson. * when a users reset a lesson. Make sure to clean beforehand and then re-initialize the lesson.
*/ */
public interface Initializable { public interface Initializeable {
default void initialize(WebGoatUser webGoatUser) {} void initialize(WebGoatUser webGoatUser);
} }

View File

@ -22,7 +22,6 @@
package org.owasp.webgoat.container.lessons; package org.owasp.webgoat.container.lessons;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -31,10 +30,13 @@ import lombok.Setter;
@Setter @Setter
public abstract class Lesson { public abstract class Lesson {
private List<Assignment> assignments = new ArrayList<>(); private static int count = 1;
private Integer id = null;
private List<Assignment> assignments;
public void addAssignment(Assignment assignment) { /** Constructor for the Lesson object */
this.assignments.add(assignment); protected Lesson() {
id = ++count;
} }
/** /**
@ -42,9 +44,9 @@ public abstract class Lesson {
* *
* @return a {@link java.lang.String} object. * @return a {@link java.lang.String} object.
*/ */
public LessonName getName() { public String getName() {
String className = getClass().getName(); String className = getClass().getName();
return new LessonName(className.substring(className.lastIndexOf('.') + 1)); return className.substring(className.lastIndexOf('.') + 1);
} }
/** /**
@ -114,10 +116,6 @@ public abstract class Lesson {
return this.getClass().getSimpleName(); return this.getClass().getSimpleName();
} }
/**
* This is used in Thymeleaf to construct the HTML to load the lesson content from. See
* lesson_content.html
*/
public final String getPackage() { public final String getPackage() {
var packageName = this.getClass().getPackageName(); var packageName = this.getClass().getPackageName();
// package name is the direct package name below lessons (any subpackage will be removed) // package name is the direct package name below lessons (any subpackage will be removed)

View File

@ -35,5 +35,6 @@ package org.owasp.webgoat.container.lessons;
*/ */
public enum LessonMenuItemType { public enum LessonMenuItemType {
CATEGORY, CATEGORY,
LESSON LESSON,
STAGE
} }

View File

@ -1,21 +0,0 @@
package org.owasp.webgoat.container.lessons;
import org.springframework.util.Assert;
/**
* Wrapper class for the name of a lesson. This class is used to ensure that the lesson name is not
* null and does not contain the ".lesson" suffix. The front-end passes the lesson name as a string
* to the back-end, which then creates a new LessonName object with the lesson name as a parameter.
* The constructor of the LessonName class checks if the lesson name is null and removes the
* ".lesson" suffix if it is present.
*
* @param lessonName
*/
public record LessonName(String lessonName) {
public LessonName {
Assert.notNull(lessonName, "Lesson name cannot be null");
if (lessonName.contains(".lesson")) {
lessonName = lessonName.substring(0, lessonName.indexOf(".lesson"));
}
}
}

View File

@ -0,0 +1,42 @@
package org.owasp.webgoat.container.lessons;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LessonScanner {
private static final Pattern lessonPattern = Pattern.compile("^.*/lessons/([^/]*)/.*$");
@Getter private final Set<String> lessons = new HashSet<>();
public LessonScanner(ResourcePatternResolver resourcePatternResolver) {
try {
var resources = resourcePatternResolver.getResources("classpath:/lessons/*/*");
for (var resource : resources) {
// WG can run as a fat jar or as directly from file system we need to support both so use
// the URL
var url = resource.getURL();
var matcher = lessonPattern.matcher(url.toString());
if (matcher.matches()) {
lessons.add(matcher.group(1));
}
}
log.debug("Found {} lessons", lessons.size());
} catch (IOException e) {
log.warn("No lessons found...");
}
}
public List<String> applyPattern(String pattern) {
return lessons.stream().map(lesson -> String.format(pattern, lesson)).toList();
}
}

View File

@ -1,3 +0,0 @@
package org.owasp.webgoat.container.report;
record LessonStatistics(String name, boolean solved, int numberOfAttempts) {}

View File

@ -1,88 +0,0 @@
/**
* *************************************************************************************************
*
* <p>
*
* <p>This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
*
* <p>Copyright (c) 2002 - 2014 Bruce Mayhew
*
* <p>This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* <p>Getting Source ==============
*
* <p>Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository
* for free software projects.
*/
package org.owasp.webgoat.container.report;
import java.util.List;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ReportCardController {
private final UserProgressRepository userProgressRepository;
private final Course course;
private final PluginMessages pluginMessages;
public ReportCardController(
UserProgressRepository userProgressRepository, Course course, PluginMessages pluginMessages) {
this.userProgressRepository = userProgressRepository;
this.course = course;
this.pluginMessages = pluginMessages;
}
/**
* Endpoint which generates the report card for the current use to show the stats on the solved
* lessons
*/
@GetMapping(path = "/service/reportcard.mvc", produces = "application/json")
@ResponseBody
public ReportCard reportCard(@CurrentUsername String username) {
var userProgress = userProgressRepository.findByUser(username);
var lessonStatistics =
course.getLessons().stream()
.map(
lesson -> {
var lessonTracker = userProgress.getLessonProgress(lesson);
return new LessonStatistics(
pluginMessages.getMessage(lesson.getTitle()),
lessonTracker.isLessonSolved(),
lessonTracker.getNumberOfAttempts());
})
.toList();
return new ReportCard(
course.getTotalOfLessons(),
course.getTotalOfAssignments(),
userProgress.numberOfAssignmentsSolved(),
userProgress.numberOfLessonsSolved(),
lessonStatistics);
}
private record ReportCard(
int totalNumberOfLessons,
int totalNumberOfAssignments,
long numberOfAssignmentsSolved,
long numberOfLessonsSolved,
List<LessonStatistics> lessonStatistics) {}
private record LessonStatistics(String name, boolean solved, int numberOfAttempts) {}
}

View File

@ -10,24 +10,26 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import org.owasp.webgoat.container.lessons.Assignment; import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Hint; import org.owasp.webgoat.container.lessons.Hint;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* HintService class.
*
* @author rlawson
* @version $Id: $Id
*/
@RestController @RestController
public class HintService { public class HintService {
public static final String URL_HINTS_MVC = "/service/hint.mvc"; public static final String URL_HINTS_MVC = "/service/hint.mvc";
private final List<Hint> allHints; private final WebSession webSession;
public HintService(Course course) { public HintService(WebSession webSession) {
this.allHints = this.webSession = webSession;
course.getLessons().stream()
.flatMap(lesson -> lesson.getAssignments().stream())
.map(this::createHint)
.flatMap(Collection::stream)
.toList();
} }
/** /**
@ -38,7 +40,15 @@ public class HintService {
@GetMapping(path = URL_HINTS_MVC, produces = "application/json") @GetMapping(path = URL_HINTS_MVC, produces = "application/json")
@ResponseBody @ResponseBody
public List<Hint> getHints() { public List<Hint> getHints() {
return allHints; Lesson l = webSession.getCurrentLesson();
return createAssignmentHints(l);
}
private List<Hint> createAssignmentHints(Lesson l) {
if (l != null) {
return l.getAssignments().stream().map(this::createHint).flatMap(Collection::stream).toList();
}
return List.of();
} }
private List<Hint> createHint(Assignment a) { private List<Hint> createHint(Assignment a) {

View File

@ -1,24 +1,33 @@
package org.owasp.webgoat.container.service; package org.owasp.webgoat.container.service;
import lombok.RequiredArgsConstructor; import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonInfoModel; import org.owasp.webgoat.container.lessons.LessonInfoModel;
import org.owasp.webgoat.container.lessons.LessonName; import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.session.Course; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* LessonInfoService class.
*
* @author dm
* @version $Id: $Id
*/
@RestController @RestController
@RequiredArgsConstructor @AllArgsConstructor
public class LessonInfoService { public class LessonInfoService {
private final Course course; private final WebSession webSession;
@GetMapping(path = "/service/lessoninfo.mvc/{lesson}") /**
public @ResponseBody LessonInfoModel getLessonInfo( * getLessonInfo.
@PathVariable("lesson") LessonName lessonName) { *
var lesson = course.getLessonByName(lessonName); * @return a {@link LessonInfoModel} object.
*/
@RequestMapping(path = "/service/lessoninfo.mvc", produces = "application/json")
public @ResponseBody LessonInfoModel getLessonInfo() {
Lesson lesson = webSession.getCurrentLesson();
return new LessonInfoModel(lesson.getTitle(), false, false, false); return new LessonInfoModel(lesson.getTitle(), false, false, false);
} }
} }

View File

@ -30,16 +30,18 @@ package org.owasp.webgoat.container.service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername; import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Category; import org.owasp.webgoat.container.lessons.Category;
import org.owasp.webgoat.container.lessons.Lesson; import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonMenuItem; import org.owasp.webgoat.container.lessons.LessonMenuItem;
import org.owasp.webgoat.container.lessons.LessonMenuItemType; import org.owasp.webgoat.container.lessons.LessonMenuItemType;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.LessonProgress; import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserProgress; import org.owasp.webgoat.container.users.LessonTracker;
import org.owasp.webgoat.container.users.UserProgressRepository; import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -57,7 +59,8 @@ public class LessonMenuService {
public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc"; public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc";
private final Course course; private final Course course;
private UserProgressRepository userTrackerRepository; private final WebSession webSession;
private UserTrackerRepository userTrackerRepository;
@Value("#{'${exclude.categories}'.split(',')}") @Value("#{'${exclude.categories}'.split(',')}")
private List<String> excludeCategories; private List<String> excludeCategories;
@ -71,13 +74,10 @@ public class LessonMenuService {
* @return a {@link java.util.List} object. * @return a {@link java.util.List} object.
*/ */
@RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json") @RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json")
public @ResponseBody List<LessonMenuItem> showLeftNav(@CurrentUsername String username) { public @ResponseBody List<LessonMenuItem> showLeftNav() {
// TODO: this looks way too complicated. Either we save it incorrectly or we miss something to
// easily find out
// if a lesson if solved or not.
List<LessonMenuItem> menu = new ArrayList<>(); List<LessonMenuItem> menu = new ArrayList<>();
List<Category> categories = course.getCategories(); List<Category> categories = course.getCategories();
UserProgress userTracker = userTrackerRepository.findByUser(username); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
for (Category category : categories) { for (Category category : categories) {
if (excludeCategories.contains(category.name())) { if (excludeCategories.contains(category.name())) {
@ -97,14 +97,28 @@ public class LessonMenuService {
lessonItem.setName(lesson.getTitle()); lessonItem.setName(lesson.getTitle());
lessonItem.setLink(lesson.getLink()); lessonItem.setLink(lesson.getLink());
lessonItem.setType(LessonMenuItemType.LESSON); lessonItem.setType(LessonMenuItemType.LESSON);
LessonProgress lessonTracker = userTracker.getLessonProgress(lesson); LessonTracker lessonTracker = userTracker.getLessonTracker(lesson);
boolean lessonSolved = lessonTracker.isLessonSolved(); boolean lessonSolved = lessonCompleted(lessonTracker.getLessonOverview(), lesson);
lessonItem.setComplete(lessonSolved); lessonItem.setComplete(lessonSolved);
categoryItem.addChild(lessonItem); categoryItem.addChild(lessonItem);
} }
categoryItem.getChildren().sort(Comparator.comparingInt(LessonMenuItem::getRanking)); categoryItem.getChildren().sort((o1, o2) -> o1.getRanking() - o2.getRanking());
menu.add(categoryItem); menu.add(categoryItem);
} }
return menu; return menu;
} }
private boolean lessonCompleted(Map<Assignment, Boolean> map, Lesson currentLesson) {
boolean result = true;
for (Map.Entry<Assignment, Boolean> entry : map.entrySet()) {
Assignment storedAssignment = entry.getKey();
for (Assignment lessonAssignment : currentLesson.getAssignments()) {
if (lessonAssignment.getName().equals(storedAssignment.getName())) {
result = result && entry.getValue();
break;
}
}
}
return result;
}
} }

View File

@ -4,15 +4,11 @@ import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.lessons.Assignment; import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.LessonName; import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
@ -24,8 +20,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor @RequiredArgsConstructor
public class LessonProgressService { public class LessonProgressService {
private final UserProgressRepository userProgressRepository; private final UserTrackerRepository userTrackerRepository;
private final Course course; private final WebSession webSession;
/** /**
* Endpoint for fetching the complete lesson overview which informs the user about whether all the * Endpoint for fetching the complete lesson overview which informs the user about whether all the
@ -33,19 +29,19 @@ public class LessonProgressService {
* *
* @return list of assignments * @return list of assignments
*/ */
@GetMapping(value = "/service/lessonoverview.mvc/{lesson}") @RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
@ResponseBody @ResponseBody
public List<LessonOverview> lessonOverview( public List<LessonOverview> lessonOverview() {
@PathVariable("lesson") LessonName lessonName, @CurrentUsername String username) { var userTracker = userTrackerRepository.findByUser(webSession.getUserName());
var userProgress = userProgressRepository.findByUser(username); var currentLesson = webSession.getCurrentLesson();
var lesson = course.getLessonByName(lessonName);
Assert.isTrue(lesson != null, "Lesson not found: " + lessonName); if (currentLesson != null) {
var lessonTracker = userTracker.getLessonTracker(currentLesson);
var lessonProgress = userProgress.getLessonProgress(lesson); return lessonTracker.getLessonOverview().entrySet().stream()
return lessonProgress.getLessonOverview().entrySet().stream() .map(entry -> new LessonOverview(entry.getKey(), entry.getValue()))
.map(entry -> new LessonOverview(entry.getKey(), entry.getValue())) .toList();
.toList(); }
return List.of();
} }
@AllArgsConstructor @AllArgsConstructor

View File

@ -0,0 +1,34 @@
package org.owasp.webgoat.container.service;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* LessonTitleService class.
*
* @author dm
* @version $Id: $Id
*/
@Controller
public class LessonTitleService {
private final WebSession webSession;
public LessonTitleService(final WebSession webSession) {
this.webSession = webSession;
}
/**
* Returns the title for the current attack
*
* @return a {@link java.lang.String} object.
*/
@RequestMapping(path = "/service/lessontitle.mvc", produces = "application/html")
public @ResponseBody String showPlan() {
Lesson lesson = webSession.getCurrentLesson();
return lesson != null ? lesson.getTitle() : "";
}
}

View File

@ -0,0 +1,105 @@
/**
* *************************************************************************************************
*
* <p>
*
* <p>This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
*
* <p>Copyright (c) 2002 - 2014 Bruce Mayhew
*
* <p>This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* <p>Getting Source ==============
*
* <p>Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository
* for free software projects.
*/
package org.owasp.webgoat.container.service;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.LessonTracker;
import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* ReportCardService
*
* @author nbaars
* @version $Id: $Id
*/
@Controller
@AllArgsConstructor
public class ReportCardService {
private final WebSession webSession;
private final UserTrackerRepository userTrackerRepository;
private final Course course;
private final PluginMessages pluginMessages;
/**
* Endpoint which generates the report card for the current use to show the stats on the solved
* lessons
*/
@GetMapping(path = "/service/reportcard.mvc", produces = "application/json")
@ResponseBody
public ReportCard reportCard() {
final ReportCard reportCard = new ReportCard();
reportCard.setTotalNumberOfLessons(course.getTotalOfLessons());
reportCard.setTotalNumberOfAssignments(course.getTotalOfAssignments());
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
reportCard.setNumberOfAssignmentsSolved(userTracker.numberOfAssignmentsSolved());
reportCard.setNumberOfLessonsSolved(userTracker.numberOfLessonsSolved());
for (Lesson lesson : course.getLessons()) {
LessonTracker lessonTracker = userTracker.getLessonTracker(lesson);
final LessonStatistics lessonStatistics = new LessonStatistics();
lessonStatistics.setName(pluginMessages.getMessage(lesson.getTitle()));
lessonStatistics.setNumberOfAttempts(lessonTracker.getNumberOfAttempts());
lessonStatistics.setSolved(lessonTracker.isLessonSolved());
reportCard.lessonStatistics.add(lessonStatistics);
}
return reportCard;
}
@Getter
@Setter
private final class ReportCard {
private int totalNumberOfLessons;
private int totalNumberOfAssignments;
private int solvedLessons;
private int numberOfAssignmentsSolved;
private int numberOfLessonsSolved;
private List<LessonStatistics> lessonStatistics = new ArrayList<>();
}
@Setter
@Getter
private final class LessonStatistics {
private String name;
private boolean solved;
private int numberOfAttempts;
}
}

View File

@ -29,17 +29,14 @@ import java.util.function.Function;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.CurrentUser; import org.owasp.webgoat.container.lessons.Initializeable;
import org.owasp.webgoat.container.lessons.Initializable; import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonName; import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserProgress; import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
@Controller @Controller
@ -47,25 +44,25 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@Slf4j @Slf4j
public class RestartLessonService { public class RestartLessonService {
private final Course course; private final WebSession webSession;
private final UserProgressRepository userTrackerRepository; private final UserTrackerRepository userTrackerRepository;
private final Function<String, Flyway> flywayLessons; private final Function<String, Flyway> flywayLessons;
private final List<Initializable> lessonsToInitialize; private final List<Initializeable> lessonsToInitialize;
@GetMapping(path = "/service/restartlesson.mvc/{lesson}") @RequestMapping(path = "/service/restartlesson.mvc", produces = "text/text")
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void restartLesson( public void restartLesson() {
@PathVariable("lesson") LessonName lessonName, @CurrentUser WebGoatUser user) { Lesson al = webSession.getCurrentLesson();
var lesson = course.getLessonByName(lessonName); log.debug("Restarting lesson: " + al);
UserProgress userTracker = userTrackerRepository.findByUser(user.getUsername()); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
userTracker.reset(lesson); userTracker.reset(al);
userTrackerRepository.save(userTracker); userTrackerRepository.save(userTracker);
var flyway = flywayLessons.apply(user.getUsername()); var flyway = flywayLessons.apply(webSession.getUserName());
flyway.clean(); flyway.clean();
flyway.migrate(); flyway.migrate();
lessonsToInitialize.forEach(i -> i.initialize(user)); lessonsToInitialize.forEach(i -> i.initialize(webSession.getUser()));
} }
} }

View File

@ -7,9 +7,8 @@
package org.owasp.webgoat.container.service; package org.owasp.webgoat.container.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUser;
import org.owasp.webgoat.container.i18n.Messages; import org.owasp.webgoat.container.i18n.Messages;
import org.owasp.webgoat.container.users.WebGoatUser; import org.owasp.webgoat.container.session.WebSession;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -18,17 +17,17 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SessionService { public class SessionService {
private final WebSession webSession;
private final RestartLessonService restartLessonService; private final RestartLessonService restartLessonService;
private final Messages messages; private final Messages messages;
@RequestMapping(path = "/service/enable-security.mvc", produces = "application/json") @RequestMapping(path = "/service/enable-security.mvc", produces = "application/json")
@ResponseBody @ResponseBody
public String applySecurity(@CurrentUser WebGoatUser user) { public String applySecurity() {
// webSession.toggleSecurity(); webSession.toggleSecurity();
// restartLessonService.restartLesson(user); restartLessonService.restartLesson();
// TODO disabled for now var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled";
// var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled"; return messages.getMessage(msg);
return messages.getMessage("Not working...");
} }
} }

View File

@ -4,7 +4,6 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.lessons.Category; import org.owasp.webgoat.container.lessons.Category;
import org.owasp.webgoat.container.lessons.Lesson; import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonName;
/** /**
* ************************************************************************************************ * ************************************************************************************************
@ -97,21 +96,4 @@ public class Course {
return this.lessons.stream() return this.lessons.stream()
.reduce(0, (total, lesson) -> lesson.getAssignments().size() + total, Integer::sum); .reduce(0, (total, lesson) -> lesson.getAssignments().size() + total, Integer::sum);
} }
public Lesson getLessonByName(LessonName lessonName) {
return lessons.stream()
.filter(lesson -> lesson.getName().equals(lessonName))
.findFirst()
.orElse(null);
}
public Lesson getLessonByAssignment(String assignmentName) {
return lessons.stream()
.filter(
lesson ->
lesson.getAssignments().stream()
.anyMatch(assignment -> assignment.getName().equals(assignmentName)))
.findFirst()
.orElse(null);
}
} }

View File

@ -1,44 +0,0 @@
package org.owasp.webgoat.container.session;
import java.util.HashMap;
import java.util.Map;
/**
* This class is responsible for managing user session data within a lesson. It uses a HashMap to
* store key-value pairs representing session data.
*/
public class LessonSession {
private Map<String, Object> userSessionData = new HashMap<>();
/** Default constructor initializing an empty session. */
public LessonSession() {}
/**
* Retrieves the value associated with the given key.
*
* @param key the key for the session data
* @return the value associated with the key, or null if the key does not exist
*/
public Object getValue(String key) {
if (!userSessionData.containsKey(key)) {
return null;
}
// else
return userSessionData.get(key);
}
/**
* Sets the value for the given key. If the key already exists, its value is updated.
*
* @param key the key for the session data
* @param value the value to be associated with the key
*/
public void setValue(String key, Object value) {
if (userSessionData.containsKey(key)) {
userSessionData.replace(key, value);
} else {
userSessionData.put(key, value);
}
}
}

View File

@ -0,0 +1,32 @@
package org.owasp.webgoat.container.session;
import java.util.HashMap;
/** Created by jason on 1/4/17. */
public class UserSessionData {
private HashMap<String, Object> userSessionData = new HashMap<>();
public UserSessionData() {}
public UserSessionData(String key, String value) {
setValue(key, value);
}
// GETTERS & SETTERS
public Object getValue(String key) {
if (!userSessionData.containsKey(key)) {
return null;
}
// else
return userSessionData.get(key);
}
public void setValue(String key, Object value) {
if (userSessionData.containsKey(key)) {
userSessionData.replace(key, value);
} else {
userSessionData.put(key, value);
}
}
}

View File

@ -0,0 +1,88 @@
package org.owasp.webgoat.container.session;
import java.io.Serializable;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.users.WebGoatUser;
/**
* *************************************************************************************************
*
* <p>
*
* <p>This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
*
* <p>Copyright (c) 2002 - 2014 Bruce Mayhew
*
* <p>This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* <p>Getting Source ==============
*
* <p>Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository
* for free software projects.
*
* @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
* @author Bruce Mayhew <a href="http://code.google.com/p/webgoat">WebGoat</a>
* @version $Id: $Id
* @since October 28, 2003
*/
public class WebSession implements Serializable {
private static final long serialVersionUID = -4270066103101711560L;
private WebGoatUser currentUser;
private transient Lesson currentLesson;
private boolean securityEnabled;
public WebSession(WebGoatUser webGoatUser) {
this.currentUser = webGoatUser;
}
/**
* Setter for the field <code>currentScreen</code>.
*
* @param lesson current lesson
*/
public void setCurrentLesson(Lesson lesson) {
this.currentLesson = lesson;
}
/**
* getCurrentLesson.
*
* @return a {@link Lesson} object.
*/
public Lesson getCurrentLesson() {
return this.currentLesson;
}
/**
* Gets the userName attribute of the WebSession object
*
* @return The userName value
*/
public String getUserName() {
return currentUser.getUsername();
}
public WebGoatUser getUser() {
return currentUser;
}
public void toggleSecurity() {
this.securityEnabled = !this.securityEnabled;
}
public boolean isSecurityEnabled() {
return securityEnabled;
}
}

View File

@ -52,7 +52,7 @@ import org.owasp.webgoat.container.lessons.Lesson;
*/ */
@Entity @Entity
@EqualsAndHashCode @EqualsAndHashCode
public class LessonProgress { public class LessonTracker {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@ -61,22 +61,25 @@ public class LessonProgress {
@Getter private String lessonName; @Getter private String lessonName;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private final Set<Assignment> assignments = new HashSet<>(); private final Set<Assignment> solvedAssignments = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private final Set<Assignment> allAssignments = new HashSet<>();
@Getter private int numberOfAttempts = 0; @Getter private int numberOfAttempts = 0;
@Version private Integer version; @Version private Integer version;
protected LessonProgress() { private LessonTracker() {
// JPA // JPA
} }
public LessonProgress(Lesson lesson) { public LessonTracker(Lesson lesson) {
lessonName = lesson.getId(); lessonName = lesson.getId();
assignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments()); allAssignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments());
} }
public Optional<Assignment> getAssignment(String name) { public Optional<Assignment> getAssignment(String name) {
return assignments.stream().filter(a -> a.getName().equals(name)).findFirst(); return allAssignments.stream().filter(a -> a.getName().equals(name)).findFirst();
} }
/** /**
@ -85,14 +88,14 @@ public class LessonProgress {
* @param solvedAssignment the assignment which the user solved * @param solvedAssignment the assignment which the user solved
*/ */
public void assignmentSolved(String solvedAssignment) { public void assignmentSolved(String solvedAssignment) {
getAssignment(solvedAssignment).ifPresent(Assignment::solved); getAssignment(solvedAssignment).ifPresent(solvedAssignments::add);
} }
/** /**
* @return did they user solved all solvedAssignments for the lesson? * @return did they user solved all solvedAssignments for the lesson?
*/ */
public boolean isLessonSolved() { public boolean isLessonSolved() {
return assignments.stream().allMatch(Assignment::isSolved); return allAssignments.size() == solvedAssignments.size();
} }
/** Increase the number attempts to solve the lesson */ /** Increase the number attempts to solve the lesson */
@ -102,17 +105,18 @@ public class LessonProgress {
/** Reset the tracker. We do not reset the number of attempts here! */ /** Reset the tracker. We do not reset the number of attempts here! */
void reset() { void reset() {
assignments.clear(); solvedAssignments.clear();
} }
/** /**
* @return list containing all the assignments solved or not * @return list containing all the assignments solved or not
*/ */
public Map<Assignment, Boolean> getLessonOverview() { public Map<Assignment, Boolean> getLessonOverview() {
return assignments.stream().collect(Collectors.toMap(a -> a, Assignment::isSolved)); List<Assignment> notSolved =
} allAssignments.stream().filter(i -> !solvedAssignments.contains(i)).toList();
Map<Assignment, Boolean> overview =
long numberOfSolvedAssignments() { notSolved.stream().collect(Collectors.toMap(a -> a, b -> false));
return assignments.size(); overview.putAll(solvedAssignments.stream().collect(Collectors.toMap(a -> a, b -> true)));
return overview;
} }
} }

View File

@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RestController;
@AllArgsConstructor @AllArgsConstructor
public class Scoreboard { public class Scoreboard {
private final UserProgressRepository userTrackerRepository; private final UserTrackerRepository userTrackerRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final Course course; private final Course course;
private final PluginMessages pluginMessages; private final PluginMessages pluginMessages;
@ -46,7 +46,7 @@ public class Scoreboard {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private List<String> challengesSolved(UserProgress userTracker) { private List<String> challengesSolved(UserTracker userTracker) {
List<String> challenges = List<String> challenges =
List.of( List.of(
"Challenge1", "Challenge1",
@ -59,10 +59,10 @@ public class Scoreboard {
"Challenge8", "Challenge8",
"Challenge9"); "Challenge9");
return challenges.stream() return challenges.stream()
.map(userTracker::getLessonProgress) .map(userTracker::getLessonTracker)
.flatMap(Optional::stream) .flatMap(Optional::stream)
.filter(LessonProgress::isLessonSolved) .filter(LessonTracker::isLessonSolved)
.map(LessonProgress::getLessonName) .map(LessonTracker::getLessonName)
.map(this::toLessonTitle) .map(this::toLessonTitle)
.toList(); .toList();
} }

View File

@ -1,9 +0,0 @@
package org.owasp.webgoat.container.users;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserProgressRepository extends JpaRepository<UserProgress, String> {
// TODO: make optional
UserProgress findByUser(String user);
}

View File

@ -4,7 +4,7 @@ import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.lessons.Initializable; import org.owasp.webgoat.container.lessons.Initializeable;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -19,10 +19,10 @@ import org.springframework.stereotype.Service;
public class UserService implements UserDetailsService { public class UserService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserProgressRepository userTrackerRepository; private final UserTrackerRepository userTrackerRepository;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final Function<String, Flyway> flywayLessons; private final Function<String, Flyway> flywayLessons;
private final List<Initializable> lessonInitializables; private final List<Initializeable> lessonInitializables;
@Override @Override
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException { public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
@ -43,7 +43,7 @@ public class UserService implements UserDetailsService {
if (!userAlreadyExists) { if (!userAlreadyExists) {
userTrackerRepository.save( userTrackerRepository.save(
new UserProgress(username)); // if user previously existed it will not get another tracker new UserTracker(username)); // if user previously existed it will not get another tracker
createLessonsForUser(webGoatUser); createLessonsForUser(webGoatUser);
} }
} }

View File

@ -9,10 +9,13 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Lesson; import org.owasp.webgoat.container.lessons.Lesson;
/** /**
@ -49,7 +52,7 @@ import org.owasp.webgoat.container.lessons.Lesson;
@Slf4j @Slf4j
@Entity @Entity
@EqualsAndHashCode @EqualsAndHashCode
public class UserProgress { public class UserTracker {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@ -59,11 +62,11 @@ public class UserProgress {
private String user; private String user;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<LessonProgress> lessonProgress = new HashSet<>(); private Set<LessonTracker> lessonTrackers = new HashSet<>();
protected UserProgress() {} private UserTracker() {}
public UserProgress(final String user) { public UserTracker(final String user) {
this.user = user; this.user = user;
} }
@ -73,15 +76,15 @@ public class UserProgress {
* @param lesson the lesson * @param lesson the lesson
* @return a lesson tracker created if not already present * @return a lesson tracker created if not already present
*/ */
public LessonProgress getLessonProgress(Lesson lesson) { public LessonTracker getLessonTracker(Lesson lesson) {
Optional<LessonProgress> progress = Optional<LessonTracker> lessonTracker =
lessonProgress.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst(); lessonTrackers.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst();
if (!progress.isPresent()) { if (!lessonTracker.isPresent()) {
LessonProgress newLessonTracker = new LessonProgress(lesson); LessonTracker newLessonTracker = new LessonTracker(lesson);
lessonProgress.add(newLessonTracker); lessonTrackers.add(newLessonTracker);
return newLessonTracker; return newLessonTracker;
} else { } else {
return progress.get(); return lessonTracker.get();
} }
} }
@ -91,34 +94,43 @@ public class UserProgress {
* @param id the id of the lesson * @param id the id of the lesson
* @return optional due to the fact we can only create a lesson tracker based on a lesson * @return optional due to the fact we can only create a lesson tracker based on a lesson
*/ */
public Optional<LessonProgress> getLessonProgress(String id) { public Optional<LessonTracker> getLessonTracker(String id) {
return lessonProgress.stream().filter(l -> l.getLessonName().equals(id)).findFirst(); return lessonTrackers.stream().filter(l -> l.getLessonName().equals(id)).findFirst();
} }
public void assignmentSolved(Lesson lesson, String assignmentName) { public void assignmentSolved(Lesson lesson, String assignmentName) {
LessonProgress progress = getLessonProgress(lesson); LessonTracker lessonTracker = getLessonTracker(lesson);
progress.incrementAttempts(); lessonTracker.incrementAttempts();
progress.assignmentSolved(assignmentName); lessonTracker.assignmentSolved(assignmentName);
} }
public void assignmentFailed(Lesson lesson) { public void assignmentFailed(Lesson lesson) {
LessonProgress progress = getLessonProgress(lesson); LessonTracker lessonTracker = getLessonTracker(lesson);
progress.incrementAttempts(); lessonTracker.incrementAttempts();
} }
public void reset(Lesson al) { public void reset(Lesson al) {
LessonProgress progress = getLessonProgress(al); LessonTracker lessonTracker = getLessonTracker(al);
progress.reset(); lessonTracker.reset();
} }
public long numberOfLessonsSolved() { public int numberOfLessonsSolved() {
return lessonProgress.stream().filter(LessonProgress::isLessonSolved).count(); int numberOfLessonsSolved = 0;
for (LessonTracker lessonTracker : lessonTrackers) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
}
}
return numberOfLessonsSolved;
} }
public long numberOfAssignmentsSolved() { public int numberOfAssignmentsSolved() {
return lessonProgress.stream() int numberOfAssignmentsSolved = 0;
.map(LessonProgress::numberOfSolvedAssignments) for (LessonTracker lessonTracker : lessonTrackers) {
.mapToLong(Long::valueOf) Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
.sum(); numberOfAssignmentsSolved =
lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
}
return numberOfAssignmentsSolved;
} }
} }

View File

@ -0,0 +1,12 @@
package org.owasp.webgoat.container.users;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author nbaars
* @since 4/30/17.
*/
public interface UserTrackerRepository extends JpaRepository<UserTracker, String> {
UserTracker findByUser(String user);
}

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.authbypass; package org.owasp.webgoat.lessons.authbypass;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
@ -35,7 +32,9 @@ import java.util.Map;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.LessonSession; import org.owasp.webgoat.container.session.UserSessionData;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -49,13 +48,11 @@ import org.springframework.web.bind.annotation.RestController;
"auth-bypass.hints.verify.3", "auth-bypass.hints.verify.3",
"auth-bypass.hints.verify.4" "auth-bypass.hints.verify.4"
}) })
public class VerifyAccount implements AssignmentEndpoint { public class VerifyAccount extends AssignmentEndpoint {
private final LessonSession userSessionData; @Autowired private WebSession webSession;
public VerifyAccount(LessonSession userSessionData) { @Autowired UserSessionData userSessionData;
this.userSessionData = userSessionData;
}
@PostMapping( @PostMapping(
path = "/auth-bypass/verify-account", path = "/auth-bypass/verify-account",

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.bypassrestrictions; package org.owasp.webgoat.lessons.bypassrestrictions;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -33,7 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class BypassRestrictionsFieldRestrictions implements AssignmentEndpoint { public class BypassRestrictionsFieldRestrictions extends AssignmentEndpoint {
@PostMapping("/BypassRestrictions/FieldRestrictions") @PostMapping("/BypassRestrictions/FieldRestrictions")
@ResponseBody @ResponseBody

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.bypassrestrictions; package org.owasp.webgoat.lessons.bypassrestrictions;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -33,7 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class BypassRestrictionsFrontendValidation implements AssignmentEndpoint { public class BypassRestrictionsFrontendValidation extends AssignmentEndpoint {
@PostMapping("/BypassRestrictions/frontendValidation") @PostMapping("/BypassRestrictions/frontendValidation")
@ResponseBody @ResponseBody

View File

@ -2,13 +2,11 @@ package org.owasp.webgoat.lessons.challenges;
import org.owasp.webgoat.container.lessons.Category; import org.owasp.webgoat.container.lessons.Category;
import org.owasp.webgoat.container.lessons.Lesson; import org.owasp.webgoat.container.lessons.Lesson;
import org.springframework.stereotype.Component;
/** /**
* @author nbaars * @author nbaars
* @since 3/21/17. * @since 3/21/17.
*/ */
@Component
public class ChallengeIntro extends Lesson { public class ChallengeIntro extends Lesson {
@Override @Override

View File

@ -22,30 +22,27 @@
package org.owasp.webgoat.lessons.challenges; package org.owasp.webgoat.lessons.challenges;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed; import lombok.AllArgsConstructor;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.PathVariable; import org.owasp.webgoat.container.session.WebSession;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class FlagController implements AssignmentEndpoint { @AllArgsConstructor
public class FlagController extends AssignmentEndpoint {
private final WebSession webSession;
private final Flags flags; private final Flags flags;
public FlagController(Flags flags) { @PostMapping(path = "/challenge/flag", produces = MediaType.APPLICATION_JSON_VALUE)
this.flags = flags;
}
@PostMapping(path = "/challenge/flag/{flagNumber}")
@ResponseBody @ResponseBody
public AttackResult postFlag(@PathVariable int flagNumber, @RequestParam String flag) { public AttackResult postFlag(@RequestParam String flag) {
var expectedFlag = flags.getFlag(flagNumber); Flag expectedFlag = flags.getFlag(webSession.getCurrentLesson());
if (expectedFlag.isCorrect(flag)) { if (expectedFlag.isCorrect(flag)) {
return success(this).feedback("challenge.flag.correct").build(); return success(this).feedback("challenge.flag.correct").build();
} else { } else {

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.owasp.webgoat.container.lessons.Lesson;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@ -14,6 +15,12 @@ public class Flags {
IntStream.range(1, 10).forEach(i -> FLAGS.put(i, new Flag(i, UUID.randomUUID().toString()))); IntStream.range(1, 10).forEach(i -> FLAGS.put(i, new Flag(i, UUID.randomUUID().toString())));
} }
public Flag getFlag(Lesson forLesson) {
String lessonName = forLesson.getName();
int challengeNumber = Integer.valueOf(lessonName.substring(lessonName.length() - 1));
return FLAGS.get(challengeNumber);
}
public Flag getFlag(int flagNumber) { public Flag getFlag(int flagNumber) {
return FLAGS.get(flagNumber); return FLAGS.get(flagNumber);
} }

View File

@ -1,9 +1,8 @@
package org.owasp.webgoat.lessons.challenges.challenge1; package org.owasp.webgoat.lessons.challenges.challenge1;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import static org.owasp.webgoat.lessons.challenges.SolutionConstants.PASSWORD; import static org.owasp.webgoat.lessons.challenges.SolutionConstants.PASSWORD;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.lessons.challenges.Flags; import org.owasp.webgoat.lessons.challenges.Flags;
@ -43,14 +42,11 @@ import org.springframework.web.bind.annotation.RestController;
* @since August 11, 2016 * @since August 11, 2016
*/ */
@RestController @RestController
public class Assignment1 implements AssignmentEndpoint { @RequiredArgsConstructor
public class Assignment1 extends AssignmentEndpoint {
private final Flags flags; private final Flags flags;
public Assignment1(Flags flags) {
this.flags = flags;
}
@PostMapping("/challenge/1") @PostMapping("/challenge/1")
@ResponseBody @ResponseBody
public AttackResult completed(@RequestParam String username, @RequestParam String password) { public AttackResult completed(@RequestParam String username, @RequestParam String password) {

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.challenges.challenge5; package org.owasp.webgoat.lessons.challenges.challenge5;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -42,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class Assignment5 implements AssignmentEndpoint { public class Assignment5 extends AssignmentEndpoint {
private final LessonDataSource dataSource; private final LessonDataSource dataSource;
private final Flags flags; private final Flags flags;

View File

@ -1,7 +1,5 @@
package org.owasp.webgoat.lessons.challenges.challenge7; package org.owasp.webgoat.lessons.challenges.challenge7;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -31,7 +29,7 @@ import org.springframework.web.client.RestTemplate;
*/ */
@RestController @RestController
@Slf4j @Slf4j
public class Assignment7 implements AssignmentEndpoint { public class Assignment7 extends AssignmentEndpoint {
public static final String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2"; public static final String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2";

View File

@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class Assignment8 implements AssignmentEndpoint { public class Assignment8 extends AssignmentEndpoint {
private static final Map<Integer, Integer> votes = new HashMap<>(); private static final Map<Integer, Integer> votes = new HashMap<>();

View File

@ -22,36 +22,28 @@
package org.owasp.webgoat.lessons.chromedevtools; package org.owasp.webgoat.lessons.chromedevtools;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.LessonSession; import org.owasp.webgoat.container.session.UserSessionData;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* This is just a class used to make the HTTP request. * This is just a class used to make the the HTTP request.
* *
* @author TMelzer * @author TMelzer
* @since 30.11.18 * @since 30.11.18
*/ */
@RestController @RestController
public class NetworkDummy implements AssignmentEndpoint { public class NetworkDummy extends AssignmentEndpoint {
private final LessonSession lessonSession;
public NetworkDummy(LessonSession lessonSession) {
this.lessonSession = lessonSession;
}
@PostMapping("/ChromeDevTools/dummy") @PostMapping("/ChromeDevTools/dummy")
@ResponseBody @ResponseBody
public AttackResult completed(@RequestParam String successMessage) { public AttackResult completed(@RequestParam String successMessage) {
String answer = (String) lessonSession.getValue("randValue"); UserSessionData userSessionData = getUserSessionData();
String answer = (String) userSessionData.getValue("randValue");
if (successMessage != null && successMessage.equals(answer)) { if (successMessage != null && successMessage.equals(answer)) {
return success(this).feedback("xss-dom-message-success").build(); return success(this).feedback("xss-dom-message-success").build();

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.chromedevtools; package org.owasp.webgoat.lessons.chromedevtools;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
@ -43,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController;
*/ */
@RestController @RestController
@AssignmentHints({"networkHint1", "networkHint2"}) @AssignmentHints({"networkHint1", "networkHint2"})
public class NetworkLesson implements AssignmentEndpoint { public class NetworkLesson extends AssignmentEndpoint {
@PostMapping( @PostMapping(
value = "/ChromeDevTools/network", value = "/ChromeDevTools/network",

View File

@ -1,8 +1,5 @@
package org.owasp.webgoat.lessons.cia; package org.owasp.webgoat.lessons.cia;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -12,9 +9,9 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class CIAQuiz implements AssignmentEndpoint { public class CIAQuiz extends AssignmentEndpoint {
private final String[] solutions = {"Solution 3", "Solution 1", "Solution 4", "Solution 2"}; String[] solutions = {"Solution 3", "Solution 1", "Solution 4", "Solution 2"};
boolean[] guesses = new boolean[solutions.length]; boolean[] guesses = new boolean[solutions.length];
@PostMapping("/cia/quiz") @PostMapping("/cia/quiz")

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.clientsidefiltering; package org.owasp.webgoat.lessons.clientsidefiltering;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
@ -40,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
"ClientSideFilteringHint3", "ClientSideFilteringHint3",
"ClientSideFilteringHint4" "ClientSideFilteringHint4"
}) })
public class ClientSideFilteringAssignment implements AssignmentEndpoint { public class ClientSideFilteringAssignment extends AssignmentEndpoint {
@PostMapping("/clientSideFiltering/attack1") @PostMapping("/clientSideFiltering/attack1")
@ResponseBody @ResponseBody

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.clientsidefiltering; package org.owasp.webgoat.lessons.clientsidefiltering;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
@ -43,7 +40,8 @@ import org.springframework.web.bind.annotation.RestController;
"client.side.filtering.free.hint2", "client.side.filtering.free.hint2",
"client.side.filtering.free.hint3" "client.side.filtering.free.hint3"
}) })
public class ClientSideFilteringFreeAssignment implements AssignmentEndpoint { public class ClientSideFilteringFreeAssignment extends AssignmentEndpoint {
public static final String SUPER_COUPON_CODE = "get_it_for_free"; public static final String SUPER_COUPON_CODE = "get_it_for_free";
@PostMapping("/clientSideFiltering/getItForFree") @PostMapping("/clientSideFiltering/getItForFree")

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.cryptography; package org.owasp.webgoat.lessons.cryptography;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Base64; import java.util.Base64;
import java.util.Random; import java.util.Random;
@ -38,7 +35,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class EncodingAssignment implements AssignmentEndpoint { public class EncodingAssignment extends AssignmentEndpoint {
public static String getBasicAuth(String username, String password) { public static String getBasicAuth(String username, String password) {
return Base64.getEncoder().encodeToString(username.concat(":").concat(password).getBytes()); return Base64.getEncoder().encodeToString(username.concat(":").concat(password).getBytes());

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.cryptography; package org.owasp.webgoat.lessons.cryptography;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -42,7 +39,8 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@AssignmentHints({"crypto-hashing.hints.1", "crypto-hashing.hints.2"}) @AssignmentHints({"crypto-hashing.hints.1", "crypto-hashing.hints.2"})
public class HashingAssignment implements AssignmentEndpoint { public class HashingAssignment extends AssignmentEndpoint {
public static final String[] SECRETS = {"secret", "admin", "password", "123456", "passw0rd"}; public static final String[] SECRETS = {"secret", "admin", "password", "123456", "passw0rd"};
@RequestMapping(path = "/crypto/hashing/md5", produces = MediaType.TEXT_HTML_VALUE) @RequestMapping(path = "/crypto/hashing/md5", produces = MediaType.TEXT_HTML_VALUE)

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.cryptography; package org.owasp.webgoat.lessons.cryptography;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
@ -40,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
"crypto-secure-defaults.hints.2", "crypto-secure-defaults.hints.2",
"crypto-secure-defaults.hints.3" "crypto-secure-defaults.hints.3"
}) })
public class SecureDefaultsAssignment implements AssignmentEndpoint { public class SecureDefaultsAssignment extends AssignmentEndpoint {
@PostMapping("/crypto/secure/defaults") @PostMapping("/crypto/secure/defaults")
@ResponseBody @ResponseBody

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.cryptography; package org.owasp.webgoat.lessons.cryptography;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair; import java.security.KeyPair;
@ -50,7 +47,7 @@ import org.springframework.web.bind.annotation.RestController;
"crypto-signing.hints.4" "crypto-signing.hints.4"
}) })
@Slf4j @Slf4j
public class SigningAssignment implements AssignmentEndpoint { public class SigningAssignment extends AssignmentEndpoint {
@RequestMapping(path = "/crypto/signing/getprivate", produces = MediaType.TEXT_HTML_VALUE) @RequestMapping(path = "/crypto/signing/getprivate", produces = MediaType.TEXT_HTML_VALUE)
@ResponseBody @ResponseBody

View File

@ -22,9 +22,6 @@
package org.owasp.webgoat.lessons.cryptography; package org.owasp.webgoat.lessons.cryptography;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
@ -35,7 +32,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@AssignmentHints({"crypto-encoding-xor.hints.1"}) @AssignmentHints({"crypto-encoding-xor.hints.1"})
public class XOREncodingAssignment implements AssignmentEndpoint { public class XOREncodingAssignment extends AssignmentEndpoint {
@PostMapping("/crypto/encoding/xor") @PostMapping("/crypto/encoding/xor")
@ResponseBody @ResponseBody

Some files were not shown because too many files have changed in this diff Show More