Merge branch 'release/v8.2.0' into develop

This commit is contained in:
Nanne Baars 2021-05-25 20:56:38 +02:00
commit eed0feed06
No known key found for this signature in database
GPG Key ID: A6D6C06FE4EC14E7
55 changed files with 499 additions and 97 deletions

View File

@ -11,6 +11,7 @@ on:
branches:
- master
- develop
- release/*
tags-ignore:
- '*'
paths-ignore:

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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/");
}
}

View File

@ -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");
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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.

View File

@ -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;
});
}

View File

@ -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[]`
|===

View File

@ -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[]`
|===

View File

@ -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[]`
|===

View File

@ -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`.

View File

@ -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[]`
|===

View File

@ -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.
----

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>