Compare commits

..

6 Commits

255 changed files with 2191 additions and 1789 deletions

View File

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

View File

@ -11,41 +11,24 @@ on:
- main
jobs:
pre-commit:
name: Pre-commit check
runs-on: ubuntu-latest
steps:
- name: Checkout git repository
uses: actions/checkout@v4.1.6
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.9"
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Pre-commit checks
uses: pre-commit/action@v3.0.1
- name: pre-commit-ci-lite
uses: pre-commit-ci/lite-action@v1.1.0
if: always()
build:
runs-on: ${{ matrix.os }}
needs: [ pre-commit ]
strategy:
fail-fast: true
matrix:
os: [ windows-latest, ubuntu-latest, macos-13 ]
max-parallel: 1
os: [ ubuntu-latest, windows-latest, macos-latest ]
steps:
- uses: actions/checkout@v4.1.6
- name: Set up JDK 21
uses: actions/setup-java@v4.2.1
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
java-version: 17
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
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:
name: release
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Set up JDK 21
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
java-version: 17
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 }}"
run: |
@ -68,7 +74,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Set up QEMU"
uses: docker/setup-qemu-action@v3.1.0
uses: docker/setup-qemu-action@v2.2.0
with:
platforms: all
@ -76,13 +82,13 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: "Login to dockerhub"
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: "Build and push WebGoat"
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: ./
file: ./Dockerfile
@ -95,7 +101,7 @@ jobs:
webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
- name: "Build and push WebGoat desktop"
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: ./
file: ./Dockerfile_desktop
@ -112,15 +118,15 @@ jobs:
needs: [ release ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK 21
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
java-version: 17
architecture: x64
- name: Set version to next snapshot

View File

@ -21,21 +21,27 @@ jobs:
name: "Robot framework test"
steps:
# 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
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.7'
architecture: x64
# Uses an action to add JDK 21 to the VM (and mvn?)
- name: set up JDK 21
uses: actions/setup-java@v4.2.1
# Uses an action to add JDK 17 to the VM (and mvn?)
- name: set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
java-version: 17
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
with:
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-jdk-jammy
LABEL name="WebGoat: A deliberately insecure Web Application"
LABEL maintainer="WebGoat team"
FROM docker.io/eclipse-temurin:21.0.1_12-jre
LABEL NAME = "WebGoat: A deliberately insecure Web Application"
MAINTAINER "WebGoat team"
RUN \
useradd -ms /bin/bash webgoat && \
@ -35,6 +33,3 @@ ENTRYPOINT [ "java", \
"--add-opens", "java.base/java.io=ALL-UNNAMED", \
"-Drunning.in.docker=true", \
"-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
LABEL NAME = "WebGoat: A deliberately insecure Web Application"
LABEL maintainer = "WebGoat team"
MAINTAINER "WebGoat team"
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/WebGoat.txt /config/Desktop/
RUN \
apt-get update && \
apt-get --yes install vim nano gzip
RUN \
case $(uname -m) in \
x86_64) ARCH=x64;; \
aarch64) ARCH=aarch64;; \
*) ARCH=unknown;; \
esac && \
echo ${ARCH}
RUN \
curl -LO https://github.com/zaproxy/zaproxy/releases/download/v2.15.0/ZAP_2.15.0_Linux.tar.gz && \
tar zfxv ZAP_2.15.0_Linux.tar.gz && \
rm -rf ZAP_2.15.0_Linux.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 && \
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 && \
rm -rf ZAP_2.12.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 OpenJDK17U-jre_${ARCH}_linux_hotspot_17.0.6_10.tar.gz && \
rm -rf OpenJDK17U-jre_${ARCH}_linux_hotspot_17.0.6_10.tar.gz && \
chmod +x /config/start_webgoat.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
ENV JAVA_HOME=/config/jdk-21.0.3+9-jre
ENV JAVA_HOME=/home/webgoat/jdk-17.0.6+10-jre
WORKDIR /config/Desktop

View File

@ -1,7 +1,7 @@
# 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)
[![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/)
[![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)
@ -80,21 +80,11 @@ Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/rel
```shell
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.
### 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
### 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 EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE"
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:

View File

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

View File

@ -1,3 +1,3 @@
#!/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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<version>3.1.5</version>
</parent>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat</artifactId>
<version>2024.2-SNAPSHOT</version>
<version>2023.9-SNAPSHOT</version>
<packaging>jar</packaging>
<name>WebGoat</name>
@ -29,6 +29,13 @@
</licenses>
<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>
<id>nbaars</id>
<name>Nanne Baars</name>
@ -36,6 +43,11 @@
<organizationUrl>https://github.com/nbaars</organizationUrl>
<timezone>Europe/Amsterdam</timezone>
</developer>
<developer>
<id>misfir3</id>
<name>Jason White</name>
<email>jason.white@owasp.org</email>
</developer>
<developer>
<id>zubcevic</id>
<name>René Zubcevic</name>
@ -46,8 +58,43 @@
<name>Àngel Ollé Blázquez</name>
<email>angel@olleb.com</email>
</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>
<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>
<connection>scm:git:git@github.com:WebGoat/WebGoat.git</connection>
<developerConnection>scm:git:git@github.com:WebGoat/WebGoat.git</developerConnection>
@ -62,56 +109,61 @@
<properties>
<!-- Shared properties with plugins and version numbers across submodules-->
<asciidoctorj.version>3.0.0</asciidoctorj.version>
<bootstrap.version>5.3.3</bootstrap.version>
<asciidoctorj.version>2.5.10</asciidoctorj.version>
<bootstrap.version>5.3.2</bootstrap.version>
<cglib.version>3.3.0</cglib.version>
<!-- do not update necessary for lesson -->
<checkstyle.version>3.6.0</checkstyle.version>
<checkstyle.version>3.3.1</checkstyle.version>
<commons-collections.version>3.2.1</commons-collections.version>
<commons-compress.version>1.27.1</commons-compress.version>
<commons-io.version>2.17.0</commons-io.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
<commons-text.version>1.12.0</commons-text.version>
<guava.version>33.3.1-jre</guava.version>
<commons-io.version>2.15.1</commons-io.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-text.version>1.10.0</commons-text.version>
<guava.version>32.1.3-jre</guava.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>
<jjwt.version>0.9.1</jjwt.version>
<jose4j.version>0.9.3</jose4j.version>
<jquery.version>3.7.1</jquery.version>
<jsoup.version>1.18.1</jsoup.version>
<jquery.version>3.7.0</jquery.version>
<jsoup.version>1.16.1</jsoup.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-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-javadoc-plugin.version>3.1.1</maven-javadoc-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.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven-surefire-plugin.version>3.2.1</maven-surefire-plugin.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<pmd.version>3.15.0</pmd.version>
<!-- Use UTF-8 Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
<waittimeForServerStart>60</waittimeForServerStart>
<webdriver.version>5.9.2</webdriver.version>
<thymeleaf.version>3.1.1.RELEASE</thymeleaf.version>
<webdriver.version>5.3.3</webdriver.version>
<webgoat.context>/</webgoat.context>
<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>
<wiremock.version>3.9.2</wiremock.version>
<wiremock.version>2.27.2</wiremock.version>
<xml-resolver.version>1.2</xml-resolver.version>
<xstream.version>1.4.5</xstream.version>
<!-- do not update necessary for lesson -->
<zxcvbn.version>1.9.0</zxcvbn.version>
<zxcvbn.version>1.8.0</zxcvbn.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.4.0</version>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
@ -196,8 +248,8 @@
<version>${webjars-locator-core.version}</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>${wiremock.version}</version>
</dependency>
<dependency>
@ -208,12 +260,12 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version>
<version>1.25.0</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby</artifactId>
<version>9.4.8.0</version>
<version>9.4.3.0</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -232,26 +284,24 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.20.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -261,10 +311,6 @@
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-hsqldb</artifactId>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
@ -371,12 +417,6 @@
<artifactId>jaxb-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.terma</groupId>
<artifactId>javaniotcpproxy</artifactId>
<version>1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -389,8 +429,10 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>3.0.0-beta-2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
@ -506,7 +548,6 @@
<version>${maven-surefire-plugin.version}</version>
<configuration>
<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
--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
@ -514,6 +555,8 @@
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED</argLine>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
<exclude>src/it/java</exclude>
<exclude>org/owasp/webgoat/*Test</exclude>
</excludes>
</configuration>
</plugin>
@ -593,7 +636,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<version>3.3.0</version>
<executions>
<execution>
<id>restrict-log4j-versions</id>
@ -617,6 +660,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
@ -688,18 +735,24 @@
<argument>--add-opens</argument>
<argument>java.base/java.lang.reflect=ALL-UNNAMED</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>--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>--add-opens</argument>
<argument>java.base/java.io=ALL-UNNAMED</argument>
<argument>--add-opens</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>
</arguments>
<waitForInterrupt>false</waitForInterrupt>
<waitAfterLaunch>${waittimeForServerStart}</waitAfterLaunch>
<healthCheckUrl>http://127.0.0.1:${webgoat.port}${webgoat.context}login</healthCheckUrl>
</configuration>
</execution>
<execution>

View File

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

View File

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

View File

@ -50,9 +50,9 @@ public class ChallengeIntegrationTest extends IntegrationTest {
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
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 =
RestAssured.given()
@ -92,9 +92,9 @@ public class ChallengeIntegrationTest extends IntegrationTest {
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
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 =
RestAssured.given()
@ -126,7 +126,7 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.extract()
.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()
.when()
.relaxedHTTPSValidation()
@ -144,7 +144,7 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("mail").build())
.get(webWolfUrl("mail"))
.then()
.extract()
.response()
@ -165,6 +165,6 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.asString();
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();
checkResults("Cryptography");
checkResults("/crypto");
}
private void checkAssignment2() {

View File

@ -28,6 +28,6 @@ public class DeserializationIntegrationTest extends IntegrationTest {
}
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");
checkAssignment(url("HttpBasics/attack2"), params, true);
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);
checkResults("/HttpBasics/");
}
@Test
@ -61,7 +51,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
.path("lessonCompleted"),
CoreMatchers.is(true));
checkResults("HttpProxies");
checkResults("/HttpProxies/");
}
@Test
@ -83,7 +73,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
"question_3_solution",
"Solution 2: The systems security is compromised even if only one goal is harmed.");
checkAssignment(url("cia/quiz"), params, true);
checkResults("CIA");
checkResults("/cia/");
}
@Test
@ -106,7 +96,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear();
params.put("payload", solution);
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("password", "BlackPearl");
checkAssignment(url("InsecureLogin/task"), params, true);
checkResults("InsecureLogin");
checkResults("/InsecureLogin/");
}
@Test
@ -128,7 +118,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear();
params.put("password", "ajnaeliclm^&&@kjn.");
checkAssignment(url("SecurePasswords/assignment"), params, true);
checkResults("SecurePasswords");
checkResults("SecurePasswords/");
startLesson("AuthBypass");
params.clear();
@ -138,7 +128,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("verifyMethod", "SEC_QUESTIONS");
params.put("userId", "12309746");
checkAssignment(url("auth-bypass/verify-account"), params, true);
checkResults("AuthBypass");
checkResults("/auth-bypass/");
startLesson("HttpProxies");
MatcherAssert.assertThat(
@ -154,7 +144,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
checkResults("HttpProxies");
checkResults("/HttpProxies/");
}
@Test
@ -190,7 +180,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("network_num", "24");
checkAssignment(url("ChromeDevTools/network"), params, true);
checkResults("ChromeDevTools");
checkResults("/ChromeDevTools/");
}
@Test
@ -204,7 +194,7 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("verifyMethod", "SEC_QUESTIONS");
params.put("userId", "12309746");
checkAssignment(url("auth-bypass/verify-account"), params, true);
checkResults("AuthBypass");
checkResults("/auth-bypass/");
}
@Test
@ -215,6 +205,6 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.put("param1", "secr37Value");
params.put("param2", "Main");
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
public void shutdown() {
checkResults("IDOR");
checkResults("/IDOR");
}
private void loginIDOR() {

View File

@ -3,7 +3,6 @@ package org.owasp.webgoat;
import static io.restassured.RestAssured.given;
import io.restassured.RestAssured;
import io.restassured.filter.log.LogDetail;
import io.restassured.http.ContentType;
import java.util.Map;
import lombok.Getter;
@ -16,80 +15,43 @@ import org.springframework.http.HttpStatus;
public abstract class IntegrationTest {
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 webWolfHost = System.getenv().getOrDefault("WEBWOLF_HOST", "127.0.0.1");
private static String webGoatContext =
System.getenv().getOrDefault("WEBGOAT_CONTEXT", "/WebGoat/");
@Getter
private static String webGoatHost = System.getenv().getOrDefault("WEBGOAT_HOST", "127.0.0.1");
private static String webWolfContext =
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 webWolfCookie;
@Getter private final String user = "webgoat";
protected String url(String url) {
return "http://localhost:%s%s%s".formatted(webGoatPort, webGoatContext, url);
return webgoatUrl + url;
}
protected class WebWolfUrlBuilder {
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 webWolfUrl(String url) {
return webWolfUrl + url;
}
/**
* In attack mode it means WebGoat calls WebWolf to perform an attack. In this case we need to
* use port 9090 in a Docker environment.
*/
protected WebWolfUrlBuilder attackMode() {
attackMode = true;
return this;
protected String webWolfFileUrl(String fileName) {
return webWolfUrl("files") + "/" + getUser() + "/" + fileName;
}
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
public void login() {
login("webgoat");
}
protected void login(String user) {
String location =
given()
.when()
@ -98,8 +60,6 @@ public abstract class IntegrationTest {
.formParam("password", "password")
.post(url("login"))
.then()
.log()
.ifValidationFails(LogDetail.ALL) // Log the response details if validation fails
.cookie("JSESSIONID")
.statusCode(302)
.extract()
@ -140,7 +100,7 @@ public abstract class IntegrationTest {
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(new WebWolfUrlBuilder().path("login").build())
.post(webWolfUrl("login"))
.then()
.statusCode(302)
.cookie("WEBWOLFSESSION")
@ -171,7 +131,7 @@ public abstract class IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/restartlesson.mvc/%s.lesson".formatted(lessonName)))
.get(url("service/restartlesson.mvc"))
.then()
.statusCode(200);
}
@ -207,18 +167,23 @@ public abstract class IntegrationTest {
CoreMatchers.is(expectedResult));
}
public void checkResults(String lesson) {
var result =
// TODO is prefix useful? not every lesson endpoint needs to start with a certain prefix (they are
// only required to be in the same package)
public void checkResults(String prefix) {
checkResults();
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc/%s.lesson".formatted(lesson)))
.andReturn();
MatcherAssert.assertThat(
result.then().statusCode(200).extract().jsonPath().getList("solved"),
CoreMatchers.everyItem(CoreMatchers.is(true)));
.get(url("service/lessonoverview.mvc"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.getList("assignment.path"),
CoreMatchers.everyItem(CoreMatchers.startsWith(prefix)));
}
public void checkResults() {
@ -273,7 +238,7 @@ public abstract class IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("file-server-location").build())
.get(webWolfUrl("file-server-location"))
.then()
.extract()
.response()
@ -301,7 +266,7 @@ public abstract class IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.delete(new WebWolfUrlBuilder().path("mail").build())
.delete(webWolfUrl("mail"))
.then()
.statusCode(HttpStatus.ACCEPTED.value());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("mail").build())
.get(webWolfUrl("mail"))
.then()
.extract()
.response()
@ -53,7 +53,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.queryParams(params)
.get(new WebWolfUrlBuilder().path("landing").build())
.get(webWolfUrl("landing"))
.then()
.statusCode(200);
responseBody =
@ -61,7 +61,7 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("requests").build())
.get(webWolfUrl("requests"))
.then()
.extract()
.response()
@ -72,6 +72,6 @@ public class WebWolfIntegrationTest extends IntegrationTest {
params.put("uniqueCode", uniqueCode);
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());");
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.http.ContentType;
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;
public class XXEIntegrationTest extends IntegrationTest {
@ -25,40 +28,47 @@ public class XXEIntegrationTest extends IntegrationTest {
""";
private String webGoatHomeDirectory;
private String webWolfFileServerLocation;
// TODO fix me
// /*
// * This test is to verify that all is secure when XXE security patch is applied.
// */
// @Test
// public void xxeSecure() throws IOException {
// startLesson("XXE");
// webGoatHomeDirectory = webGoatServerDirectory();
// RestAssured.given()
// .when()
// .relaxedHTTPSValidation()
// .cookie("JSESSIONID", getWebGoatCookie())
// .get(url("service/enable-security.mvc"))
// .then()
// .statusCode(200);
// checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, false);
// checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, false);
// checkAssignment(
// url("xxe/blind"),
// ContentType.XML,
// "<comment><text>" + getSecret() + "</text></comment>",
// false);
// }
/*
* This test is to verify that all is secure when XXE security patch is applied.
*/
@Test
public void xxeSecure() throws IOException {
startLesson("XXE");
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/enable-security.mvc"))
.then()
.statusCode(200);
checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, false);
checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, false);
checkAssignment(
url("xxe/blind"),
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
false);
}
/**
* This performs the steps of the exercise before the secret can be committed in the final step.
*
* @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 webWolfCallback = new WebWolfUrlBuilder().path("landing").attackMode().build();
String dtd7String = dtd7.replace("WEBWOLFURL", webWolfCallback).replace("SECRET", secretFile);
String dtd7String =
dtd7.replace("WEBWOLFURL", webWolfUrl("landing")).replace("SECRET", secretFile);
// upload DTD
RestAssured.given()
@ -66,17 +76,15 @@ public class XXEIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", "blind.dtd", dtd7String.getBytes())
.post(new WebWolfUrlBuilder().path("fileupload").build())
.post(webWolfUrl("fileupload"))
.then()
.extract()
.response()
.getBody()
.asString();
// upload attack
String xxe7String =
xxe7.replace("WEBWOLFURL", new WebWolfUrlBuilder().attackMode().path("files").build())
.replace("USERNAME", this.getUser());
xxe7.replace("WEBWOLFURL", webWolfUrl("files")).replace("USERNAME", this.getUser());
checkAssignment(url("xxe/blind"), ContentType.XML, xxe7String, false);
// read results from WebWolf
@ -85,7 +93,7 @@ public class XXEIntegrationTest extends IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(new WebWolfUrlBuilder().path("requests").build())
.get(webWolfUrl("requests"))
.then()
.extract()
.response()
@ -105,6 +113,7 @@ public class XXEIntegrationTest extends IntegrationTest {
public void runTests() throws IOException {
startLesson("XXE", true);
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
checkAssignment(url("xxe/simple"), ContentType.XML, xxe3, true);
checkAssignment(url("xxe/content-type"), ContentType.XML, xxe4, true);
checkAssignment(
@ -112,6 +121,6 @@ public class XXEIntegrationTest extends IntegrationTest {
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
true);
checkResults("XXE");
checkResults("xxe/");
}
}

View File

@ -32,23 +32,22 @@ package org.owasp.webgoat.container;
import static org.asciidoctor.Asciidoctor.Factory.create;
import io.undertow.util.Headers;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Attributes;
import org.asciidoctor.Options;
import org.asciidoctor.extension.JavaExtensionRegistry;
import org.owasp.webgoat.container.asciidoc.*;
import org.owasp.webgoat.container.i18n.Language;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
@ -136,17 +135,17 @@ public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
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()
.attributes(
Attributes.builder()
.attribute("source-highlighter", "coderay")
.attribute("backend", "xhtml")
.attribute("lang", determineLanguage())
.attribute("icons", org.asciidoctor.Attributes.FONT_ICONS)
.build())
.build();
Map<String, Object> options = new HashMap<>();
options.put("attributes", attributes);
return options;
}
private String determineLanguage() {
@ -160,7 +159,7 @@ public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
log.debug("browser locale {}", browserLocale);
return browserLocale.getLanguage();
} else {
String langHeader = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
String langHeader = request.getHeader(Headers.ACCEPT_LANGUAGE_STRING);
if (null != langHeader) {
log.debug("browser locale {}", langHeader);
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 org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.service.RestartLessonService;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
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
* 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
* clean the lesson related tables.
* quite easily see {@link RestartLessonService#restartLesson()} for how we clean the lesson
* related tables.
*/
@Bean(initMethod = "migrate")
public Flyway flyWayContainer() {
@ -61,7 +60,7 @@ public class DatabaseConfiguration {
}
@Bean
public LessonDataSource lessonDataSource(DataSource dataSource) {
return new LessonDataSource(dataSource);
public LessonDataSource lessonDataSource() {
return new LessonDataSource(dataSource());
}
}

View File

@ -32,33 +32,30 @@
package org.owasp.webgoat.container;
import java.io.File;
import org.owasp.webgoat.container.session.LessonSession;
import org.owasp.webgoat.container.session.UserSessionData;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
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;
@Configuration
@ComponentScan(basePackages = {"org.owasp.webgoat.container", "org.owasp.webgoat.lessons"})
@PropertySource("classpath:application-webgoat.properties")
@EnableAutoConfiguration
@EnableJpaRepositories(basePackages = {"org.owasp.webgoat.container"})
@EntityScan(basePackages = "org.owasp.webgoat.container")
public class WebGoat {
private final UserRepository userRepository;
public WebGoat(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired private UserRepository userRepository;
@Bean(name = "pluginTargetDirectory")
public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) {
@ -67,8 +64,21 @@ public class WebGoat {
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public LessonSession userSessionData() {
return new LessonSession();
public WebSession webSession() {
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,13 +25,27 @@
package org.owasp.webgoat.container.assignments;
import lombok.Getter;
import org.owasp.webgoat.container.i18n.PluginMessages;
import org.owasp.webgoat.container.lessons.Initializeable;
import org.owasp.webgoat.container.session.UserSessionData;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AssignmentEndpoint {
public abstract class AssignmentEndpoint implements Initializeable {
// TODO: move this to different bean.
@Autowired private PluginMessages messages;
@Autowired private WebSession webSession;
@Autowired private UserSessionData userSessionData;
@Getter @Autowired private PluginMessages messages;
protected WebSession getWebSession() {
return webSession;
}
protected UserSessionData getUserSessionData() {
return userSessionData;
}
/**
* Convenience method for create a successful result:
@ -72,4 +86,7 @@ public abstract class AssignmentEndpoint {
protected AttackResult.AttackResultBuilder informationMessage(AssignmentEndpoint 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;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class LessonTrackerInterceptor implements ResponseBodyAdvice<Object> {
private final Course course;
private final UserProgressRepository userProgressRepository;
private UserTrackerRepository userTrackerRepository;
private WebSession webSession;
public LessonTrackerInterceptor(Course course, UserProgressRepository userProgressRepository) {
this.course = course;
this.userProgressRepository = userProgressRepository;
public LessonTrackerInterceptor(
UserTrackerRepository userTrackerRepository, WebSession webSession) {
this.userTrackerRepository = userTrackerRepository;
this.webSession = webSession;
}
@Override
@ -68,30 +65,18 @@ public class LessonTrackerInterceptor implements ResponseBodyAdvice<Object> {
return o;
}
private void trackProgress(AttackResult attackResult) {
var user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Assert.notNull(user, "User not found in SecurityContext");
var username = realUsername(user);
var userProgress = userProgressRepository.findByUser(username);
if (userProgress == null) {
userProgress = new UserProgress(username);
protected AttackResult trackProgress(AttackResult attackResult) {
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
if (userTracker == null) {
userTracker = new UserTracker(webSession.getUserName());
}
Lesson lesson = course.getLessonByAssignment(attackResult.getAssignment());
Assert.notNull(lesson, "Lesson not found for assignment " + attackResult.getAssignment());
if (attackResult.assignmentSolved()) {
userProgress.assignmentSolved(lesson, attackResult.getAssignment());
userTracker.assignmentSolved(webSession.getCurrentLesson(), attackResult.getAssignment());
} else {
userProgress.assignmentFailed(lesson);
}
userProgressRepository.save(userProgress);
userTracker.assignmentFailed(webSession.getCurrentLesson());
}
userTrackerRepository.save(userTracker);
private String realUsername(WebGoatUser user) {
// 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();
return attackResult;
}
}

View File

@ -33,20 +33,42 @@ package org.owasp.webgoat.container.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.session.WebSession;
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;
@Controller
public class StartLesson {
private final WebSession ws;
private final Course course;
public StartLesson(Course course) {
public StartLesson(WebSession ws, Course course) {
this.ws = ws;
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"},
produces = "text/html")
public ModelAndView lessonPage(HttpServletRequest request) {
@ -59,7 +81,8 @@ public class StartLesson {
.findFirst()
.ifPresent(
lesson -> {
request.setAttribute("lesson", lesson);
ws.setCurrentLesson(lesson);
model.addObject("lesson", lesson);
});
return model;

View File

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

View File

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

View File

@ -22,9 +22,12 @@
package org.owasp.webgoat.container.lessons;
import static java.util.stream.Collectors.groupingBy;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
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.springframework.context.annotation.Bean;
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.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Slf4j
@Configuration
public class CourseConfiguration {
private final List<Lesson> lessons;
private final List<AssignmentEndpoint> assignments;
private final Map<String, List<AssignmentEndpoint>> assignmentsByPackage;
public CourseConfiguration(List<Lesson> lessons, List<AssignmentEndpoint> assignments) {
this.lessons = lessons;
this.assignments = assignments;
}
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()));
assignmentsByPackage =
this.assignments.stream().collect(groupingBy(a -> a.getClass().getPackageName()));
}
@Bean
public Course course() {
assignments.stream().forEach(this::attachToLesson);
// 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());
lessons.stream().forEach(l -> l.setAssignments(createAssignment(l)));
return new Course(lessons);
}
private List<String> findDiff() {
var matchedToLessons =
lessons.stream().flatMap(l -> l.getAssignments().stream()).map(a -> a.getName()).toList();
var allAssignments = assignments.stream().map(a -> a.getClass().getSimpleName()).toList();
var diff = new ArrayList<>(allAssignments);
diff.removeAll(matchedToLessons);
return diff;
private List<Assignment> createAssignment(Lesson lesson) {
var endpoints = assignmentsByPackage.get(lesson.getClass().getPackageName());
if (CollectionUtils.isEmpty(endpoints)) {
log.warn("Lesson: {} has no endpoints, is this intentionally?", lesson.getTitle());
return new ArrayList<>();
}
return endpoints.stream()
.map(
e ->
new Assignment(
e.getClass().getSimpleName(), getPath(e.getClass()), getHints(e.getClass())))
.toList();
}
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
* 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;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@ -31,10 +30,13 @@ import lombok.Setter;
@Setter
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) {
this.assignments.add(assignment);
/** Constructor for the Lesson object */
protected Lesson() {
id = ++count;
}
/**
@ -42,9 +44,9 @@ public abstract class Lesson {
*
* @return a {@link java.lang.String} object.
*/
public LessonName getName() {
public String 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();
}
/**
* This is used in Thymeleaf to construct the HTML to load the lesson content from. See
* lesson_content.html
*/
public final String getPackage() {
var packageName = this.getClass().getPackageName();
// 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 org.owasp.webgoat.container.lessons.Assignment;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* HintService class.
*
* @author rlawson
* @version $Id: $Id
*/
@RestController
public class HintService {
public static final String URL_HINTS_MVC = "/service/hint.mvc";
private final List<Hint> allHints;
private final WebSession webSession;
public HintService(Course course) {
this.allHints =
course.getLessons().stream()
.flatMap(lesson -> lesson.getAssignments().stream())
.map(this::createHint)
.flatMap(Collection::stream)
.toList();
public HintService(WebSession webSession) {
this.webSession = webSession;
}
/**
@ -38,7 +40,15 @@ public class HintService {
@GetMapping(path = URL_HINTS_MVC, produces = "application/json")
@ResponseBody
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) {

View File

@ -1,24 +1,33 @@
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.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* LessonInfoService class.
*
* @author dm
* @version $Id: $Id
*/
@RestController
@RequiredArgsConstructor
@AllArgsConstructor
public class LessonInfoService {
private final Course course;
private final WebSession webSession;
@GetMapping(path = "/service/lessoninfo.mvc/{lesson}")
public @ResponseBody LessonInfoModel getLessonInfo(
@PathVariable("lesson") LessonName lessonName) {
var lesson = course.getLessonByName(lessonName);
/**
* getLessonInfo.
*
* @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);
}
}

View File

@ -32,16 +32,16 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Category;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.lessons.LessonMenuItem;
import org.owasp.webgoat.container.lessons.LessonMenuItemType;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.LessonProgress;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@ -59,7 +59,8 @@ public class LessonMenuService {
public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc";
private final Course course;
private UserProgressRepository userTrackerRepository;
private final WebSession webSession;
private UserTrackerRepository userTrackerRepository;
@Value("#{'${exclude.categories}'.split(',')}")
private List<String> excludeCategories;
@ -73,13 +74,10 @@ public class LessonMenuService {
* @return a {@link java.util.List} object.
*/
@RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json")
public @ResponseBody List<LessonMenuItem> showLeftNav(@CurrentUsername String username) {
// 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.
public @ResponseBody List<LessonMenuItem> showLeftNav() {
List<LessonMenuItem> menu = new ArrayList<>();
List<Category> categories = course.getCategories();
UserProgress userTracker = userTrackerRepository.findByUser(username);
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
for (Category category : categories) {
if (excludeCategories.contains(category.name())) {
@ -99,12 +97,12 @@ public class LessonMenuService {
lessonItem.setName(lesson.getTitle());
lessonItem.setLink(lesson.getLink());
lessonItem.setType(LessonMenuItemType.LESSON);
LessonProgress lessonTracker = userTracker.getLessonProgress(lesson);
LessonTracker lessonTracker = userTracker.getLessonTracker(lesson);
boolean lessonSolved = lessonCompleted(lessonTracker.getLessonOverview(), lesson);
lessonItem.setComplete(lessonSolved);
categoryItem.addChild(lessonItem);
}
categoryItem.getChildren().sort(Comparator.comparingInt(LessonMenuItem::getRanking));
categoryItem.getChildren().sort((o1, o2) -> o1.getRanking() - o2.getRanking());
menu.add(categoryItem);
}
return menu;

View File

@ -4,15 +4,11 @@ import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
@ -24,8 +20,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor
public class LessonProgressService {
private final UserProgressRepository userProgressRepository;
private final Course course;
private final UserTrackerRepository userTrackerRepository;
private final WebSession webSession;
/**
* 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
*/
@GetMapping(value = "/service/lessonoverview.mvc/{lesson}")
@RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
@ResponseBody
public List<LessonOverview> lessonOverview(
@PathVariable("lesson") LessonName lessonName, @CurrentUsername String username) {
var userProgress = userProgressRepository.findByUser(username);
var lesson = course.getLessonByName(lessonName);
public List<LessonOverview> lessonOverview() {
var userTracker = userTrackerRepository.findByUser(webSession.getUserName());
var currentLesson = webSession.getCurrentLesson();
Assert.isTrue(lesson != null, "Lesson not found: " + lessonName);
var lessonProgress = userProgress.getLessonProgress(lesson);
return lessonProgress.getLessonOverview().entrySet().stream()
if (currentLesson != null) {
var lessonTracker = userTracker.getLessonTracker(currentLesson);
return lessonTracker.getLessonOverview().entrySet().stream()
.map(entry -> new LessonOverview(entry.getKey(), entry.getValue()))
.toList();
}
return List.of();
}
@AllArgsConstructor
@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.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.owasp.webgoat.container.CurrentUser;
import org.owasp.webgoat.container.lessons.Initializable;
import org.owasp.webgoat.container.lessons.LessonName;
import org.owasp.webgoat.container.session.Course;
import org.owasp.webgoat.container.users.UserProgress;
import org.owasp.webgoat.container.users.UserProgressRepository;
import org.owasp.webgoat.container.users.WebGoatUser;
import org.owasp.webgoat.container.lessons.Initializeable;
import org.owasp.webgoat.container.lessons.Lesson;
import org.owasp.webgoat.container.session.WebSession;
import org.owasp.webgoat.container.users.UserTracker;
import org.owasp.webgoat.container.users.UserTrackerRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
@ -47,25 +44,25 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@Slf4j
public class RestartLessonService {
private final Course course;
private final UserProgressRepository userTrackerRepository;
private final WebSession webSession;
private final UserTrackerRepository userTrackerRepository;
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)
public void restartLesson(
@PathVariable("lesson") LessonName lessonName, @CurrentUser WebGoatUser user) {
var lesson = course.getLessonByName(lessonName);
public void restartLesson() {
Lesson al = webSession.getCurrentLesson();
log.debug("Restarting lesson: " + al);
UserProgress userTracker = userTrackerRepository.findByUser(user.getUsername());
userTracker.reset(lesson);
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
userTracker.reset(al);
userTrackerRepository.save(userTracker);
var flyway = flywayLessons.apply(user.getUsername());
var flyway = flywayLessons.apply(webSession.getUserName());
flyway.clean();
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;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.container.CurrentUser;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -18,17 +17,17 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequiredArgsConstructor
public class SessionService {
private final WebSession webSession;
private final RestartLessonService restartLessonService;
private final Messages messages;
@RequestMapping(path = "/service/enable-security.mvc", produces = "application/json")
@ResponseBody
public String applySecurity(@CurrentUser WebGoatUser user) {
// webSession.toggleSecurity();
// restartLessonService.restartLesson(user);
public String applySecurity() {
webSession.toggleSecurity();
restartLessonService.restartLesson();
// TODO disabled for now
// var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled";
return messages.getMessage("Not working...");
var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled";
return messages.getMessage(msg);
}
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.lessons.Category;
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()
.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
@EqualsAndHashCode
public class LessonProgress {
public class LessonTracker {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ -61,22 +61,25 @@ public class LessonProgress {
@Getter private String lessonName;
@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;
@Version private Integer version;
protected LessonProgress() {
private LessonTracker() {
// JPA
}
public LessonProgress(Lesson lesson) {
public LessonTracker(Lesson lesson) {
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) {
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
*/
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?
*/
public boolean isLessonSolved() {
return assignments.stream().allMatch(Assignment::isSolved);
return allAssignments.size() == solvedAssignments.size();
}
/** 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! */
void reset() {
assignments.clear();
solvedAssignments.clear();
}
/**
* @return list containing all the assignments solved or not
*/
public Map<Assignment, Boolean> getLessonOverview() {
return assignments.stream().collect(Collectors.toMap(a -> a, Assignment::isSolved));
}
long numberOfSolvedAssignments() {
return assignments.size();
List<Assignment> notSolved =
allAssignments.stream().filter(i -> !solvedAssignments.contains(i)).toList();
Map<Assignment, Boolean> overview =
notSolved.stream().collect(Collectors.toMap(a -> a, b -> false));
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
public class Scoreboard {
private final UserProgressRepository userTrackerRepository;
private final UserTrackerRepository userTrackerRepository;
private final UserRepository userRepository;
private final Course course;
private final PluginMessages pluginMessages;
@ -46,7 +46,7 @@ public class Scoreboard {
.collect(Collectors.toList());
}
private List<String> challengesSolved(UserProgress userTracker) {
private List<String> challengesSolved(UserTracker userTracker) {
List<String> challenges =
List.of(
"Challenge1",
@ -59,10 +59,10 @@ public class Scoreboard {
"Challenge8",
"Challenge9");
return challenges.stream()
.map(userTracker::getLessonProgress)
.map(userTracker::getLessonTracker)
.flatMap(Optional::stream)
.filter(LessonProgress::isLessonSolved)
.map(LessonProgress::getLessonName)
.filter(LessonTracker::isLessonSolved)
.map(LessonTracker::getLessonName)
.map(this::toLessonTitle)
.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 lombok.AllArgsConstructor;
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.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -19,10 +19,10 @@ import org.springframework.stereotype.Service;
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final UserProgressRepository userTrackerRepository;
private final UserTrackerRepository userTrackerRepository;
private final JdbcTemplate jdbcTemplate;
private final Function<String, Flyway> flywayLessons;
private final List<Initializable> lessonInitializables;
private final List<Initializeable> lessonInitializables;
@Override
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
@ -43,7 +43,7 @@ public class UserService implements UserDetailsService {
if (!userAlreadyExists) {
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);
}
}

View File

@ -9,10 +9,13 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.container.lessons.Assignment;
import org.owasp.webgoat.container.lessons.Lesson;
/**
@ -49,7 +52,7 @@ import org.owasp.webgoat.container.lessons.Lesson;
@Slf4j
@Entity
@EqualsAndHashCode
public class UserProgress {
public class UserTracker {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ -59,11 +62,11 @@ public class UserProgress {
private String user;
@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;
}
@ -73,15 +76,15 @@ public class UserProgress {
* @param lesson the lesson
* @return a lesson tracker created if not already present
*/
public LessonProgress getLessonProgress(Lesson lesson) {
Optional<LessonProgress> progress =
lessonProgress.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst();
if (!progress.isPresent()) {
LessonProgress newLessonTracker = new LessonProgress(lesson);
lessonProgress.add(newLessonTracker);
public LessonTracker getLessonTracker(Lesson lesson) {
Optional<LessonTracker> lessonTracker =
lessonTrackers.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst();
if (!lessonTracker.isPresent()) {
LessonTracker newLessonTracker = new LessonTracker(lesson);
lessonTrackers.add(newLessonTracker);
return newLessonTracker;
} else {
return progress.get();
return lessonTracker.get();
}
}
@ -91,34 +94,43 @@ public class UserProgress {
* @param id the id of the lesson
* @return optional due to the fact we can only create a lesson tracker based on a lesson
*/
public Optional<LessonProgress> getLessonProgress(String id) {
return lessonProgress.stream().filter(l -> l.getLessonName().equals(id)).findFirst();
public Optional<LessonTracker> getLessonTracker(String id) {
return lessonTrackers.stream().filter(l -> l.getLessonName().equals(id)).findFirst();
}
public void assignmentSolved(Lesson lesson, String assignmentName) {
LessonProgress progress = getLessonProgress(lesson);
progress.incrementAttempts();
progress.assignmentSolved(assignmentName);
LessonTracker lessonTracker = getLessonTracker(lesson);
lessonTracker.incrementAttempts();
lessonTracker.assignmentSolved(assignmentName);
}
public void assignmentFailed(Lesson lesson) {
LessonProgress progress = getLessonProgress(lesson);
progress.incrementAttempts();
LessonTracker lessonTracker = getLessonTracker(lesson);
lessonTracker.incrementAttempts();
}
public void reset(Lesson al) {
LessonProgress progress = getLessonProgress(al);
progress.reset();
LessonTracker lessonTracker = getLessonTracker(al);
lessonTracker.reset();
}
public long numberOfLessonsSolved() {
return lessonProgress.stream().filter(LessonProgress::isLessonSolved).count();
public int numberOfLessonsSolved() {
int numberOfLessonsSolved = 0;
for (LessonTracker lessonTracker : lessonTrackers) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
}
}
return numberOfLessonsSolved;
}
public long numberOfAssignmentsSolved() {
return lessonProgress.stream()
.map(LessonProgress::numberOfSolvedAssignments)
.mapToLong(Long::valueOf)
.sum();
public int numberOfAssignmentsSolved() {
int numberOfAssignmentsSolved = 0;
for (LessonTracker lessonTracker : lessonTrackers) {
Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
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.AssignmentHints;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@ -48,11 +50,9 @@ import org.springframework.web.bind.annotation.RestController;
})
public class VerifyAccount extends AssignmentEndpoint {
private final LessonSession userSessionData;
@Autowired private WebSession webSession;
public VerifyAccount(LessonSession userSessionData) {
this.userSessionData = userSessionData;
}
@Autowired UserSessionData userSessionData;
@PostMapping(
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.Lesson;
import org.springframework.stereotype.Component;
/**
* @author nbaars
* @since 3/21/17.
*/
@Component
public class ChallengeIntro extends Lesson {
@Override

View File

@ -25,7 +25,8 @@ package org.owasp.webgoat.lessons.challenges;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@ -35,12 +36,13 @@ import org.springframework.web.bind.annotation.RestController;
@AllArgsConstructor
public class FlagController extends AssignmentEndpoint {
private final WebSession webSession;
private final Flags flags;
@PostMapping(path = "/challenge/flag/{flagNumber}")
@PostMapping(path = "/challenge/flag", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public AttackResult postFlag(@PathVariable int flagNumber, @RequestParam String flag) {
var expectedFlag = flags.getFlag(flagNumber);
public AttackResult postFlag(@RequestParam String flag) {
Flag expectedFlag = flags.getFlag(webSession.getCurrentLesson());
if (expectedFlag.isCorrect(flag)) {
return success(this).feedback("challenge.flag.correct").build();
} else {

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.IntStream;
import org.owasp.webgoat.container.lessons.Lesson;
import org.springframework.context.annotation.Configuration;
@Configuration
@ -14,6 +15,12 @@ public class Flags {
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) {
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.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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
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
* @since 30.11.18
@ -39,16 +39,11 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class NetworkDummy extends AssignmentEndpoint {
private final LessonSession lessonSession;
public NetworkDummy(LessonSession lessonSession) {
this.lessonSession = lessonSession;
}
@PostMapping("/ChromeDevTools/dummy")
@ResponseBody
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)) {
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 {
@Override
public Category getDefaultCategory() {
return Category.A5;
return Category.A10;
}
@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.AssignmentHints;
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.web.bind.annotation.PostMapping;
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"})
public class CSRFConfirmFlag1 extends AssignmentEndpoint {
@Autowired LessonSession userSessionData;
@Autowired UserSessionData userSessionData;
@PostMapping(
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.AssignmentHints;
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.http.MediaType;
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.RestController;
/**
* @author nbaars
* @since 11/17/17.
*/
@RestController
@AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"})
public class CSRFFeedback extends AssignmentEndpoint {
@Autowired private LessonSession userSessionData;
@Autowired private UserSessionData userSessionData;
@Autowired private ObjectMapper objectMapper;
@PostMapping(

View File

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

View File

@ -22,26 +22,47 @@
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.AssignmentHints;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author nbaars
* @since 11/17/17.
*/
@RestController
@AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"})
public class CSRFLogin extends AssignmentEndpoint {
private final UserTrackerRepository userTrackerRepository;
public CSRFLogin(UserTrackerRepository userTrackerRepository) {
this.userTrackerRepository = userTrackerRepository;
}
@PostMapping(
path = "/csrf/login",
produces = {"application/json"})
@ResponseBody
public AttackResult completed(@CurrentUsername String username) {
if (username.startsWith("csrf")) {
public AttackResult completed(HttpServletRequest request) {
String userName = request.getUserPrincipal().getName();
if (userName.startsWith("csrf")) {
markAssignmentSolvedWithRealUser(userName.substring("csrf-".length()));
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.List;
import java.util.Map;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints;
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.web.bind.annotation.GetMapping;
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"})
public class ForgedReviews extends AssignmentEndpoint {
@Autowired private WebSession webSession;
private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss");
private static final Map<String, List<Review>> userReviews = new HashMap<>();
@ -71,9 +73,9 @@ public class ForgedReviews extends AssignmentEndpoint {
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = ALL_VALUE)
@ResponseBody
public Collection<Review> retrieveReviews(@CurrentUsername String username) {
public Collection<Review> retrieveReviews() {
Collection<Review> allReviews = Lists.newArrayList();
Collection<Review> newReviews = userReviews.get(username);
Collection<Review> newReviews = userReviews.get(webSession.getUserName());
if (newReviews != null) {
allReviews.addAll(newReviews);
}
@ -86,11 +88,7 @@ public class ForgedReviews extends AssignmentEndpoint {
@PostMapping("/csrf/review")
@ResponseBody
public AttackResult createNewReview(
String reviewText,
Integer stars,
String validateReq,
HttpServletRequest request,
@CurrentUsername String username) {
String reviewText, Integer stars, String validateReq, HttpServletRequest request) {
final String host = (request.getHeader("host") == null) ? "NULL" : request.getHeader("host");
final String referer =
(request.getHeader("referer") == null) ? "NULL" : request.getHeader("referer");
@ -99,11 +97,11 @@ public class ForgedReviews extends AssignmentEndpoint {
Review review = new Review();
review.setText(reviewText);
review.setDateTime(LocalDateTime.now().format(fmt));
review.setUser(username);
review.setUser(webSession.getUserName());
review.setStars(stars);
var reviews = userReviews.getOrDefault(username, new ArrayList<>());
var reviews = userReviews.getOrDefault(webSession.getUserName(), new ArrayList<>());
reviews.add(review);
userReviews.put(username, reviews);
userReviews.put(webSession.getUserName(), reviews);
// short-circuit
if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {
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.AssignmentHints;
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.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
@ -48,7 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
})
public class IDOREditOtherProfile extends AssignmentEndpoint {
@Autowired private LessonSession userSessionData;
@Autowired private UserSessionData userSessionData;
@PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json")
@ResponseBody

View File

@ -28,7 +28,7 @@ import java.util.Map;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@ -38,12 +38,6 @@ import org.springframework.web.bind.annotation.RestController;
@AssignmentHints({"idor.hints.idor_login"})
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<>();
public void initIDORInfo() {
@ -65,11 +59,13 @@ public class IDORLogin extends AssignmentEndpoint {
@ResponseBody
public AttackResult completed(@RequestParam String username, @RequestParam String password) {
initIDORInfo();
UserSessionData userSessionData = getUserSessionData();
if (idorUserInfo.containsKey(username)) {
if ("tom".equals(username) && idorUserInfo.get("tom").get("password").equals(password)) {
lessonSession.setValue("idor-authenticated-as", username);
lessonSession.setValue("idor-authenticated-user-id", idorUserInfo.get(username).get("id"));
userSessionData.setValue("idor-authenticated-as", username);
userSessionData.setValue(
"idor-authenticated-user-id", idorUserInfo.get(username).get("id"));
return success(this).feedback("idor.login.success").feedbackArgs(username).build();
} else {
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.AssignmentHints;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -48,7 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
})
public class IDORViewOtherProfile extends AssignmentEndpoint {
@Autowired LessonSession userSessionData;
@Autowired UserSessionData userSessionData;
@GetMapping(
path = "/IDOR/profile/{userId}",

View File

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

View File

@ -1,6 +1,7 @@
package org.owasp.webgoat.lessons.jwt.claimmisuse;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
@ -19,7 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/JWT/")
@RequestMapping("/JWT/jku")
@RestController
@AssignmentHints({
"jwt-jku-hint1",
@ -30,7 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
})
public class JWTHeaderJKUEndpoint extends AssignmentEndpoint {
@PostMapping("jku/follow/{user}")
@PostMapping("/follow/{user}")
public @ResponseBody String follow(@PathVariable("user") String user) {
if ("Jerry".equals(user)) {
return "Following yourself seems redundant";
@ -39,7 +40,7 @@ public class JWTHeaderJKUEndpoint extends AssignmentEndpoint {
}
}
@PostMapping("jku/delete")
@PostMapping("/delete")
public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) {
if (StringUtils.isEmpty(token)) {
return failed(this).feedback("jwt-invalid-token").build();
@ -47,12 +48,12 @@ public class JWTHeaderJKUEndpoint extends AssignmentEndpoint {
try {
var decodedJWT = JWT.decode(token);
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 algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey());
JWT.require(algorithm).build().verify(decodedJWT);
var username = decodedJWT.getClaims().get("username").asString();
String username = decodedJWT.getClaims().get("username").asString();
if ("Jerry".equals(username)) {
return failed(this).feedback("jwt-final-jerry-account").build();
}

View File

@ -52,7 +52,7 @@ import org.springframework.web.bind.annotation.RestController;
"jwt-kid-hint5",
"jwt-kid-hint6"
})
@RequestMapping("/JWT/")
@RequestMapping("/JWT/kid")
public class JWTHeaderKIDEndpoint extends AssignmentEndpoint {
private final LessonDataSource dataSource;
@ -61,7 +61,7 @@ public class JWTHeaderKIDEndpoint extends AssignmentEndpoint {
this.dataSource = dataSource;
}
@PostMapping("kid/follow/{user}")
@PostMapping("/follow/{user}")
public @ResponseBody String follow(@PathVariable("user") String user) {
if ("Jerry".equals(user)) {
return "Following yourself seems redundant";
@ -70,7 +70,7 @@ public class JWTHeaderKIDEndpoint extends AssignmentEndpoint {
}
}
@PostMapping("kid/delete")
@PostMapping("/delete")
public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) {
if (StringUtils.isEmpty(token)) {
return failed(this).feedback("jwt-invalid-token").build();

View File

@ -27,7 +27,7 @@ import lombok.AllArgsConstructor;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -44,7 +44,7 @@ public class SampleAttack extends AssignmentEndpoint {
String secretValue = "secr37Value";
// 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")
@ResponseBody

View File

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

View File

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

View File

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

View File

@ -26,7 +26,6 @@ import static java.util.Optional.ofNullable;
import java.time.LocalDateTime;
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.AttackResult;
import org.springframework.beans.factory.annotation.Value;
@ -58,14 +57,12 @@ public class SimpleMailAssignment extends AssignmentEndpoint {
path = "/PasswordReset/simple-mail",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseBody
public AttackResult login(
@RequestParam String email,
@RequestParam String password,
@CurrentUsername String webGoatUsername) {
public AttackResult login(@RequestParam String email, @RequestParam String password) {
String emailAddress = ofNullable(email).orElse("unknown@webgoat.org");
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();
} else {
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,
value = "/PasswordReset/simple-mail/reset")
@ResponseBody
public AttackResult resetPassword(
@RequestParam String emailReset, @CurrentUsername String username) {
public AttackResult resetPassword(@RequestParam String emailReset) {
String email = ofNullable(emailReset).orElse("unknown@webgoat.org");
return sendEmail(extractUsername(email), email, username);
return sendEmail(extractUsername(email), email);
}
private String extractUsername(String email) {
@ -87,8 +83,8 @@ public class SimpleMailAssignment extends AssignmentEndpoint {
return email.substring(0, index == -1 ? email.length() : index);
}
private AttackResult sendEmail(String username, String email, String webGoatUsername) {
if (username.equals(webGoatUsername)) {
private AttackResult sendEmail(String username, String email) {
if (username.equals(getWebSession().getUserName())) {
PasswordResetEmail mailEvent =
PasswordResetEmail.builder()
.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.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@ -23,8 +23,9 @@ import org.springframework.web.multipart.MultipartFile;
})
public class ProfileUpload extends ProfileUploadBase {
public ProfileUpload(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
super(webGoatHomeDirectory);
public ProfileUpload(
@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
}
@PostMapping(
@ -34,14 +35,13 @@ public class ProfileUpload extends ProfileUploadBase {
@ResponseBody
public AttackResult uploadFileHandler(
@RequestParam("uploadedFile") MultipartFile file,
@RequestParam(value = "fullName", required = false) String fullName,
@CurrentUsername String username) {
return super.execute(file, fullName, username);
@RequestParam(value = "fullName", required = false) String fullName) {
return super.execute(file, fullName);
}
@GetMapping("/PathTraversal/profile-picture")
@ResponseBody
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) {
return super.getProfilePicture(username);
public ResponseEntity<?> getProfilePicture() {
return super.getProfilePicture();
}
}

View File

@ -11,9 +11,9 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
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.AttackResult;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
@ -26,8 +26,9 @@ import org.springframework.web.multipart.MultipartFile;
public class ProfileUploadBase extends AssignmentEndpoint {
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()) {
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();
}
File uploadDirectory = cleanupAndCreateDirectoryForUser(username);
File uploadDirectory = cleanupAndCreateDirectoryForUser();
try {
var uploadedFile = new File(uploadDirectory, fullName);
@ -56,8 +57,9 @@ public class ProfileUploadBase extends AssignmentEndpoint {
}
@SneakyThrows
protected File cleanupAndCreateDirectoryForUser(String username) {
var uploadDirectory = new File(this.webGoatHomeDirectory, "/PathTraversal/" + username);
protected File cleanupAndCreateDirectoryForUser() {
var uploadDirectory =
new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName());
if (uploadDirectory.exists()) {
FileSystemUtils.deleteRecursively(uploadDirectory);
}
@ -83,14 +85,15 @@ public class ProfileUploadBase extends AssignmentEndpoint {
.build();
}
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) {
public ResponseEntity<?> getProfilePicture() {
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))
.body(getProfilePictureAsBase64(username));
.body(getProfilePictureAsBase64());
}
protected byte[] getProfilePictureAsBase64(String username) {
var profilePictureDirectory = new File(this.webGoatHomeDirectory, "/PathTraversal/" + username);
protected byte[] getProfilePictureAsBase64() {
var profilePictureDirectory =
new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName());
var profileDirectoryFiles = profilePictureDirectory.listFiles();
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.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@ -23,8 +23,9 @@ import org.springframework.web.multipart.MultipartFile;
})
public class ProfileUploadFix extends ProfileUploadBase {
public ProfileUploadFix(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
super(webGoatHomeDirectory);
public ProfileUploadFix(
@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
}
@PostMapping(
@ -34,14 +35,13 @@ public class ProfileUploadFix extends ProfileUploadBase {
@ResponseBody
public AttackResult uploadFileHandler(
@RequestParam("uploadedFileFix") MultipartFile file,
@RequestParam(value = "fullNameFix", required = false) String fullName,
@CurrentUsername String username) {
return super.execute(file, fullName != null ? fullName.replace("../", "") : "", username);
@RequestParam(value = "fullNameFix", required = false) String fullName) {
return super.execute(file, fullName != null ? fullName.replace("../", "") : "");
}
@GetMapping("/PathTraversal/profile-picture-fix")
@ResponseBody
public ResponseEntity<?> getProfilePicture(@CurrentUsername String username) {
return super.getProfilePicture(username);
public ResponseEntity<?> getProfilePicture() {
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.APPLICATION_JSON_VALUE;
import org.owasp.webgoat.container.CurrentUsername;
import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult;
import org.owasp.webgoat.container.session.WebSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -22,8 +22,8 @@ import org.springframework.web.multipart.MultipartFile;
public class ProfileUploadRemoveUserInput extends ProfileUploadBase {
public ProfileUploadRemoveUserInput(
@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
super(webGoatHomeDirectory);
@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) {
super(webGoatHomeDirectory, webSession);
}
@PostMapping(
@ -32,8 +32,7 @@ public class ProfileUploadRemoveUserInput extends ProfileUploadBase {
produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttackResult uploadFileHandler(
@RequestParam("uploadedFileRemoveUserInput") MultipartFile file,
@CurrentUsername String username) {
return super.execute(file, file.getOriginalFilename(), username);
@RequestParam("uploadedFileRemoveUserInput") MultipartFile file) {
return super.execute(file, file.getOriginalFilename());
}
}

View File

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

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