Compare commits

..

6 Commits

218 changed files with 2063 additions and 1653 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,46 +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
- 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.0
- 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 }}
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.4.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.16.1</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.0-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.1</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>30</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.1</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.8.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.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</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.5</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 : "");
} }
/** protected String webWolfFileUrl(String fileName) {
* In attack mode it means WebGoat calls WebWolf to perform an attack. In this case we need to return webWolfUrl("files") + "/" + getUser() + "/" + fileName;
* 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;
}
}
/**
* Debugging options: install TestContainers Desktop and map port 5005 to the host machine with
* 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

@ -32,33 +32,30 @@
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.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 {
private final UserRepository userRepository; @Autowired private UserRepository userRepository;
public WebGoat(UserRepository userRepository) {
this.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) {
@ -67,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,13 +25,27 @@
package org.owasp.webgoat.container.assignments; package org.owasp.webgoat.container.assignments;
import lombok.Getter;
import org.owasp.webgoat.container.i18n.PluginMessages; 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; import org.springframework.beans.factory.annotation.Autowired;
public abstract class AssignmentEndpoint { public abstract class AssignmentEndpoint implements Initializeable {
// TODO: move this to different bean. @Autowired private WebSession webSession;
@Autowired private PluginMessages messages; @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: * Convenience method for create a successful result:
@ -72,4 +86,7 @@ public abstract class AssignmentEndpoint {
protected AttackResult.AttackResultBuilder informationMessage(AssignmentEndpoint assignment) { protected AttackResult.AttackResultBuilder informationMessage(AssignmentEndpoint assignment) {
return AttackResult.builder(messages).lessonCompleted(false).assignment(assignment); return AttackResult.builder(messages).lessonCompleted(false).assignment(assignment);
} }
@Override
public void initialize(WebGoatUser user) {}
} }

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,9 +22,12 @@
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;
@ -32,91 +35,45 @@ import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.Course; import org.owasp.webgoat.container.session.Course;
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 Map<String, List<AssignmentEndpoint>> assignmentsByPackage;
public CourseConfiguration(List<Lesson> lessons, List<AssignmentEndpoint> assignments) { public CourseConfiguration(List<Lesson> lessons, List<AssignmentEndpoint> assignments) {
this.lessons = lessons; this.lessons = lessons;
this.assignments = assignments; this.assignments = assignments;
} 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) {

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

@ -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

@ -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

@ -32,16 +32,16 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; 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.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;
@ -59,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;
@ -73,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())) {
@ -99,12 +97,12 @@ 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 = lessonCompleted(lessonTracker.getLessonOverview(), lesson); 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;

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,20 +29,20 @@ 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
@Getter @Getter

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

@ -32,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;
@ -48,11 +50,9 @@ import org.springframework.web.bind.annotation.RestController;
}) })
public class VerifyAccount extends 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

@ -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

@ -25,7 +25,8 @@ package org.owasp.webgoat.lessons.challenges;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
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;
@ -35,12 +36,13 @@ import org.springframework.web.bind.annotation.RestController;
@AllArgsConstructor @AllArgsConstructor
public class FlagController extends AssignmentEndpoint { public class FlagController extends AssignmentEndpoint {
private final WebSession webSession;
private final Flags flags; private final Flags flags;
@PostMapping(path = "/challenge/flag/{flagNumber}") @PostMapping(path = "/challenge/flag", produces = MediaType.APPLICATION_JSON_VALUE)
@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

@ -24,14 +24,14 @@ package org.owasp.webgoat.lessons.chromedevtools;
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
@ -39,16 +39,11 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class NetworkDummy extends 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

@ -31,7 +31,7 @@ import org.springframework.stereotype.Component;
public class CSRF extends Lesson { public class CSRF extends Lesson {
@Override @Override
public Category getDefaultCategory() { public Category getDefaultCategory() {
return Category.A5; return Category.A10;
} }
@Override @Override

View File

@ -25,7 +25,7 @@ package org.owasp.webgoat.lessons.csrf;
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.springframework.beans.factory.annotation.Autowired; 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.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
@AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"}) @AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"})
public class CSRFConfirmFlag1 extends AssignmentEndpoint { public class CSRFConfirmFlag1 extends AssignmentEndpoint {
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@PostMapping( @PostMapping(
path = "/csrf/confirm-flag-1", path = "/csrf/confirm-flag-1",

View File

@ -33,7 +33,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -42,11 +42,15 @@ 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;
/**
* @author nbaars
* @since 11/17/17.
*/
@RestController @RestController
@AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"}) @AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"})
public class CSRFFeedback extends AssignmentEndpoint { public class CSRFFeedback extends AssignmentEndpoint {
@Autowired private LessonSession userSessionData; @Autowired private UserSessionData userSessionData;
@Autowired private ObjectMapper objectMapper; @Autowired private ObjectMapper objectMapper;
@PostMapping( @PostMapping(

View File

@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import org.owasp.webgoat.container.i18n.PluginMessages; import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.session.LessonSession; import org.owasp.webgoat.container.session.UserSessionData;
import org.springframework.beans.factory.annotation.Autowired; 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.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class CSRFGetFlag { public class CSRFGetFlag {
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@Autowired private PluginMessages pluginMessages; @Autowired private PluginMessages pluginMessages;
@PostMapping( @PostMapping(

View File

@ -22,26 +22,47 @@
package org.owasp.webgoat.lessons.csrf; package org.owasp.webgoat.lessons.csrf;
import org.owasp.webgoat.container.CurrentUsername; import jakarta.servlet.http.HttpServletRequest;
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.users.UserTracker;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
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;
/**
* @author nbaars
* @since 11/17/17.
*/
@RestController @RestController
@AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"}) @AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"})
public class CSRFLogin extends AssignmentEndpoint { public class CSRFLogin extends AssignmentEndpoint {
private final UserTrackerRepository userTrackerRepository;
public CSRFLogin(UserTrackerRepository userTrackerRepository) {
this.userTrackerRepository = userTrackerRepository;
}
@PostMapping( @PostMapping(
path = "/csrf/login", path = "/csrf/login",
produces = {"application/json"}) produces = {"application/json"})
@ResponseBody @ResponseBody
public AttackResult completed(@CurrentUsername String username) { public AttackResult completed(HttpServletRequest request) {
if (username.startsWith("csrf")) { String userName = request.getUserPrincipal().getName();
if (userName.startsWith("csrf")) {
markAssignmentSolvedWithRealUser(userName.substring("csrf-".length()));
return success(this).feedback("csrf-login-success").build(); return success(this).feedback("csrf-login-success").build();
} }
return failed(this).feedback("csrf-login-failed").feedbackArgs(username).build(); return failed(this).feedback("csrf-login-failed").feedbackArgs(userName).build();
}
private void markAssignmentSolvedWithRealUser(String username) {
UserTracker userTracker = userTrackerRepository.findByUser(username);
userTracker.assignmentSolved(
getWebSession().getCurrentLesson(), this.getClass().getSimpleName());
userTrackerRepository.save(userTracker);
} }
} }

View File

@ -33,10 +33,11 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
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;
@ -47,6 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
@AssignmentHints({"csrf-review-hint1", "csrf-review-hint2", "csrf-review-hint3"}) @AssignmentHints({"csrf-review-hint1", "csrf-review-hint2", "csrf-review-hint3"})
public class ForgedReviews extends AssignmentEndpoint { public class ForgedReviews extends AssignmentEndpoint {
@Autowired private WebSession webSession;
private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss"); private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss");
private static final Map<String, List<Review>> userReviews = new HashMap<>(); private static final Map<String, List<Review>> userReviews = new HashMap<>();
@ -71,9 +73,9 @@ public class ForgedReviews extends AssignmentEndpoint {
produces = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE,
consumes = ALL_VALUE) consumes = ALL_VALUE)
@ResponseBody @ResponseBody
public Collection<Review> retrieveReviews(@CurrentUsername String username) { public Collection<Review> retrieveReviews() {
Collection<Review> allReviews = Lists.newArrayList(); Collection<Review> allReviews = Lists.newArrayList();
Collection<Review> newReviews = userReviews.get(username); Collection<Review> newReviews = userReviews.get(webSession.getUserName());
if (newReviews != null) { if (newReviews != null) {
allReviews.addAll(newReviews); allReviews.addAll(newReviews);
} }
@ -86,11 +88,7 @@ public class ForgedReviews extends AssignmentEndpoint {
@PostMapping("/csrf/review") @PostMapping("/csrf/review")
@ResponseBody @ResponseBody
public AttackResult createNewReview( public AttackResult createNewReview(
String reviewText, String reviewText, Integer stars, String validateReq, HttpServletRequest request) {
Integer stars,
String validateReq,
HttpServletRequest request,
@CurrentUsername String username) {
final String host = (request.getHeader("host") == null) ? "NULL" : request.getHeader("host"); final String host = (request.getHeader("host") == null) ? "NULL" : request.getHeader("host");
final String referer = final String referer =
(request.getHeader("referer") == null) ? "NULL" : request.getHeader("referer"); (request.getHeader("referer") == null) ? "NULL" : request.getHeader("referer");
@ -99,11 +97,11 @@ public class ForgedReviews extends AssignmentEndpoint {
Review review = new Review(); Review review = new Review();
review.setText(reviewText); review.setText(reviewText);
review.setDateTime(LocalDateTime.now().format(fmt)); review.setDateTime(LocalDateTime.now().format(fmt));
review.setUser(username); review.setUser(webSession.getUserName());
review.setStars(stars); review.setStars(stars);
var reviews = userReviews.getOrDefault(username, new ArrayList<>()); var reviews = userReviews.getOrDefault(webSession.getUserName(), new ArrayList<>());
reviews.add(review); reviews.add(review);
userReviews.put(username, reviews); userReviews.put(webSession.getUserName(), reviews);
// short-circuit // short-circuit
if (validateReq == null || !validateReq.equals(weakAntiCSRF)) { if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {
return failed(this).feedback("csrf-you-forgot-something").build(); return failed(this).feedback("csrf-you-forgot-something").build();

View File

@ -26,7 +26,7 @@ package org.owasp.webgoat.lessons.idor;
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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@ -48,7 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
}) })
public class IDOREditOtherProfile extends AssignmentEndpoint { public class IDOREditOtherProfile extends AssignmentEndpoint {
@Autowired private LessonSession userSessionData; @Autowired private UserSessionData userSessionData;
@PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json") @PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json")
@ResponseBody @ResponseBody

View File

@ -28,7 +28,7 @@ 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.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;
@ -38,12 +38,6 @@ import org.springframework.web.bind.annotation.RestController;
@AssignmentHints({"idor.hints.idor_login"}) @AssignmentHints({"idor.hints.idor_login"})
public class IDORLogin extends AssignmentEndpoint { public class IDORLogin extends AssignmentEndpoint {
private final LessonSession lessonSession;
public IDORLogin(LessonSession lessonSession) {
this.lessonSession = lessonSession;
}
private Map<String, Map<String, String>> idorUserInfo = new HashMap<>(); private Map<String, Map<String, String>> idorUserInfo = new HashMap<>();
public void initIDORInfo() { public void initIDORInfo() {
@ -65,11 +59,13 @@ public class IDORLogin extends AssignmentEndpoint {
@ResponseBody @ResponseBody
public AttackResult completed(@RequestParam String username, @RequestParam String password) { public AttackResult completed(@RequestParam String username, @RequestParam String password) {
initIDORInfo(); initIDORInfo();
UserSessionData userSessionData = getUserSessionData();
if (idorUserInfo.containsKey(username)) { if (idorUserInfo.containsKey(username)) {
if ("tom".equals(username) && idorUserInfo.get("tom").get("password").equals(password)) { if ("tom".equals(username) && idorUserInfo.get("tom").get("password").equals(password)) {
lessonSession.setValue("idor-authenticated-as", username); userSessionData.setValue("idor-authenticated-as", username);
lessonSession.setValue("idor-authenticated-user-id", idorUserInfo.get(username).get("id")); userSessionData.setValue(
"idor-authenticated-user-id", idorUserInfo.get(username).get("id"));
return success(this).feedback("idor.login.success").feedbackArgs(username).build(); return success(this).feedback("idor.login.success").feedbackArgs(username).build();
} else { } else {
return failed(this).feedback("idor.login.failure").build(); return failed(this).feedback("idor.login.failure").build();

View File

@ -27,7 +27,7 @@ import jakarta.servlet.http.HttpServletResponse;
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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -48,7 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
}) })
public class IDORViewOtherProfile extends AssignmentEndpoint { public class IDORViewOtherProfile extends AssignmentEndpoint {
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@GetMapping( @GetMapping(
path = "/IDOR/profile/{userId}", path = "/IDOR/profile/{userId}",

View File

@ -26,7 +26,7 @@ package org.owasp.webgoat.lessons.idor;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.session.LessonSession; import org.owasp.webgoat.container.session.UserSessionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
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;
@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
public class IDORViewOwnProfile { public class IDORViewOwnProfile {
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@GetMapping( @GetMapping(
path = {"/IDOR/own", "/IDOR/profile"}, path = {"/IDOR/own", "/IDOR/profile"},

View File

@ -26,7 +26,7 @@ package org.owasp.webgoat.lessons.idor;
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.springframework.beans.factory.annotation.Autowired; 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;
@ -41,7 +41,7 @@ import org.springframework.web.bind.annotation.RestController;
}) })
public class IDORViewOwnProfileAltUrl extends AssignmentEndpoint { public class IDORViewOwnProfileAltUrl extends AssignmentEndpoint {
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@PostMapping("/IDOR/profile/alt-path") @PostMapping("/IDOR/profile/alt-path")
@ResponseBody @ResponseBody

View File

@ -1,6 +1,7 @@
package org.owasp.webgoat.lessons.jwt.claimmisuse; package org.owasp.webgoat.lessons.jwt.claimmisuse;
import com.auth0.jwk.JwkException; import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder; import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.algorithms.Algorithm;
@ -47,12 +48,12 @@ public class JWTHeaderJKUEndpoint extends AssignmentEndpoint {
try { try {
var decodedJWT = JWT.decode(token); var decodedJWT = JWT.decode(token);
var jku = decodedJWT.getHeaderClaim("jku"); var jku = decodedJWT.getHeaderClaim("jku");
var jwkProvider = new JwkProviderBuilder(new URL(jku.asString())).build(); JwkProvider jwkProvider = new JwkProviderBuilder(new URL(jku.asString())).build();
var jwk = jwkProvider.get(decodedJWT.getKeyId()); var jwk = jwkProvider.get(decodedJWT.getKeyId());
var algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey()); var algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey());
JWT.require(algorithm).build().verify(decodedJWT); JWT.require(algorithm).build().verify(decodedJWT);
var username = decodedJWT.getClaims().get("username").asString(); String username = decodedJWT.getClaims().get("username").asString();
if ("Jerry".equals(username)) { if ("Jerry".equals(username)) {
return failed(this).feedback("jwt-final-jerry-account").build(); return failed(this).feedback("jwt-final-jerry-account").build();
} }

View File

@ -27,7 +27,7 @@ import lombok.AllArgsConstructor;
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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -44,7 +44,7 @@ public class SampleAttack extends AssignmentEndpoint {
String secretValue = "secr37Value"; String secretValue = "secr37Value";
// UserSessionData is bound to session and can be used to persist data across multiple assignments // UserSessionData is bound to session and can be used to persist data across multiple assignments
@Autowired LessonSession userSessionData; @Autowired UserSessionData userSessionData;
@PostMapping("/lesson-template/sample-attack") @PostMapping("/lesson-template/sample-attack")
@ResponseBody @ResponseBody

View File

@ -30,7 +30,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.CurrentUsername; import org.owasp.webgoat.container.session.WebSession;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -47,6 +47,7 @@ import org.springframework.web.servlet.ModelAndView;
public class MissingFunctionACUsers { public class MissingFunctionACUsers {
private final MissingAccessControlUserRepository userRepository; private final MissingAccessControlUserRepository userRepository;
private final WebSession webSession;
@GetMapping(path = {"access-control/users"}) @GetMapping(path = {"access-control/users"})
public ModelAndView listUsers() { public ModelAndView listUsers() {
@ -80,8 +81,8 @@ public class MissingFunctionACUsers {
path = {"access-control/users-admin-fix"}, path = {"access-control/users-admin-fix"},
consumes = "application/json") consumes = "application/json")
@ResponseBody @ResponseBody
public ResponseEntity<List<DisplayUser>> usersFixed(@CurrentUsername String username) { public ResponseEntity<List<DisplayUser>> usersFixed() {
var currentUser = userRepository.findByUsername(username); var currentUser = userRepository.findByUsername(webSession.getUserName());
if (currentUser != null && currentUser.isAdmin()) { if (currentUser != null && currentUser.isAdmin()) {
return ResponseEntity.ok( return ResponseEntity.ok(
userRepository.findAllUsers().stream() userRepository.findAllUsers().stream()

View File

@ -27,7 +27,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.owasp.webgoat.container.CurrentUsername;
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;
@ -82,10 +81,10 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
@PostMapping("/PasswordReset/reset/login") @PostMapping("/PasswordReset/reset/login")
@ResponseBody @ResponseBody
public AttackResult login( public AttackResult login(@RequestParam String password, @RequestParam String email) {
@RequestParam String password, @RequestParam String email, @CurrentUsername String username) {
if (TOM_EMAIL.equals(email)) { if (TOM_EMAIL.equals(email)) {
String passwordTom = usersToTomPassword.getOrDefault(username, PASSWORD_TOM_9); String passwordTom =
usersToTomPassword.getOrDefault(getWebSession().getUserName(), PASSWORD_TOM_9);
if (passwordTom.equals(PASSWORD_TOM_9)) { if (passwordTom.equals(PASSWORD_TOM_9)) {
return failed(this).feedback("login_failed").build(); return failed(this).feedback("login_failed").build();
} else if (passwordTom.equals(password)) { } else if (passwordTom.equals(password)) {
@ -113,9 +112,7 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
@PostMapping("/PasswordReset/reset/change-password") @PostMapping("/PasswordReset/reset/change-password")
public ModelAndView changePassword( public ModelAndView changePassword(
@ModelAttribute("form") PasswordChangeForm form, @ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) {
BindingResult bindingResult,
@CurrentUsername String username) {
ModelAndView modelAndView = new ModelAndView(); ModelAndView modelAndView = new ModelAndView();
if (!org.springframework.util.StringUtils.hasText(form.getPassword())) { if (!org.springframework.util.StringUtils.hasText(form.getPassword())) {
bindingResult.rejectValue("password", "not.empty"); bindingResult.rejectValue("password", "not.empty");
@ -128,15 +125,15 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found")); modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found"));
return modelAndView; return modelAndView;
} }
if (checkIfLinkIsFromTom(form.getResetLink(), username)) { if (checkIfLinkIsFromTom(form.getResetLink())) {
usersToTomPassword.put(username, form.getPassword()); usersToTomPassword.put(getWebSession().getUserName(), form.getPassword());
} }
modelAndView.setViewName(VIEW_FORMATTER.formatted("success")); modelAndView.setViewName(VIEW_FORMATTER.formatted("success"));
return modelAndView; return modelAndView;
} }
private boolean checkIfLinkIsFromTom(String resetLinkFromForm, String username) { private boolean checkIfLinkIsFromTom(String resetLinkFromForm) {
String resetLink = userToTomResetLink.getOrDefault(username, "unknown"); String resetLink = userToTomResetLink.getOrDefault(getWebSession().getUserName(), "unknown");
return resetLink.equals(resetLinkFromForm); return resetLink.equals(resetLinkFromForm);
} }
} }

View File

@ -24,7 +24,6 @@ package org.owasp.webgoat.lessons.passwordreset;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.UUID; import java.util.UUID;
import org.owasp.webgoat.container.CurrentUsername;
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.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -68,14 +67,14 @@ public class ResetLinkAssignmentForgotPassword extends AssignmentEndpoint {
@PostMapping("/PasswordReset/ForgotPassword/create-password-reset-link") @PostMapping("/PasswordReset/ForgotPassword/create-password-reset-link")
@ResponseBody @ResponseBody
public AttackResult sendPasswordResetLink( public AttackResult sendPasswordResetLink(
@RequestParam String email, HttpServletRequest request, @CurrentUsername String username) { @RequestParam String email, HttpServletRequest request) {
String resetLink = UUID.randomUUID().toString(); String resetLink = UUID.randomUUID().toString();
ResetLinkAssignment.resetLinks.add(resetLink); ResetLinkAssignment.resetLinks.add(resetLink);
String host = request.getHeader(HttpHeaders.HOST); String host = request.getHeader(HttpHeaders.HOST);
if (ResetLinkAssignment.TOM_EMAIL.equals(email) if (ResetLinkAssignment.TOM_EMAIL.equals(email)
&& (host.contains(webWolfPort) && (host.contains(webWolfPort)
&& host.contains(webWolfHost))) { // User indeed changed the host header. && host.contains(webWolfHost))) { // User indeed changed the host header.
ResetLinkAssignment.userToTomResetLink.put(username, resetLink); ResetLinkAssignment.userToTomResetLink.put(getWebSession().getUserName(), resetLink);
fakeClickingLinkEmail(webWolfURL, resetLink); fakeClickingLinkEmail(webWolfURL, resetLink);
} else { } else {
try { try {

View File

@ -26,7 +26,6 @@ import static java.util.Optional.ofNullable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.owasp.webgoat.container.CurrentUsername;
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.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -58,14 +57,12 @@ public class SimpleMailAssignment extends AssignmentEndpoint {
path = "/PasswordReset/simple-mail", path = "/PasswordReset/simple-mail",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseBody @ResponseBody
public AttackResult login( public AttackResult login(@RequestParam String email, @RequestParam String password) {
@RequestParam String email,
@RequestParam String password,
@CurrentUsername String webGoatUsername) {
String emailAddress = ofNullable(email).orElse("unknown@webgoat.org"); String emailAddress = ofNullable(email).orElse("unknown@webgoat.org");
String username = extractUsername(emailAddress); String username = extractUsername(emailAddress);
if (username.equals(webGoatUsername) && StringUtils.reverse(username).equals(password)) { if (username.equals(getWebSession().getUserName())
&& StringUtils.reverse(username).equals(password)) {
return success(this).build(); return success(this).build();
} else { } else {
return failed(this).feedbackArgs("password-reset-simple.password_incorrect").build(); return failed(this).feedbackArgs("password-reset-simple.password_incorrect").build();
@ -76,10 +73,9 @@ public class SimpleMailAssignment extends AssignmentEndpoint {
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
value = "/PasswordReset/simple-mail/reset") value = "/PasswordReset/simple-mail/reset")
@ResponseBody @ResponseBody
public AttackResult resetPassword( public AttackResult resetPassword(@RequestParam String emailReset) {
@RequestParam String emailReset, @CurrentUsername String username) {
String email = ofNullable(emailReset).orElse("unknown@webgoat.org"); String email = ofNullable(emailReset).orElse("unknown@webgoat.org");
return sendEmail(extractUsername(email), email, username); return sendEmail(extractUsername(email), email);
} }
private String extractUsername(String email) { private String extractUsername(String email) {
@ -87,8 +83,8 @@ public class SimpleMailAssignment extends AssignmentEndpoint {
return email.substring(0, index == -1 ? email.length() : index); return email.substring(0, index == -1 ? email.length() : index);
} }
private AttackResult sendEmail(String username, String email, String webGoatUsername) { private AttackResult sendEmail(String username, String email) {
if (username.equals(webGoatUsername)) { if (username.equals(getWebSession().getUserName())) {
PasswordResetEmail mailEvent = PasswordResetEmail mailEvent =
PasswordResetEmail.builder() PasswordResetEmail.builder()
.recipient(username) .recipient(username)

View File

@ -3,9 +3,9 @@ package org.owasp.webgoat.lessons.pathtraversal;
import static org.springframework.http.MediaType.ALL_VALUE; import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -23,8 +23,9 @@ import org.springframework.web.multipart.MultipartFile;
}) })
public class ProfileUpload extends ProfileUploadBase { public class ProfileUpload extends ProfileUploadBase {
public ProfileUpload(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { public ProfileUpload(
super(webGoatHomeDirectory); @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
} }
@PostMapping( @PostMapping(
@ -34,14 +35,13 @@ public class ProfileUpload extends ProfileUploadBase {
@ResponseBody @ResponseBody
public AttackResult uploadFileHandler( public AttackResult uploadFileHandler(
@RequestParam("uploadedFile") MultipartFile file, @RequestParam("uploadedFile") MultipartFile file,
@RequestParam(value = "fullName", required = false) String fullName, @RequestParam(value = "fullName", required = false) String fullName) {
@CurrentUsername String username) { return super.execute(file, fullName);
return super.execute(file, fullName, username);
} }
@GetMapping("/PathTraversal/profile-picture") @GetMapping("/PathTraversal/profile-picture")
@ResponseBody @ResponseBody
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) { public ResponseEntity<?> getProfilePicture() {
return super.getProfilePicture(username); return super.getProfilePicture();
} }
} }

View File

@ -11,9 +11,9 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@ -26,8 +26,9 @@ import org.springframework.web.multipart.MultipartFile;
public class ProfileUploadBase extends AssignmentEndpoint { public class ProfileUploadBase extends AssignmentEndpoint {
private String webGoatHomeDirectory; private String webGoatHomeDirectory;
private WebSession webSession;
protected AttackResult execute(MultipartFile file, String fullName, String username) { protected AttackResult execute(MultipartFile file, String fullName) {
if (file.isEmpty()) { if (file.isEmpty()) {
return failed(this).feedback("path-traversal-profile-empty-file").build(); return failed(this).feedback("path-traversal-profile-empty-file").build();
} }
@ -35,7 +36,7 @@ public class ProfileUploadBase extends AssignmentEndpoint {
return failed(this).feedback("path-traversal-profile-empty-name").build(); return failed(this).feedback("path-traversal-profile-empty-name").build();
} }
File uploadDirectory = cleanupAndCreateDirectoryForUser(username); File uploadDirectory = cleanupAndCreateDirectoryForUser();
try { try {
var uploadedFile = new File(uploadDirectory, fullName); var uploadedFile = new File(uploadDirectory, fullName);
@ -56,8 +57,9 @@ public class ProfileUploadBase extends AssignmentEndpoint {
} }
@SneakyThrows @SneakyThrows
protected File cleanupAndCreateDirectoryForUser(String username) { protected File cleanupAndCreateDirectoryForUser() {
var uploadDirectory = new File(this.webGoatHomeDirectory, "/PathTraversal/" + username); var uploadDirectory =
new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName());
if (uploadDirectory.exists()) { if (uploadDirectory.exists()) {
FileSystemUtils.deleteRecursively(uploadDirectory); FileSystemUtils.deleteRecursively(uploadDirectory);
} }
@ -83,14 +85,15 @@ public class ProfileUploadBase extends AssignmentEndpoint {
.build(); .build();
} }
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) { public ResponseEntity<?> getProfilePicture() {
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))
.body(getProfilePictureAsBase64(username)); .body(getProfilePictureAsBase64());
} }
protected byte[] getProfilePictureAsBase64(String username) { protected byte[] getProfilePictureAsBase64() {
var profilePictureDirectory = new File(this.webGoatHomeDirectory, "/PathTraversal/" + username); var profilePictureDirectory =
new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName());
var profileDirectoryFiles = profilePictureDirectory.listFiles(); var profileDirectoryFiles = profilePictureDirectory.listFiles();
if (profileDirectoryFiles != null && profileDirectoryFiles.length > 0) { if (profileDirectoryFiles != null && profileDirectoryFiles.length > 0) {

View File

@ -3,9 +3,9 @@ package org.owasp.webgoat.lessons.pathtraversal;
import static org.springframework.http.MediaType.ALL_VALUE; import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -23,8 +23,9 @@ import org.springframework.web.multipart.MultipartFile;
}) })
public class ProfileUploadFix extends ProfileUploadBase { public class ProfileUploadFix extends ProfileUploadBase {
public ProfileUploadFix(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { public ProfileUploadFix(
super(webGoatHomeDirectory); @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
} }
@PostMapping( @PostMapping(
@ -34,14 +35,13 @@ public class ProfileUploadFix extends ProfileUploadBase {
@ResponseBody @ResponseBody
public AttackResult uploadFileHandler( public AttackResult uploadFileHandler(
@RequestParam("uploadedFileFix") MultipartFile file, @RequestParam("uploadedFileFix") MultipartFile file,
@RequestParam(value = "fullNameFix", required = false) String fullName, @RequestParam(value = "fullNameFix", required = false) String fullName) {
@CurrentUsername String username) { return super.execute(file, fullName != null ? fullName.replace("../", "") : "");
return super.execute(file, fullName != null ? fullName.replace("../", "") : "", username);
} }
@GetMapping("/PathTraversal/profile-picture-fix") @GetMapping("/PathTraversal/profile-picture-fix")
@ResponseBody @ResponseBody
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) { public ResponseEntity<?> getProfilePicture() {
return super.getProfilePicture(username); return super.getProfilePicture();
} }
} }

View File

@ -3,9 +3,9 @@ package org.owasp.webgoat.lessons.pathtraversal;
import static org.springframework.http.MediaType.ALL_VALUE; import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
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;
@ -22,8 +22,8 @@ import org.springframework.web.multipart.MultipartFile;
public class ProfileUploadRemoveUserInput extends ProfileUploadBase { public class ProfileUploadRemoveUserInput extends ProfileUploadBase {
public ProfileUploadRemoveUserInput( public ProfileUploadRemoveUserInput(
@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory); super(webGoatHomeDirectory, webSession);
} }
@PostMapping( @PostMapping(
@ -32,8 +32,7 @@ public class ProfileUploadRemoveUserInput extends ProfileUploadBase {
produces = APPLICATION_JSON_VALUE) produces = APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public AttackResult uploadFileHandler( public AttackResult uploadFileHandler(
@RequestParam("uploadedFileRemoveUserInput") MultipartFile file, @RequestParam("uploadedFileRemoveUserInput") MultipartFile file) {
@CurrentUsername String username) { return super.execute(file, file.getOriginalFilename());
return super.execute(file, file.getOriginalFilename(), username);
} }
} }

View File

@ -12,7 +12,6 @@ import java.nio.file.Files;
import java.util.Base64; import java.util.Base64;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.RandomUtils;
import org.owasp.webgoat.container.CurrentUsername;
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;
@ -72,10 +71,8 @@ public class ProfileUploadRetrieval extends AssignmentEndpoint {
@PostMapping("/PathTraversal/random") @PostMapping("/PathTraversal/random")
@ResponseBody @ResponseBody
public AttackResult execute( public AttackResult execute(@RequestParam(value = "secret", required = false) String secret) {
@RequestParam(value = "secret", required = false) String secret, if (Sha512DigestUtils.shaHex(getWebSession().getUserName()).equalsIgnoreCase(secret)) {
@CurrentUsername String username) {
if (Sha512DigestUtils.shaHex(username).equalsIgnoreCase(secret)) {
return success(this).build(); return success(this).build();
} }
return failed(this).build(); return failed(this).build();

View File

@ -14,9 +14,9 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.CurrentUsername;
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.WebSession;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@ -38,8 +38,9 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j @Slf4j
public class ProfileZipSlip extends ProfileUploadBase { public class ProfileZipSlip extends ProfileUploadBase {
public ProfileZipSlip(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { public ProfileZipSlip(
super(webGoatHomeDirectory); @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
} }
@PostMapping( @PostMapping(
@ -47,20 +48,19 @@ public class ProfileZipSlip extends ProfileUploadBase {
consumes = ALL_VALUE, consumes = ALL_VALUE,
produces = APPLICATION_JSON_VALUE) produces = APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public AttackResult uploadFileHandler( public AttackResult uploadFileHandler(@RequestParam("uploadedFileZipSlip") MultipartFile file) {
@RequestParam("uploadedFileZipSlip") MultipartFile file, @CurrentUsername String username) {
if (!file.getOriginalFilename().toLowerCase().endsWith(".zip")) { if (!file.getOriginalFilename().toLowerCase().endsWith(".zip")) {
return failed(this).feedback("path-traversal-zip-slip.no-zip").build(); return failed(this).feedback("path-traversal-zip-slip.no-zip").build();
} else { } else {
return processZipUpload(file, username); return processZipUpload(file);
} }
} }
@SneakyThrows @SneakyThrows
private AttackResult processZipUpload(MultipartFile file, String username) { private AttackResult processZipUpload(MultipartFile file) {
var tmpZipDirectory = Files.createTempDirectory(username); var tmpZipDirectory = Files.createTempDirectory(getWebSession().getUserName());
cleanupAndCreateDirectoryForUser(username); cleanupAndCreateDirectoryForUser();
var currentImage = getProfilePictureAsBase64(username); var currentImage = getProfilePictureAsBase64();
try { try {
var uploadedZipFile = tmpZipDirectory.resolve(file.getOriginalFilename()); var uploadedZipFile = tmpZipDirectory.resolve(file.getOriginalFilename());
@ -75,7 +75,7 @@ public class ProfileZipSlip extends ProfileUploadBase {
Files.copy(is, f.toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(is, f.toPath(), StandardCopyOption.REPLACE_EXISTING);
} }
return isSolved(currentImage, getProfilePictureAsBase64(username)); return isSolved(currentImage, getProfilePictureAsBase64());
} catch (IOException e) { } catch (IOException e) {
return failed(this).output(e.getMessage()).build(); return failed(this).output(e.getMessage()).build();
} }
@ -90,13 +90,13 @@ public class ProfileZipSlip extends ProfileUploadBase {
@GetMapping("/PathTraversal/zip-slip/") @GetMapping("/PathTraversal/zip-slip/")
@ResponseBody @ResponseBody
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) { public ResponseEntity<?> getProfilePicture() {
return super.getProfilePicture(username); return super.getProfilePicture();
} }
@GetMapping("/PathTraversal/zip-slip/profile-image/{username}") @GetMapping("/PathTraversal/zip-slip/profile-image/{username}")
@ResponseBody @ResponseBody
public ResponseEntity<?> getProfileImage(@PathVariable String username) { public ResponseEntity<?> getProfilePicture(@PathVariable("username") String username) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
} }

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