* refactor: modernize code * refactor: move to Tomcat * chore: bump to Spring Boot 3.3.3 * refactor: use Testcontainers to run integration tests * refactor: lesson/assignment progress * chore: format code * refactor: first step into removing base class for assignment Always been a bit of an ugly construction, as none of the dependencies are clear. The constructors are hidden due to autowiring the base class. This PR removes two of the fields. As a bonus we now wire the authentication principal directly in the controllers. * refactor: use authentication principal directly. * refactor: pass lesson to the endpoints No more need to get the current lesson set in a session. The lesson is now passed to the endpoints. * fix: Testcontainers cannot run on Windows host in Github actions. Since we have Windows specific paths let's run it standalone for now. We need to run these tests on Docker as well (for now disabled)
287 lines
9.7 KiB
Java
287 lines
9.7 KiB
Java
package org.owasp.webgoat;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
|
|
|
import io.restassured.RestAssured;
|
|
import io.restassured.http.ContentType;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import lombok.Data;
|
|
import lombok.SneakyThrows;
|
|
import org.junit.jupiter.api.AfterEach;
|
|
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;
|
|
|
|
public class CSRFIntegrationTest extends IntegrationTest {
|
|
|
|
private static final String trickHTML3 =
|
|
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
|
|
+ "<input type=\"hidden\" name=\"csrf\" value=\"thisisnotchecked\"/>\n"
|
|
+ "<input type=\"submit\" name=\"submit\" value=\"assignment 3\"/>\n"
|
|
+ "</form></body></html>";
|
|
|
|
private static final String trickHTML4 =
|
|
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
|
|
+ "<input type=\"hidden\" name=\"reviewText\" value=\"hoi\"/>\n"
|
|
+ "<input type=\"hidden\" name=\"starts\" value=\"3\"/>\n"
|
|
+ "<input type=\"hidden\" name=\"validateReq\""
|
|
+ " value=\"2aa14227b9a13d0bede0388a7fba9aa9\"/>\n"
|
|
+ "<input type=\"submit\" name=\"submit\" value=\"assignment 4\"/>\n"
|
|
+ "</form>\n"
|
|
+ "</body></html>";
|
|
|
|
private static final String trickHTML7 =
|
|
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" enctype='text/plain'"
|
|
+ " method=\"POST\">\n"
|
|
+ "<input type=\"hidden\""
|
|
+ " name='{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat"
|
|
+ " is the best!!' value='\"}' />\n"
|
|
+ "<input type=\"submit\" value=\"assignment 7\"/>\n"
|
|
+ "</form></body></html>";
|
|
|
|
private static final String trickHTML8 =
|
|
"<!DOCTYPE html><html><body><form action=\"WEBGOATURL\" method=\"POST\">\n"
|
|
+ "<input type=\"hidden\" name=\"username\" value=\"csrf-USERNAME\"/>\n"
|
|
+ "<input type=\"hidden\" name=\"password\" value=\"password\"/>\n"
|
|
+ "<input type=\"hidden\" name=\"matchingPassword\" value=\"password\"/>\n"
|
|
+ "<input type=\"hidden\" name=\"agree\" value=\"agree\"/>\n"
|
|
+ "<input type=\"submit\" value=\"assignment 8\"/>\n"
|
|
+ "</form></body></html>";
|
|
|
|
private String webwolfFileDir;
|
|
|
|
@BeforeEach
|
|
@SneakyThrows
|
|
public void init() {
|
|
startLesson("CSRF");
|
|
webwolfFileDir = getWebWolfFileServerLocation();
|
|
uploadTrickHtml("csrf3.html", trickHTML3.replace("WEBGOATURL", url("csrf/basic-get-flag")));
|
|
uploadTrickHtml("csrf4.html", trickHTML4.replace("WEBGOATURL", url("csrf/review")));
|
|
uploadTrickHtml("csrf7.html", trickHTML7.replace("WEBGOATURL", url("csrf/feedback/message")));
|
|
uploadTrickHtml(
|
|
"csrf8.html",
|
|
trickHTML8.replace("WEBGOATURL", url("login")).replace("USERNAME", this.getUser()));
|
|
}
|
|
|
|
@TestFactory
|
|
Iterable<DynamicTest> testCSRFLesson() {
|
|
return Arrays.asList(
|
|
dynamicTest("assignment 3", () -> checkAssignment3(callTrickHtml("csrf3.html"))),
|
|
dynamicTest("assignment 4", () -> checkAssignment4(callTrickHtml("csrf4.html"))),
|
|
dynamicTest("assignment 7", () -> checkAssignment7(callTrickHtml("csrf7.html"))),
|
|
dynamicTest("assignment 8", () -> checkAssignment8(callTrickHtml("csrf8.html"))));
|
|
}
|
|
|
|
@AfterEach
|
|
public void shutdown() throws IOException {
|
|
// logout();
|
|
login(); // because old cookie got replaced and invalidated
|
|
startLesson("CSRF", false);
|
|
checkResults("CSRF");
|
|
}
|
|
|
|
private void uploadTrickHtml(String htmlName, String htmlContent) throws IOException {
|
|
|
|
// remove any left over html
|
|
Path webWolfFilePath = Paths.get(webwolfFileDir);
|
|
if (webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)).toFile().exists()) {
|
|
Files.delete(webWolfFilePath.resolve(Paths.get(this.getUser(), htmlName)));
|
|
}
|
|
|
|
// upload trick html
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("WEBWOLFSESSION", getWebWolfCookie())
|
|
.multiPart("file", htmlName, htmlContent.getBytes())
|
|
.post(new WebWolfUrlBuilder().path("fileupload").build())
|
|
.then()
|
|
.extract()
|
|
.response()
|
|
.getBody()
|
|
.asString();
|
|
}
|
|
|
|
private String callTrickHtml(String htmlName) {
|
|
String result =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.cookie("WEBWOLFSESSION", getWebWolfCookie())
|
|
.get(new WebWolfUrlBuilder().path("files/%s/%s", this.getUser(), htmlName).build())
|
|
.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", new WebWolfUrlBuilder().path("files/fake.html").build())
|
|
.post(goatURL)
|
|
.then()
|
|
.extract()
|
|
.path("flag")
|
|
.toString();
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.clear();
|
|
params.put("confirmFlagVal", flag);
|
|
checkAssignment(url("csrf/confirm-flag-1"), params, true);
|
|
}
|
|
|
|
private void checkAssignment4(String goatURL) {
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.clear();
|
|
params.put("reviewText", "test review");
|
|
params.put("stars", "5");
|
|
params.put(
|
|
"validateReq", "2aa14227b9a13d0bede0388a7fba9aa9"); // always the same token is the weakness
|
|
|
|
boolean result =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build())
|
|
.formParams(params)
|
|
.post(goatURL)
|
|
.then()
|
|
.extract()
|
|
.path("lessonCompleted");
|
|
assertEquals(true, result);
|
|
}
|
|
|
|
private void checkAssignment7(String goatURL) {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put(
|
|
"{\"name\":\"WebGoat\",\"email\":\"webgoat@webgoat.org\",\"content\":\"WebGoat is the"
|
|
+ " best!!",
|
|
"\"}");
|
|
|
|
String flag =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build())
|
|
.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("csrf/feedback"), params, true);
|
|
}
|
|
|
|
private void checkAssignment8(String goatURL) {
|
|
|
|
// first make sure there is an attack csrf- user
|
|
registerCSRFUser();
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.clear();
|
|
params.put("username", "csrf-" + this.getUser());
|
|
params.put("password", "password");
|
|
|
|
// login and get the new cookie
|
|
String newCookie =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.header("Referer", new WebWolfUrlBuilder().path("files/fake.html").build())
|
|
.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())
|
|
.relaxedHTTPSValidation()
|
|
.get(url("service/lessonoverview.mvc/CSRF"))
|
|
.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"));
|
|
}
|
|
}
|