Merge branch 'release/v8.2.0' into develop
This commit is contained in:
commit
eed0feed06
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -11,6 +11,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
tags-ignore:
|
||||
- '*'
|
||||
paths-ignore:
|
||||
|
4
.mvn/wrapper/maven-wrapper.properties
vendored
4
.mvn/wrapper/maven-wrapper.properties
vendored
@ -1,2 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.2.5/apache-maven-3.2.5-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar
|
||||
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
|
||||
|
@ -1,5 +1,44 @@
|
||||
# WebGoat release notes
|
||||
|
||||
## 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
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
|
||||
<name>WebGoat Parent Pom</name>
|
||||
<description>Parent Pom for the WebGoat Project. A deliberately insecure Web Application</description>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
@ -84,6 +84,7 @@ public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
|
||||
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());
|
||||
|
@ -17,6 +17,9 @@ public class OperatingSystemMacro extends InlineMacroProcessor {
|
||||
|
||||
@Override
|
||||
public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
|
||||
return System.getProperty("os.name");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package org.owasp.webgoat.asciidoc;
|
||||
|
||||
import org.asciidoctor.ast.ContentNode;
|
||||
import org.asciidoctor.extension.InlineMacroProcessor;
|
||||
import org.owasp.webgoat.users.WebGoatUser;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class UsernameMacro extends InlineMacroProcessor {
|
||||
|
||||
public UsernameMacro(String macroName) {
|
||||
super(macroName);
|
||||
}
|
||||
|
||||
public UsernameMacro(String macroName, Map<String, Object> config) {
|
||||
super(macroName, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
|
||||
var auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
var username = "unknown";
|
||||
if (auth.getPrincipal() instanceof WebGoatUser) {
|
||||
username = ((WebGoatUser) auth.getPrincipal()).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);
|
||||
}
|
||||
}
|
@ -16,7 +16,11 @@ public class WebGoatTmpDirMacro extends InlineMacroProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(ContentNode contentNode, String target, Map<String, Object> attributes) {
|
||||
return EnvironmentExposure.getEnv().getProperty("webgoat.server.directory");
|
||||
public Object process(ContentNode contentNode, String target, Map<String, Object> attributes) {
|
||||
var env = EnvironmentExposure.getEnv().getProperty("webgoat.server.directory");
|
||||
|
||||
//see https://discuss.asciidoctor.org/How-to-create-inline-macro-producing-HTML-In-AsciidoctorJ-td8313.html for why quoted is used
|
||||
return createPhraseNode(contentNode, "quoted", env);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ public class WebGoatVersionMacro extends InlineMacroProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(ContentNode contentNode, String target, Map<String, Object> attributes) {
|
||||
return EnvironmentExposure.getEnv().getProperty("webgoat.build.version");
|
||||
public Object process(ContentNode contentNode, String target, Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -10,25 +10,25 @@ import org.owasp.webgoat.deserialization.SerializationHelper;
|
||||
|
||||
public class DeserializationTest extends IntegrationTest {
|
||||
|
||||
private static String OS = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
private static String OS = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
@Test
|
||||
public void runTests() throws IOException {
|
||||
startLesson("InsecureDeserialization");
|
||||
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.clear();
|
||||
|
||||
if (OS.indexOf("win")>-1) {
|
||||
params.put("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5")));
|
||||
|
||||
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);
|
||||
|
||||
checkAssignment(url("/WebGoat/InsecureDeserialization/task"), params, true);
|
||||
|
||||
checkResults("/InsecureDeserialization/");
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,7 @@
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import lombok.SneakyThrows;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@ -18,38 +11,49 @@ import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.security.core.token.Sha512DigestUtils;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import lombok.SneakyThrows;
|
||||
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;
|
||||
|
||||
public class PathTraversalTest extends IntegrationTest {
|
||||
|
||||
//the JUnit5 way
|
||||
|
||||
//the JUnit5 way
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
|
||||
private File fileToUpload = null;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@SneakyThrows
|
||||
public void init() {
|
||||
fileToUpload = Files.createFile(
|
||||
fileToUpload = Files.createFile(
|
||||
tempDir.resolve("test.jpg")).toFile();
|
||||
Files.write(fileToUpload.toPath(), "This is a test" .getBytes());
|
||||
startLesson("PathTraversal");
|
||||
Files.write(fileToUpload.toPath(), "This is a test".getBytes());
|
||||
startLesson("PathTraversal");
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Iterable<DynamicTest> testPathTraversal() {
|
||||
return Arrays.asList(
|
||||
dynamicTest("assignment 1 - profile upload",()-> assignment1()),
|
||||
dynamicTest("assignment 2 - profile upload fix",()-> assignment2()),
|
||||
dynamicTest("assignment 3 - profile upload remove user input",()-> assignment3()),
|
||||
dynamicTest("assignment 4 - profile upload random pic",()-> assignment4())
|
||||
);
|
||||
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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public void assignment1() throws IOException {
|
||||
MatcherAssert.assertThat(
|
||||
MatcherAssert.assertThat(
|
||||
RestAssured.given()
|
||||
.when()
|
||||
.relaxedHTTPSValidation()
|
||||
@ -63,7 +67,7 @@ public class PathTraversalTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
public void assignment2() throws IOException {
|
||||
MatcherAssert.assertThat(
|
||||
MatcherAssert.assertThat(
|
||||
RestAssured.given()
|
||||
.when()
|
||||
.relaxedHTTPSValidation()
|
||||
@ -77,7 +81,7 @@ public class PathTraversalTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
public void assignment3() throws IOException {
|
||||
MatcherAssert.assertThat(
|
||||
MatcherAssert.assertThat(
|
||||
RestAssured.given()
|
||||
.when()
|
||||
.relaxedHTTPSValidation()
|
||||
@ -88,6 +92,7 @@ public class PathTraversalTest extends IntegrationTest {
|
||||
.statusCode(200)
|
||||
.extract().path("lessonCompleted"), CoreMatchers.is(true));
|
||||
}
|
||||
|
||||
public void assignment4() throws IOException {
|
||||
var uri = "/WebGoat/PathTraversal/random-picture?id=%2E%2E%2F%2E%2E%2Fpath-traversal-secret";
|
||||
RestAssured.given().urlEncodingEnabled(false)
|
||||
@ -101,10 +106,34 @@ public class PathTraversalTest extends IntegrationTest {
|
||||
|
||||
checkAssignment("/WebGoat/PathTraversal/random", Map.of("secret", Sha512DigestUtils.shaHex(getWebgoatUser())), true);
|
||||
}
|
||||
|
||||
|
||||
public void assignment5() throws IOException {
|
||||
var webGoatHome = System.getProperty("user.dir") + "/target/.webgoat/PathTraversal/" + getWebgoatUser();
|
||||
webGoatHome = webGoatHome.replaceAll("^[a-zA-Z]:", ""); //Remove C: from the home directory on Windows
|
||||
|
||||
var webGoatDirectory = new File(webGoatHome);
|
||||
var zipFile = new File(webGoatDirectory, "upload.zip");
|
||||
try (var zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
|
||||
ZipEntry e = new ZipEntry("../../../../../../../../../../" + webGoatDirectory.toString() + "/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("/WebGoat/PathTraversal/zip-slip")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract().path("lessonCompleted"), CoreMatchers.is(true));
|
||||
|
||||
}
|
||||
|
||||
@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("/PathTraversal");
|
||||
//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");
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -1,6 +1,7 @@
|
||||
package org.owasp.webgoat.path_traversal;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
@ -15,9 +16,12 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Comparator;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class ProfileUploadBase extends AssignmentEndpoint {
|
||||
|
||||
private String webGoatHomeDirectory;
|
||||
@ -64,14 +68,18 @@ public class ProfileUploadBase extends AssignmentEndpoint {
|
||||
}
|
||||
|
||||
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) {
|
||||
try (var inputStream = new FileInputStream(profileDirectoryFiles[0])) {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))
|
||||
.body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)));
|
||||
return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream));
|
||||
} catch (IOException e) {
|
||||
return defaultImage();
|
||||
}
|
||||
@ -81,10 +89,8 @@ public class ProfileUploadBase extends AssignmentEndpoint {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private ResponseEntity<?> defaultImage() {
|
||||
private byte[] defaultImage() {
|
||||
var inputStream = getClass().getResourceAsStream("/images/account.png");
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))
|
||||
.body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)));
|
||||
return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
package org.owasp.webgoat.path_traversal;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.CopyOption;
|
||||
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 static org.springframework.http.MediaType.ALL_VALUE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
@RestController
|
||||
@AssignmentHints({"path-traversal-zip-slip.hint1", "path-traversal-zip-slip.hint2", "path-traversal-zip-slip.hint3", "path-traversal-zip-slip.hint4"})
|
||||
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 = new File(getWebGoatHomeDirectory(), "/PathTraversal/zip-slip/" + getWebSession().getUserName());
|
||||
var uploadDirectory = new File(getWebGoatHomeDirectory(), "/PathTraversal/" + getWebSession().getUserName());
|
||||
FileSystemUtils.deleteRecursively(uploadDirectory);
|
||||
Files.createDirectories(tmpZipDirectory.toPath());
|
||||
Files.createDirectories(uploadDirectory.toPath());
|
||||
byte[] currentImage = getProfilePictureAsBase64();
|
||||
|
||||
try {
|
||||
var uploadedZipFile = new File(tmpZipDirectory, file.getOriginalFilename());
|
||||
FileCopyUtils.copy(file.getBytes(), uploadedZipFile);
|
||||
|
||||
ZipFile zip = new ZipFile(uploadedZipFile);
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry e = entries.nextElement();
|
||||
File f = new File(uploadDirectory, 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();
|
||||
}
|
||||
|
||||
}
|
@ -78,7 +78,7 @@
|
||||
enctype="multipart/form-data"
|
||||
action="/WebGoat/PathTraversal/profile-upload-fix">
|
||||
<div class="preview text-center">
|
||||
<img class="preview-img-fix" th:src="@{/images/account.png}" alt="Preview Image" width="200"
|
||||
<img class="preview-img" th:src="@{/images/account.png}" alt="Preview Image" width="200"
|
||||
height="200" id="previewFix"/>
|
||||
<div class="browse-button">
|
||||
<i class="fa fa-pencil"></i>
|
||||
@ -133,7 +133,7 @@
|
||||
enctype="multipart/form-data"
|
||||
action="/WebGoat/PathTraversal/profile-upload-remove-user-input">
|
||||
<div class="preview text-center">
|
||||
<img class="preview-img-fix" th:src="@{/images/account.png}" alt="Preview Image" width="200"
|
||||
<img class="preview-img" th:src="@{/images/account.png}" alt="Preview Image" width="200"
|
||||
height="200" id="previewRemoveUserInput"/>
|
||||
<div class="browse-button">
|
||||
<i class="fa fa-pencil"></i>
|
||||
@ -211,4 +211,70 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:PathTraversal_zip_slip.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:PathTraversal_zip_slip_assignment.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<div class="upload-container">
|
||||
<form class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
onsubmit='return false'
|
||||
contentType="false"
|
||||
|
||||
prepareData="profileZipSlip"
|
||||
enctype="multipart/form-data"
|
||||
action="/WebGoat/PathTraversal/zip-slip">
|
||||
<div class="preview text-center">
|
||||
<img th:src="@{|~/WebGoat/PathTraversal/zip-slip/profile-image/${#authentication.name}|}" width="1"
|
||||
height="1" />
|
||||
<img class="preview-img" th:src="@{/images/account.png}" alt="Preview Image" width="200"
|
||||
height="200" id="previewZipSlip"/>
|
||||
<div class="browse-button">
|
||||
<i class="fa fa-pencil"></i>
|
||||
<input class="browse-input" type="file" required name="uploadedFile"
|
||||
id="uploadedFileZipSlip"/>
|
||||
</div>
|
||||
<span class="Error"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Full Name:</label>
|
||||
<input class="form-control" type="text" id="fullNameZipSlip" name="fullName" required
|
||||
value="test"
|
||||
placeholder="Enter Your Full Name"/>
|
||||
<span class="Error"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email:</label>
|
||||
<input class="form-control" type="email" id="emailZipSlip" name="email" required
|
||||
placeholder="Enter Your Email" value="test@test.com"/>
|
||||
<span class="Error"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input class="form-control" type="password" id="passwordZipSlip" name="password" required
|
||||
placeholder="Enter Password" value="test"/>
|
||||
<span class="Error"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary btn-block" value="Submit">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="lesson-page-solution">
|
||||
<div class="adoc-content" th:replace="doc:PathTraversal_zip_slip_solution.adoc"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</html>
|
@ -45,4 +45,14 @@ 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-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 image. Please contact our helpdesk.
|
||||
|
||||
|
@ -60,3 +60,19 @@ function newRandomPicture() {
|
||||
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;
|
||||
}
|
||||
|
||||
webgoat.customjs.profileZipSlipRetrieval = function () {
|
||||
$.get("PathTraversal/zip-slip", function (result, status) {
|
||||
document.getElementById("previewZipSlip").src = "data:image/png;base64," + result;
|
||||
});
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ so you need to upload your file to the following location which is outside the n
|
||||
|OS |Location
|
||||
|
||||
|`operatingSystem:os[]`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/username:user[]`
|
||||
|
||||
|===
|
||||
|
@ -7,5 +7,5 @@ Again the same assignment but can you bypass the implemented fix?
|
||||
|OS |Location
|
||||
|
||||
|`operatingSystem:os[]`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/username:user[]`
|
||||
|===
|
||||
|
@ -9,6 +9,6 @@ Again the same assignment but can you bypass the implemented fix?
|
||||
|OS |Location
|
||||
|
||||
|`operatingSystem:os[]`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/username:user[]`
|
||||
|
||||
|===
|
||||
|
@ -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 for example send the outcome of the listing towards your server before showing the real 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<? extends ZipEntry> 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. The problem is, as we have seen in the previous assignments, 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`.
|
@ -0,0 +1,13 @@
|
||||
=== Zip Slip assignment
|
||||
|
||||
This time the developers only allow you to upload zip files, however, they made a programming mistake in that 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?
|
||||
|
||||
|===
|
||||
|OS |Location
|
||||
|
||||
|`operatingSystem:os[]`
|
||||
|`webGoatTempDir:temppath[]PathTraversal/username:user[]`
|
||||
|
||||
|===
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
=== Solution
|
||||
|
||||
First let's create a zip file with an image inside:
|
||||
|
||||
[source]
|
||||
----
|
||||
curl -o cat.jpg http://localhost:8080/WebGoat/images/cats/1.jpg
|
||||
zip profile.zip cat.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 image. Please contact our helpdesk.
|
||||
----
|
||||
|
||||
Let's create a zip file which traverses all the way 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[] http://localhost:8080/WebGoat/images/cats/1.jpg
|
||||
zip profile.zip ../../../../../../../..webGoatTempDir:temppath[]PathTraversal/username:user[]/username:user[].jpg
|
||||
----
|
||||
|
||||
Now if we upload this zip file, the assignment will be solved.
|
||||
|
||||
=== Why did this work?
|
||||
|
||||
In the code the developers used the following fragment:
|
||||
|
||||
[source%linenums]
|
||||
----
|
||||
ZipFile zip = new ZipFile(uploadedZipFile);
|
||||
Enumeration<? extends ZipEntry> 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. You can use the following method in Java:
|
||||
|
||||
[source]
|
||||
----
|
||||
File profilePicture = new File(uploadDirectory, e.getName());
|
||||
if (profilePicture.
|
||||
|
||||
----
|
||||
|
@ -5,12 +5,12 @@
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.2.0-SNAPSHOT</version>
|
||||
<version>8.2.0</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
Loading…
x
Reference in New Issue
Block a user