Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
d3ee9431d8 | |||
4811a9d563 | |||
c6e86861fe | |||
b64aa43760 | |||
dd7f4074cd | |||
8c10000e4e | |||
43b82027f5 | |||
5eed385d5d | |||
75d0405da1 | |||
157b982394 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -14,7 +14,8 @@
|
||||
/.settings/org.eclipse.wst.validation.prefs
|
||||
/.externalToolBuilders/
|
||||
.project
|
||||
/target
|
||||
*/target/*
|
||||
mongo-data/*
|
||||
.classpath
|
||||
.idea/
|
||||
.settings/
|
||||
@ -29,6 +30,7 @@ src/main/webapp/plugin_lessons/*.jar
|
||||
src/main/webapp/users/*.props
|
||||
classes/*
|
||||
*.iml
|
||||
pom.xml.versionsBackup
|
||||
|
||||
/*.iml
|
||||
.extract/*
|
||||
@ -39,3 +41,4 @@ webgoat-lessons/**/target
|
||||
**/*.jar
|
||||
**/.DS_Store
|
||||
webgoat-server/mongo-data/*
|
||||
webgoat-lessons/vulnerable-components/dependency-reduced-pom.xml
|
||||
|
19
README.MD
19
README.MD
@ -1,4 +1,4 @@
|
||||
# WebGoat: A deliberately insecure Web Application
|
||||
# WebGoat 8: A deliberately insecure Web Application
|
||||
|
||||
[](https://travis-ci.org/WebGoat/WebGoat)
|
||||
[](https://coveralls.io/github/WebGoat/WebGoat?branch=master)
|
||||
@ -6,10 +6,6 @@
|
||||
[](https://www.versioneye.com/user/projects/562da95ae346d7000e0369aa)
|
||||
[](https://www.owasp.org/index.php/OWASP_Project_Inventory#tab=Labs_Projects)
|
||||
|
||||
# Important
|
||||
|
||||
This is the development version of WebGoat 8, if you are looking for a released stable version please go to: https://github.com/WebGoat/WebGoat/wiki/Running-WebGoat
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
@ -68,6 +64,11 @@ Download the latest WebWolf release from [https://github.com/WebGoat/WebGoat/rel
|
||||
java -jar webgoat-server-<<version>>.jar
|
||||
```
|
||||
|
||||
By default WebGoat starts at port 8080 in order to change this use the following property:
|
||||
|
||||
```Shell
|
||||
java -jar webgoat-server-<<version>>.jar --server.port=9090
|
||||
```
|
||||
|
||||
## 3. Run from the sources
|
||||
|
||||
@ -88,20 +89,22 @@ Now let's start by compiling the project.
|
||||
|
||||
```Shell
|
||||
cd WebGoat
|
||||
git checkout develop
|
||||
git checkout <<branch_name>>
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
Now we are ready to run the project. WebGoat 8.x is using Spring-Boot.
|
||||
|
||||
```Shell
|
||||
mvn -pl webwolf spring-boot:run
|
||||
mvn -pl webgoat-server spring-boot:run
|
||||
```
|
||||
... you should be running webgoat on localhost:8080/WebGoat momentarily
|
||||
|
||||
|
||||
To change IP addresss add the following variable to WebGoat/webgoat-container/src/main/resources/application.properties file
|
||||
|
||||
```server.address=x.x.x.x
|
||||
```
|
||||
server.address=x.x.x.x
|
||||
```
|
||||
|
||||
# Vagrant
|
||||
|
@ -1,25 +1,11 @@
|
||||
version: '2.0'
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:latest
|
||||
expose:
|
||||
- "27017"
|
||||
volumes:
|
||||
- './mongo-data:/data/db'
|
||||
webgoat:
|
||||
build: webgoat-server/
|
||||
command: "sh /home/webgoat/start.sh"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
[mongo, activemq]
|
||||
environment:
|
||||
WG_MONGO_PORT: 27017
|
||||
WG_MONGO_HOST: mongo
|
||||
WG_MQ_HOST: activemq
|
||||
WG_MQ_PORT: 61616
|
||||
WG_INTERNAL_MONGO: "false"
|
||||
webwolf:
|
||||
build: webwolf/
|
||||
command: "sh /home/webwolf/start.sh"
|
||||
@ -27,8 +13,3 @@ services:
|
||||
- webgoat
|
||||
ports:
|
||||
- "8081:8081"
|
||||
environment:
|
||||
WG_MONGO_PORT: 27017
|
||||
WG_MONGO_HOST: mongo
|
||||
WG_MQ_HOST: activemq
|
||||
WG_MQ_PORT: 61616
|
@ -12,7 +12,7 @@ if [ "${BRANCH}" == "master" ] && [ ! -z "${TRAVIS_TAG}" ]; then
|
||||
docker push $REPO
|
||||
elif [ ! -z "${TRAVIS_TAG}" ]; then
|
||||
# Creating a tag build we push it to Docker with that tag
|
||||
docker build --build-arg webgoat_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:${TRAVIS_TAG} .
|
||||
docker build --build-arg webgoat_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:${TRAVIS_TAG} -t $REPO:latest .
|
||||
docker push $REPO
|
||||
elif [ "${BRANCH}" == "develop" ]; then
|
||||
docker build -f Dockerfile -t $REPO:snapshot .
|
||||
|
@ -36,16 +36,6 @@
|
||||
|
||||
</profiles>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
@ -127,7 +117,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
@ -202,12 +192,6 @@
|
||||
<version>${junit.version}</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.fakemongo</groupId>
|
||||
<artifactId>fongo</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- ************* END: Dependencies for Unit and Integration Testing ************** -->
|
||||
<!-- ************* END: <dependencies> ************** -->
|
||||
</dependencies>
|
||||
|
@ -32,6 +32,7 @@ package org.owasp.webgoat;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.asciidoctor.Asciidoctor;
|
||||
import org.owasp.webgoat.i18n.Language;
|
||||
import org.thymeleaf.TemplateProcessingParameters;
|
||||
@ -41,6 +42,7 @@ import org.thymeleaf.templateresolver.TemplateResolver;
|
||||
import java.io.*;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.commons.lang3.CharEncoding.UTF_8;
|
||||
import static org.asciidoctor.Asciidoctor.Factory.create;
|
||||
|
||||
/**
|
||||
@ -50,6 +52,7 @@ import static org.asciidoctor.Asciidoctor.Factory.create;
|
||||
* <div th:replace="doc:AccessControlMatrix_plan.adoc"></div>
|
||||
* </code>
|
||||
*/
|
||||
@Slf4j
|
||||
public class AsciiDoctorTemplateResolver extends TemplateResolver {
|
||||
|
||||
private static final Asciidoctor asciidoctor = create();
|
||||
@ -73,11 +76,15 @@ public class AsciiDoctorTemplateResolver extends TemplateResolver {
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(TemplateProcessingParameters params, String resourceName) {
|
||||
InputStream is = readInputStreamOrFallbackToEnglish(resourceName, language);
|
||||
try {
|
||||
try (InputStream is = readInputStreamOrFallbackToEnglish(resourceName, language)) {
|
||||
if (is == null) {
|
||||
log.warn("Resource name: {} not found, did you add the adoc file?", resourceName);
|
||||
return new ByteArrayInputStream(new byte[0]);
|
||||
} else {
|
||||
StringWriter writer = new StringWriter();
|
||||
asciidoctor.convert(new InputStreamReader(is), writer, createAttributes());
|
||||
return new ByteArrayInputStream(writer.getBuffer().toString().getBytes());
|
||||
return new ByteArrayInputStream(writer.getBuffer().toString().getBytes(UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//no html yet
|
||||
return new ByteArrayInputStream(new byte[0]);
|
||||
|
@ -23,11 +23,5 @@ public class CleanupLocalProgressFiles {
|
||||
|
||||
@PostConstruct
|
||||
public void clean() {
|
||||
File dir = new File(webgoatHome);
|
||||
//do it safe, check whether the subdir mongodb is available as subdirectory
|
||||
File[] mongoDir = dir.listFiles(f -> f.isDirectory() && f.getName().contains("mongodb"));
|
||||
if (mongoDir != null && mongoDir.length == 1) {
|
||||
FileSystemUtils.deleteRecursively(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@ package org.owasp.webgoat.lessons;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -38,11 +42,14 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@Entity
|
||||
public class Assignment {
|
||||
@NonNull
|
||||
@Id
|
||||
private String name;
|
||||
@NonNull
|
||||
private String path;
|
||||
@Transient
|
||||
private List<String> hints;
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import lombok.Getter;
|
||||
import org.owasp.webgoat.lessons.AbstractLesson;
|
||||
import org.owasp.webgoat.lessons.Assignment;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -44,16 +45,20 @@ import java.util.stream.Collectors;
|
||||
* @version $Id: $Id
|
||||
* @since October 29, 2003
|
||||
*/
|
||||
@Entity
|
||||
public class LessonTracker {
|
||||
@Getter
|
||||
@Id
|
||||
private String lessonName;
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private final Set<Assignment> solvedAssignments = Sets.newHashSet();
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private final List<Assignment> allAssignments = Lists.newArrayList();
|
||||
@Getter
|
||||
private int numberOfAttempts = 0;
|
||||
|
||||
protected LessonTracker() {
|
||||
//Mongo
|
||||
private LessonTracker() {
|
||||
//JPA
|
||||
}
|
||||
|
||||
public LessonTracker(AbstractLesson lesson) {
|
||||
|
@ -15,7 +15,7 @@ import javax.validation.constraints.Size;
|
||||
public class UserForm {
|
||||
|
||||
@NotNull
|
||||
@Size(min=6, max=10)
|
||||
@Size(min=6, max=20)
|
||||
private String username;
|
||||
@NotNull
|
||||
@Size(min=6, max=10)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -8,7 +8,7 @@ import java.util.List;
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
public interface UserRepository extends MongoRepository<WebGoatUser, String> {
|
||||
public interface UserRepository extends JpaRepository<WebGoatUser, String> {
|
||||
|
||||
WebGoatUser findByUsername(String username);
|
||||
|
||||
|
@ -5,8 +5,8 @@ import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.lessons.AbstractLesson;
|
||||
import org.owasp.webgoat.lessons.Assignment;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -44,12 +44,16 @@ import java.util.stream.Collectors;
|
||||
* @since October 29, 2003
|
||||
*/
|
||||
@Slf4j
|
||||
@Entity
|
||||
public class UserTracker {
|
||||
|
||||
@Id
|
||||
private final String user;
|
||||
private String user;
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private List<LessonTracker> lessonTrackers = Lists.newArrayList();
|
||||
|
||||
private UserTracker() {}
|
||||
|
||||
public UserTracker(final String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 4/30/17.
|
||||
*/
|
||||
public interface UserTrackerRepository extends MongoRepository<UserTracker, String> {
|
||||
public interface UserTrackerRepository extends JpaRepository<UserTracker, String> {
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
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;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@ -16,6 +17,7 @@ import java.util.Collections;
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
@Getter
|
||||
@Entity
|
||||
public class WebGoatUser implements UserDetails {
|
||||
|
||||
public static final String ROLE_USER = "WEBGOAT_USER";
|
||||
|
@ -4,6 +4,9 @@ server.session.timeout=600
|
||||
server.contextPath=/WebGoat
|
||||
server.port=8080
|
||||
|
||||
spring.datasource.url=jdbc:hsqldb:file:${webgoat.server.directory}/data/webgoat
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
|
||||
|
||||
logging.level.org.springframework=WARN
|
||||
logging.level.org.springframework.boot.devtools=WARN
|
||||
@ -28,7 +31,6 @@ webgoat.feedback.address.html=<A HREF=mailto:webgoat@owasp.org>webgoat@owasp.org
|
||||
webgoat.database.driver=org.hsqldb.jdbcDriver
|
||||
webgoat.database.connection.string=jdbc:hsqldb:mem:{USER}
|
||||
webgoat.default.language=en
|
||||
webgoat.embedded.mongo=${WG_INTERNAL_MONGO:true}
|
||||
|
||||
webwolf.host=${WEBWOLF_HOST:localhost}
|
||||
webwolf.port=${WEBWOLF_PORT:8081}
|
||||
@ -39,10 +41,5 @@ webwolf.url.mail=http://${webwolf.host}:${webwolf.port}/mail
|
||||
spring.jackson.serialization.indent_output=true
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
|
||||
spring.data.mongodb.host=${WG_MONGO_HOST:localhost}
|
||||
spring.data.mongodb.port=${WG_MONGO_PORT:27017}
|
||||
spring.data.mongodb.database=webgoat
|
||||
spring.mongodb.embedded.storage.databaseDir=${webgoat.user.directory}/mongodb/
|
||||
|
||||
#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
|
||||
|
@ -45,7 +45,6 @@ define(['jquery',
|
||||
this.$el.find('.attack-feedback').hide();
|
||||
this.$el.find('.attack-output').hide();
|
||||
this.makeFormsAjax();
|
||||
//this.ajaxifyAttackHref();
|
||||
$(window).scrollTop(0); //work-around til we get the scroll down sorted out
|
||||
var startPageNum = this.model.get('pageNum');
|
||||
this.initPagination(startPageNum);
|
||||
@ -86,6 +85,8 @@ define(['jquery',
|
||||
var prepareDataFunctionName = $(curForm).attr('prepareData');
|
||||
var callbackFunctionName = $(curForm).attr('callback');
|
||||
var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||
var successCallBackFunctionName = $(curForm).attr('successCallback');
|
||||
var failureCallbackFunctionName = $(curForm).attr('failureCallback');
|
||||
var callbackFunction = (typeof webgoat.customjs[callbackFunctionName] === 'function') ? webgoat.customjs[callbackFunctionName] : function() {};
|
||||
// var submitData = this.$form.serialize();
|
||||
this.curForm = curForm;
|
||||
@ -104,19 +105,18 @@ define(['jquery',
|
||||
//complete: function (data) {
|
||||
//callbackFunction(data);
|
||||
//}
|
||||
}).then(self.onSuccessResponse.bind(self), self.onErrorResponse.bind(self));
|
||||
}).then(function(data){
|
||||
self.onSuccessResponse(data, failureCallbackFunctionName, successCallBackFunctionName)}, self.onErrorResponse.bind(self));
|
||||
return false;
|
||||
},
|
||||
|
||||
onSuccessResponse: function(data) {
|
||||
onSuccessResponse: function(data, failureCallbackFunctionName, successCallBackFunctionName) {
|
||||
this.renderFeedback(data.feedback);
|
||||
this.renderOutput(data.output || "");
|
||||
|
||||
var successCallBackFunctionName = this.$form.attr('successCallback');
|
||||
var failureCallbackFunctionName = this.$form.attr('failureCallback');
|
||||
//var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||
successCallbackFunction = (typeof webgoat.customjs[successCallBackFunctionName] === 'function') ? webgoat.customjs[successCallBackFunctionName] : function() {};
|
||||
failureCallbackFunction = (typeof webgoat.customjs[failureCallbackFunctionName] === 'function') ? webgoat.customjs[failureCallbackFunctionName] : function() {};
|
||||
var successCallbackFunction = (typeof webgoat.customjs[successCallBackFunctionName] === 'function') ? webgoat.customjs[successCallBackFunctionName] : function() {};
|
||||
var failureCallbackFunction = (typeof webgoat.customjs[failureCallbackFunctionName] === 'function') ? webgoat.customjs[failureCallbackFunctionName] : function() {};
|
||||
//TODO: refactor back assignmentCompleted in Java
|
||||
if (data.lessonCompleted || data.assignmentCompleted) {
|
||||
this.markAssignmentComplete();
|
||||
@ -146,14 +146,6 @@ define(['jquery',
|
||||
return false;
|
||||
},
|
||||
|
||||
ajaxifyAttackHref: function() { // rewrite any links with hrefs point to relative attack URLs
|
||||
var self = this;
|
||||
// instruct in template to have links returned with the attack-link class
|
||||
$('a.attack-link').submit(function(event){
|
||||
$.get(this.action, "json").then(self.onSuccessResponse, self.onErrorResponse);
|
||||
});
|
||||
},
|
||||
|
||||
renderFeedback: function(feedback) {
|
||||
this.$curFeedback.html(polyglot.t(feedback) || "");
|
||||
this.$curFeedback.show(400)
|
||||
|
@ -1,23 +0,0 @@
|
||||
package org.owasp.webgoat.plugins;
|
||||
|
||||
import com.github.fakemongo.Fongo;
|
||||
import com.mongodb.MongoClient;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
|
||||
|
||||
/**
|
||||
* Using Fongo for embedded in memory MongoDB testing
|
||||
*/
|
||||
@Configuration
|
||||
public class TestConfig extends AbstractMongoConfiguration {
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoClient mongo() throws Exception {
|
||||
return new Fongo(getDatabaseName()).getMongo();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@DataJpaTest
|
||||
@RunWith(SpringRunner.class)
|
||||
public class UserRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Test
|
||||
public void userShouldBeSaved() {
|
||||
WebGoatUser user = new WebGoatUser("test", "password");
|
||||
userRepository.saveAndFlush(user);
|
||||
|
||||
user = userRepository.findByUsername("test");
|
||||
|
||||
Assertions.assertThat(user.getUsername()).isEqualTo("test");
|
||||
Assertions.assertThat(user.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.owasp.webgoat.lessons.Assignment;
|
||||
import org.owasp.webgoat.lessons.Category;
|
||||
import org.owasp.webgoat.lessons.NewLesson;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@DataJpaTest
|
||||
@RunWith(SpringRunner.class)
|
||||
public class UserTrackerRepositoryTest {
|
||||
|
||||
private class TestLesson extends NewLesson {
|
||||
|
||||
@Override
|
||||
public Category getDefaultCategory() {
|
||||
return Category.AJAX_SECURITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHints() {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDefaultRanking() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Assignment> getAssignments() {
|
||||
Assignment assignment = new Assignment("test", "test", Lists.newArrayList());
|
||||
return Lists.newArrayList(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private UserTrackerRepository userTrackerRepository;
|
||||
|
||||
|
||||
@Test
|
||||
public void saveUserTracker() {
|
||||
UserTracker userTracker = new UserTracker("test");
|
||||
LessonTracker lessonTracker = userTracker.getLessonTracker(new TestLesson());
|
||||
|
||||
userTrackerRepository.save(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
Assertions.assertThat(userTracker.getLessonTracker("test")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void solvedAssignmentsShouldBeSaved() {
|
||||
UserTracker userTracker = new UserTracker("test");
|
||||
TestLesson lesson = new TestLesson();
|
||||
userTracker.getLessonTracker(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTracker.assignmentSolved(lesson, "test");
|
||||
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
Assertions.assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAndLoadShouldHaveCorrectNumberOfAttemtps() {
|
||||
UserTracker userTracker = new UserTracker("test");
|
||||
TestLesson lesson = new TestLesson();
|
||||
userTracker.getLessonTracker(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
||||
Assertions.assertThat(userTracker.getLessonTracker(lesson).getNumberOfAttempts()).isEqualTo(4);
|
||||
}
|
||||
|
||||
}
|
@ -1 +1,4 @@
|
||||
webgoat.user.directory=${java.io.tmpdir}
|
||||
|
||||
spring.datasource.url=jdbc:hsqldb:mem:test
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
35
webgoat-images/vagrant-training/Vagrantfile
vendored
Normal file
35
webgoat-images/vagrant-training/Vagrantfile
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Setup a Linux box headless which will start WebGoat and WebWolf helpful image to give away during training
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.network :forwarded_port, guest: 8080, host: 8080
|
||||
config.vm.network :forwarded_port, guest: 8081, host: 8081
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.gui = false
|
||||
vb.memory = "4096"
|
||||
vb.cpus = 2
|
||||
vb.name = "WebGoat-Training"
|
||||
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
|
||||
end
|
||||
config.vm.provider "vmware_fusion" do |vf|
|
||||
vf.gui = false
|
||||
vf.vmx["memsize"] = 4096
|
||||
vf.vmx["numvcpus"] = 2
|
||||
vf.vmx["displayname"] = "WebGoat-Training"
|
||||
end
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
wget https://github.com/WebGoat/WebGoat/releases/download/v8.0.0.M5/webgoat-server-8.0.0.M6.jar
|
||||
wget https://github.com/WebGoat/WebGoat/releases/download/v8.0.0.M5/webwolf-8.0.0.M6.jar
|
||||
sudo add-apt-repository ppa:openjdk-r/ppa
|
||||
sudo apt-get update
|
||||
sudo apt-get install openjdk-8-jre -y
|
||||
SHELL
|
||||
|
||||
config.vm.provision "shell", run: "always", privileged: false, inline: <<-SHELL
|
||||
java -jar webgoat-server-8.0.0.M6.jar &
|
||||
sleep 40s
|
||||
java -jar webwolf-8.0.0.M6.jar
|
||||
SHELL
|
||||
|
||||
end
|
@ -0,0 +1,88 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.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 javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 11/17/17.
|
||||
*/
|
||||
@AssignmentPath("/csrf/feedback")
|
||||
@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 = "/message", produces = {"application/json"})
|
||||
@ResponseBody
|
||||
public AttackResult completed(HttpServletRequest request, @RequestBody String feedback) {
|
||||
try {
|
||||
objectMapper.readValue(feedback.getBytes(), Map.class);
|
||||
} catch (IOException e) {
|
||||
return failed().feedback(ExceptionUtils.getStackTrace(e)).build();
|
||||
}
|
||||
boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().equals(MediaType.TEXT_PLAIN_VALUE);
|
||||
correctCSRF &= hostOrRefererDifferentHost(request);
|
||||
if (correctCSRF) {
|
||||
String flag = UUID.randomUUID().toString();
|
||||
userSessionData.setValue("csrf-feedback", flag);
|
||||
return success().feedback("csrf-feedback-success").feedbackArgs(flag).build();
|
||||
}
|
||||
return failed().build();
|
||||
}
|
||||
|
||||
@PostMapping(produces = "application/json")
|
||||
@ResponseBody
|
||||
public AttackResult flag(@RequestParam("confirmFlagVal") String flag) {
|
||||
if (flag.equals(userSessionData.getValue("csrf-feedback"))) {
|
||||
return trackProgress(success().build());
|
||||
} else {
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hostOrRefererDifferentHost(HttpServletRequest request) {
|
||||
String referer = request.getHeader("referer");
|
||||
String host = request.getHeader("host");
|
||||
return !StringUtils.contains(referer, host);
|
||||
}
|
||||
|
||||
private boolean requestContainsWebGoatCookie(Cookie[] cookies) {
|
||||
if (cookies != null) {
|
||||
for (Cookie c : cookies) {
|
||||
if (c.getName().equals("JSESSIONID")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Solution
|
||||
<form name="attack" enctype="text/plain" action="http://localhost:8080/WebGoat/csrf/feedback/message" METHOD="POST">
|
||||
<input type="hidden" name='{"name": "Test", "email": "test1233@dfssdf.de", "subject": "service", "message":"dsaffd"}'>
|
||||
</form>
|
||||
<script>document.attack.submit();</script>
|
||||
*/
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.users.UserTracker;
|
||||
import org.owasp.webgoat.users.UserTrackerRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 11/17/17.
|
||||
*/
|
||||
@AssignmentPath("/csrf/login")
|
||||
@AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"})
|
||||
public class CSRFLogin extends AssignmentEndpoint {
|
||||
|
||||
@Autowired
|
||||
private UserTrackerRepository userTrackerRepository;
|
||||
|
||||
@PostMapping(produces = {"application/json"})
|
||||
@ResponseBody
|
||||
public AttackResult completed() {
|
||||
String userName = getWebSession().getUserName();
|
||||
if (userName.startsWith("csrf")) {
|
||||
markAssignmentSolvedWithRealUser(userName.substring("csrf-".length()));
|
||||
return trackProgress(success().feedback("csrf-login-success").build());
|
||||
}
|
||||
return trackProgress(failed().feedback("csrf-login-failed").feedbackArgs(userName).build());
|
||||
}
|
||||
|
||||
private void markAssignmentSolvedWithRealUser(String username) {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(username);
|
||||
userTracker.assignmentSolved(getWebSession().getCurrentLesson(), this.getClass().getSimpleName());
|
||||
userTrackerRepository.save(userTracker);
|
||||
}
|
||||
}
|
@ -2,15 +2,15 @@
|
||||
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_intro.adoc"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_GET.adoc"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Get_Flag.adoc"></div>
|
||||
|
||||
<form accept-charset="UNKNOWN" id="basic-csrf-get"
|
||||
@ -18,8 +18,8 @@
|
||||
successCallback=""
|
||||
action="/WebGoat/csrf/basic-get-flag"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
<input name="csrf" type="hidden" value="false" />
|
||||
<input type="submit" name="ubmit=" />
|
||||
<input name="csrf" type="hidden" value="false"/>
|
||||
<input type="submit" name="ubmit="/>
|
||||
|
||||
</form>
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
Confirm Flag Value:
|
||||
<input type="text" length="6" name="confirmFlagVal" value="" />
|
||||
<input type="text" length="6" name="confirmFlagVal" value=""/>
|
||||
|
||||
<input name="submit" value="Submit" type="submit"/>
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="lesson-page-wrapper">
|
||||
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Reviews.adoc"></div>
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
|
||||
<div class="attack-container">
|
||||
<div class="container-fluid">
|
||||
<div class="panel post">
|
||||
<div class="post-heading">
|
||||
@ -89,9 +90,10 @@
|
||||
method="POST" name="review-form"
|
||||
successCallback=""
|
||||
action="/WebGoat/csrf/review">
|
||||
<input class="form-control" id="reviewText" name="reviewText" placeholder="Add a Review" type="text"/>
|
||||
<input class="form-control" id="reviewStars" name="stars" type="text" />
|
||||
<input type="hidden" name="validateReq" value="2aa14227b9a13d0bede0388a7fba9aa9" />
|
||||
<input class="form-control" id="reviewText" name="reviewText" placeholder="Add a Review"
|
||||
type="text"/>
|
||||
<input class="form-control" id="reviewStars" name="stars" type="text"/>
|
||||
<input type="hidden" name="validateReq" value="2aa14227b9a13d0bede0388a7fba9aa9"/>
|
||||
<input type="submit" name="submit" value="Submit review"/>
|
||||
</form>
|
||||
<div class="attack-feedback"></div>
|
||||
@ -108,14 +110,144 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end comments -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Frameworks.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_JSON.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_ContentType.adoc"></div>
|
||||
|
||||
<script th:src="@{/lesson_js/feedback.js}" language="JavaScript"></script>
|
||||
<div style="container-fluid; background-color: #f1f1f1; border: 2px solid #a66;
|
||||
border-radius: 12px;
|
||||
padding: 7px;
|
||||
margin-top:7px;
|
||||
padding:5px;">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="well well-sm">
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="csrf-feedback"
|
||||
method="POST"
|
||||
prepareData="feedback"
|
||||
action="/WebGoat/csrf/feedback/message"
|
||||
contentType="application/json">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Name</label>
|
||||
<input type="text" class="form-control" name="name" id="name"
|
||||
placeholder="Enter name"
|
||||
required="required"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">
|
||||
Email Address</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-envelope"></span>
|
||||
</span>
|
||||
<input type="email" name="email" class="form-control" id="email"
|
||||
placeholder="Enter email"
|
||||
required="required"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject">
|
||||
Subject</label>
|
||||
<select id="subject" name="subject" class="form-control" required="required">
|
||||
<option value="na" selected="">Choose One:</option>
|
||||
<option value="service">General Customer Service</option>
|
||||
<option value="suggestions">Suggestions</option>
|
||||
<option value="product">Product Support</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Message</label>
|
||||
<textarea name="message" id="message" class="form-control" rows="9" cols="25"
|
||||
required="required"
|
||||
placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary pull-right" id="btnContactUs">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Impact_Defense.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success">
|
||||
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
|
||||
</i>
|
||||
</div>
|
||||
<!--</div>-->
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-feedback"
|
||||
method="POST" name="form2"
|
||||
action="/WebGoat/csrf/feedback"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
Confirm Flag Value:
|
||||
<input type="text" length="6" name="confirmFlagVal" value=""/>
|
||||
|
||||
<input name="submit" value="Submit" type="submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Login.adoc"></div>
|
||||
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success">
|
||||
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
|
||||
</i>
|
||||
</div>
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-login"
|
||||
method="POST" name="form2"
|
||||
action="/WebGoat/csrf/login"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
Press the button below when your are logged in as the other user<br/>
|
||||
|
||||
<input name="submit" value="Solved!" type="submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Impact_Defense.adoc"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--</div>-->
|
||||
|
||||
</html>
|
@ -17,3 +17,16 @@ csrf-review.success=It appears you have submitted correctly from another site. G
|
||||
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}
|
BIN
webgoat-lessons/csrf/src/main/resources/images/login-csrf.png
Normal file
BIN
webgoat-lessons/csrf/src/main/resources/images/login-csrf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 KiB |
7
webgoat-lessons/csrf/src/main/resources/js/feedback.js
Normal file
7
webgoat-lessons/csrf/src/main/resources/js/feedback.js
Normal file
@ -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);
|
||||
}
|
@ -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 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.
|
@ -0,0 +1,22 @@
|
||||
=== 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.
|
||||
|
||||
Another effective 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.
|
||||
|
||||
|
||||
|
||||
|
@ -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 preflight 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.
|
||||
|
||||
[qoute, 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.
|
@ -0,0 +1,24 @@
|
||||
: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. First 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`
|
||||
|
@ -1,9 +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 inititate the submission elsewhere as you might
|
||||
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 keepoing it simple here. In this case, you just need to trigger a review submission on behalf of the currently
|
||||
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.
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.hamcrest.core.StringContains;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.owasp.webgoat.plugins.LessonTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 11/17/17.
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class CSRFFeedbackTest extends LessonTest {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
CSRF csrf = new CSRF();
|
||||
when(webSession.getCurrentLesson()).thenReturn(csrf);
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
when(webSession.getUserName()).thenReturn("unit-test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postingJsonMessageThroughWebGoatShouldWork() throws Exception {
|
||||
mockMvc.perform(post("/csrf/feedback/message")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void csrfAttack() throws Exception {
|
||||
mockMvc.perform(post("/csrf/feedback/message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.cookie(new Cookie("JSESSIONID", "test"))
|
||||
.header("host", "localhost:8080")
|
||||
.header("referer", "webgoat.org")
|
||||
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
|
||||
.andExpect(jsonPath("lessonCompleted", is(true)))
|
||||
.andExpect(jsonPath("feedback", StringContains.containsString("the flag is: ")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -43,34 +43,13 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
<type>jar</type>
|
||||
<!-- Exclude Mongo embedded so testcases do not start it automatically, seems to be
|
||||
the easiest way to stop the autoconfiguration of Spring Boot -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>org.apache.commons</groupId>-->
|
||||
<!--<artifactId>commons-exec</artifactId>-->
|
||||
<!--<version>1.3</version>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-container</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
<!-- Exclude Mongo embedded so testcases do not start it automatically, seems to be
|
||||
the easiest way to stop the autoconfiguration of Spring Boot -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
@ -96,12 +75,6 @@
|
||||
<version>4.1.3.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.fakemongo</groupId>
|
||||
<artifactId>fongo</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.owasp.encoder</groupId>
|
||||
<artifactId>encoder</artifactId>
|
||||
|
@ -19,8 +19,10 @@ import javax.xml.stream.XMLStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
@ -54,7 +56,7 @@ public class Comments {
|
||||
allComments.addAll(xmlComments);
|
||||
}
|
||||
allComments.addAll(comments);
|
||||
return allComments;
|
||||
return allComments.stream().sorted(Comparator.comparing(Comment::getDateTime).reversed()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected Comment parseXml(String xml) throws Exception {
|
||||
|
@ -26,6 +26,7 @@
|
||||
method="POST" name="form"
|
||||
prepareData="simpleXXE"
|
||||
successCallback="simpleXXECallback"
|
||||
failureCallback="simpleXXECallback"
|
||||
contentType="application/xml"
|
||||
action="/WebGoat/xxe/simple">
|
||||
<div class="container-fluid">
|
||||
@ -82,6 +83,7 @@
|
||||
method="POST" name="form"
|
||||
prepareData="contentTypeXXE"
|
||||
successCallback="contentTypeXXECallback"
|
||||
failureCallback="contentTypeXXECallback"
|
||||
action="xxe/content-type"
|
||||
contentType="application/json">
|
||||
<div class="container-fluid">
|
||||
@ -147,6 +149,7 @@
|
||||
method="POST" name="form"
|
||||
prepareData="blindXXE"
|
||||
successCallback="blindXXECallback"
|
||||
failureCallback="blindXXECallback"
|
||||
action="/WebGoat/xxe/blind"
|
||||
contentType="application/xml">
|
||||
<div class="container-fluid">
|
||||
|
@ -8,7 +8,7 @@ webgoat.customjs.simpleXXE = function () {
|
||||
}
|
||||
|
||||
webgoat.customjs.simpleXXECallback = function() {
|
||||
$("#commentInputBlind").val('');
|
||||
$("#commentInputSimple").val('');
|
||||
getComments('#commentsListSimple');
|
||||
}
|
||||
|
||||
@ -16,6 +16,25 @@ $(document).ready(function () {
|
||||
getComments('#commentsListSimple');
|
||||
});
|
||||
|
||||
//// Content-type
|
||||
|
||||
webgoat.customjs.contentTypeXXE = function() {
|
||||
var commentInput = $("#commentInputContentType").val();
|
||||
return JSON.stringify({text: commentInput});
|
||||
}
|
||||
|
||||
webgoat.customjs.contentTypeXXECallback = function() {
|
||||
$("#commentInputContentType").val('');
|
||||
getComments('#commentsListContentType');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
getComments('#commentsListContentType');
|
||||
});
|
||||
|
||||
|
||||
//// Blind
|
||||
|
||||
webgoat.customjs.blindXXE = function() {
|
||||
var commentInput = $("#commentInputBlind").val();
|
||||
var xml = '<?xml version="1.0"?>' +
|
||||
@ -34,19 +53,7 @@ $(document).ready(function () {
|
||||
getComments('#commentsListBlind');
|
||||
});
|
||||
|
||||
webgoat.customjs.contentTypeXXE = function() {
|
||||
var commentInput = $("#commentInputContentType").val();
|
||||
return JSON.stringify({text: commentInput});
|
||||
}
|
||||
|
||||
webgoat.customjs.contentTypeXXECallback = function() {
|
||||
$("#commentInputContentType").val('');
|
||||
getComments('#commentsListContentType');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
getComments('#commentsListContentType');
|
||||
});
|
||||
|
||||
var html = '<li class="comment">' +
|
||||
'<div class="pull-left">' +
|
||||
|
@ -10,7 +10,5 @@ COPY start.sh /home/webgoat/start.sh
|
||||
RUN chmod +x /home/webgoat/start.sh
|
||||
|
||||
USER webgoat
|
||||
RUN mkdir -p /home/webgoat/.embedmongo/linux
|
||||
RUN curl -o /home/webgoat/.embedmongo/linux/mongodb-linux-x86_64-3.2.2.tgz https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.2.tgz
|
||||
RUN cd /home/webgoat/; mkdir -p .webgoat
|
||||
COPY target/webgoat-server-${webgoat_version}.jar /home/webgoat/webgoat.jar
|
||||
|
@ -90,11 +90,6 @@
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-container</artifactId>
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* If we run
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "webgoat.embedded.mongo", havingValue = "false")
|
||||
public class ExternalMongoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private MongoProperties properties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private MongoClientOptions options;
|
||||
|
||||
@Bean
|
||||
public MongodExecutable mongodExecutable() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(Environment env) throws Exception {
|
||||
MongoClient client = properties.createMongoClient(this.options, env);
|
||||
return new SimpleMongoDbFactory(client, properties.getDatabase());
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
WG_MONGO_PORT=27017
|
||||
WG_MONGO_HOST=mongo
|
||||
WG_MQ_HOST=activemq
|
||||
WG_MQ_PORT=61616
|
@ -1,146 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bootstrap the setup of WebGoat for developer use in Linux and Mac machines
|
||||
# This script will clone the necessary git repositories, call the maven goals
|
||||
# in the order the are needed and launch tomcat listening on localhost:8080
|
||||
# Happy hacking !
|
||||
|
||||
# Find out what is our terminal size
|
||||
COLS="$(tput cols)"
|
||||
if (( COLS <= 0 )) ; then
|
||||
COLS="${COLUMNS:-80}"
|
||||
fi
|
||||
|
||||
# Colors
|
||||
ESC_SEQ="\x1b["
|
||||
COL_RESET=$ESC_SEQ"39;49;00m"
|
||||
COL_RED=$ESC_SEQ"31;01m"
|
||||
COL_GREEN=$ESC_SEQ"32;01m"
|
||||
COL_YELLOW=$ESC_SEQ"33;01m"
|
||||
COL_BLUE=$ESC_SEQ"34;01m"
|
||||
COL_MAGENTA=$ESC_SEQ"35;01m"
|
||||
COL_CYAN=$ESC_SEQ"36;01m"
|
||||
|
||||
# Horizontal Rule function
|
||||
horizontal_rule() {
|
||||
local WORD
|
||||
|
||||
for WORD in "#"
|
||||
do
|
||||
hr "$WORD"
|
||||
done
|
||||
}
|
||||
|
||||
hr() {
|
||||
local WORD="$1"
|
||||
if [[ -n "$WORD" ]] ; then
|
||||
local LINE=''
|
||||
while (( ${#LINE} < COLS ))
|
||||
do
|
||||
LINE="$LINE$WORD"
|
||||
done
|
||||
|
||||
echo -e "${LINE:0:$COLS}"
|
||||
fi
|
||||
}
|
||||
|
||||
## test if command exists
|
||||
ftest() {
|
||||
echo -e "$COL_CYAN info: Checking if ${1} is installed $COL_RESET"
|
||||
if ! type "${1}" > /dev/null 2>&1; then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
## feature tests
|
||||
features() {
|
||||
for f in "${@}"; do
|
||||
ftest "${f}" || {
|
||||
echo -e >&2 "***$COL_RED ERROR: Missing \`${f}'! Make sure it exists and try again. $COL_RESET"
|
||||
return 1
|
||||
}
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
tomcat_started () {
|
||||
STAT=`netstat -na | grep 8080 | awk '{print $6}'`
|
||||
if [ "$STAT" = "LISTEN" ]; then
|
||||
echo -e "$COL_GREEN WebGoat has started successfully! Browse to the following address. $COL_RESET"
|
||||
echo -e "$COL_CYAN Happy Hacking! $COL_RESET"
|
||||
return 0
|
||||
|
||||
elif [ "$STAT" = "" ]; then
|
||||
echo -e "$COL_RED WebGoat failed to start up.... please wait run the following command for debugging : $COL_RESET"
|
||||
echo -e "$COL_MAGENTA mvn -q -file WebGoat/pom.xml -pl webgoat-container tomcat7:run-war"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
## main setup
|
||||
developer_bootstrap() {
|
||||
horizontal_rule
|
||||
echo -e "$COL_RED
|
||||
██╗ ██╗███████╗██████╗ ██████╗ ██████╗ █████╗ ████████╗
|
||||
██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔═══██╗██╔══██╗╚══██╔══╝
|
||||
██║ █╗ ██║█████╗ ██████╔╝██║ ███╗██║ ██║███████║ ██║
|
||||
██║███╗██║██╔══╝ ██╔══██╗██║ ██║██║ ██║██╔══██║ ██║
|
||||
╚███╔███╔╝███████╗██████╔╝╚██████╔╝╚██████╔╝██║ ██║ ██║
|
||||
╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
||||
$COL_RESET"
|
||||
horizontal_rule
|
||||
echo -e "Welcome to the WebGoat Developer Bootstrap script for Linux/Mac."
|
||||
echo -e "Now checking if all the required software to run WebGoat is already installed."
|
||||
echo -e "FYI: This Developer Bootstrap Script for WebGoat requires: Git, Java JDK and Maven accessible on the path"
|
||||
|
||||
## test for require features
|
||||
features git mvn java || return $?
|
||||
|
||||
# Clone WebGoat from github
|
||||
if [ ! -d "WebGoat" ]; then
|
||||
echo -e "Cloning the WebGoat container repository"
|
||||
git clone https://github.com/WebGoat/WebGoat.git
|
||||
else
|
||||
horizontal_rule
|
||||
(
|
||||
echo -e "$COL_YELLOW The WebGoat container repo has already been clonned before, pulling upstream changes. $COL_RESET"
|
||||
cd WebGoat || {
|
||||
echo -e >&2 "$COL_RED *** ERROR: Could not cd into the WebGoat Directory. $COL_RESET"
|
||||
return 1
|
||||
}
|
||||
git pull origin develop
|
||||
)
|
||||
fi
|
||||
|
||||
# Start the embedded Tomcat server
|
||||
echo -e "$COL_MAGENTA"
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
echo "$COL_MAGENTA"
|
||||
echo "$COL_CYAN ***** Starting WebGoat using the embedded Tomcat ***** $COL_RESET"
|
||||
echo " Please be patient.... The startup of the server takes about 5 seconds..."
|
||||
echo " WebGoat will be ready for you when you see the following message on the command prompt:"
|
||||
echo "$COL_YELLOW INFO: Starting ProtocolHandler ["http-bio-8080"] $COL_RESET"
|
||||
echo "$COL_CYAN When you see the message above, open a web browser and navigate to http://localhost:8080/WebGoat/ $COL_RESET"
|
||||
echo " To stop the WebGoat and Tomcat Execution execution, press CTRL + C"
|
||||
echo "$COL_RED If you close this terminal window, Tomcat and WebGoat will stop running $COL_RESET"
|
||||
echo "$COL_MAGENTA"
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
horizontal_rule
|
||||
echo -e "$COL_RESET"
|
||||
sleep 5
|
||||
|
||||
# Starting WebGoat
|
||||
mvn -q -pl webgoat-container spring-boot:run
|
||||
}
|
||||
|
||||
# Start main script
|
||||
developer_bootstrap
|
@ -55,7 +55,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -73,6 +73,11 @@
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>3.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>${hsqldb.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -4,10 +4,9 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -18,7 +17,7 @@ import java.time.format.DateTimeFormatter;
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@Document
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Email implements Serializable {
|
||||
@ -29,7 +28,6 @@ public class Email implements Serializable {
|
||||
private String contents;
|
||||
private String sender;
|
||||
private String title;
|
||||
@Indexed
|
||||
private String recipient;
|
||||
|
||||
public String getSummary() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.owasp.webwolf.mailbox;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -9,7 +8,7 @@ import java.util.List;
|
||||
* @author nbaars
|
||||
* @since 8/17/17.
|
||||
*/
|
||||
public interface MailboxRepository extends MongoRepository<Email, ObjectId> {
|
||||
public interface MailboxRepository extends JpaRepository<Email, String> {
|
||||
|
||||
List<Email> findByRecipientOrderByTimeDesc(String recipient);
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
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;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* @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")
|
||||
@SneakyThrows
|
||||
public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult, HttpServletRequest request) {
|
||||
userValidator.validate(userForm, bindingResult);
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
return "registration";
|
||||
}
|
||||
userService.addUser(userForm.getUsername(), userForm.getPassword());
|
||||
request.login(userForm.getUsername(), userForm.getPassword());
|
||||
|
||||
return "redirect:/WebWolf/home";
|
||||
}
|
||||
}
|
28
webwolf/src/main/java/org/owasp/webwolf/user/UserForm.java
Normal file
28
webwolf/src/main/java/org/owasp/webwolf/user/UserForm.java
Normal file
@ -0,0 +1,28 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserForm {
|
||||
|
||||
@NotNull
|
||||
@Size(min=6, max=20)
|
||||
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;
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
public interface UserRepository extends MongoRepository<WebGoatUser, String> {
|
||||
public interface UserRepository extends JpaRepository<WebGoatUser, String> {
|
||||
|
||||
WebGoatUser findByUsername(String username);
|
||||
}
|
||||
|
@ -27,4 +27,9 @@ public class UserService implements UserDetailsService {
|
||||
}
|
||||
|
||||
|
||||
public void addUser(String username, String password) {
|
||||
userRepository.save(new WebGoatUser(username, password));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
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<?> aClass) {
|
||||
return UserForm.class.equals(aClass);
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
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;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@ -16,6 +17,7 @@ import java.util.Collections;
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
@Getter
|
||||
@Entity
|
||||
public class WebGoatUser implements UserDetails {
|
||||
|
||||
public static final String ROLE_USER = "WEBGOAT_USER";
|
||||
|
@ -5,6 +5,10 @@ server.session.timeout=6000
|
||||
server.port=8081
|
||||
server.session.cookie.name = WEBWOLFSESSION
|
||||
|
||||
spring.datasource.url=jdbc:hsqldb:file:${webgoat.server.directory}/data/webwolf
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.messages.basename=i18n/messages
|
||||
|
||||
logging.level.org.springframework=INFO
|
||||
logging.level.org.springframework.boot.devtools=WARN
|
||||
logging.level.org.owasp=DEBUG
|
||||
@ -25,12 +29,9 @@ multipart.location=${java.io.tmpdir}
|
||||
multipart.max-file-size=1Mb
|
||||
multipart.max-request-size=1Mb
|
||||
|
||||
webgoat.server.directory=${user.home}/.webgoat/
|
||||
webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver
|
||||
|
||||
spring.data.mongodb.host=${WG_MONGO_HOST:}
|
||||
spring.data.mongodb.port=${WG_MONGO_PORT:27017}
|
||||
spring.data.mongodb.database=webgoat
|
||||
|
||||
spring.jackson.serialization.indent_output=true
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
|
||||
|
40
webwolf/src/main/resources/i18n/messages.properties
Normal file
40
webwolf/src/main/resources/i18n/messages.properties
Normal file
@ -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/
|
||||
# <p>
|
||||
# Copyright (c) 2002 - 2017 Bruce Mayhew
|
||||
# <p>
|
||||
# This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License as published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
# <p>
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
# <p>
|
||||
# You should have received a copy of the GNU General Public License along with this program; if
|
||||
# not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
# 02111-1307, USA.
|
||||
# <p>
|
||||
# Getting Source ==============
|
||||
# <p>
|
||||
# Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
|
||||
# projects.
|
||||
# <p>
|
||||
#
|
||||
|
||||
register.new=Register new user
|
||||
sign.up=Sign up
|
||||
register.title=Register
|
||||
|
||||
password=Password
|
||||
password.confirm=Confirm password
|
||||
username=Username
|
||||
|
||||
|
||||
|
||||
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.
|
@ -45,6 +45,7 @@
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
</div>
|
||||
</div>
|
||||
<div><b><a th:href="@{/registration}" th:text="#{register.new}"></a></b></div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
89
webwolf/src/main/resources/templates/registration.html
Normal file
89
webwolf/src/main/resources/templates/registration.html
Normal file
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<div th:replace="fragments/header :: header-css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="fragments/header :: header"/>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<br/><br/>
|
||||
<fieldset>
|
||||
<legend th:text="#{register.title}">Please Sign Up</legend>
|
||||
<form class="form-horizontal" action="#" th:action="@{/register.mvc}" th:object="${userForm}"
|
||||
method='POST'>
|
||||
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('username')}? 'has-error'">
|
||||
<label for="username" class="col-sm-2 control-label" th:text="#{username}">Username</label>
|
||||
<div class="col-sm-4">
|
||||
<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"
|
||||
th:field="*{username}"
|
||||
id="username" placeholder="Username" name='username'/>
|
||||
</div>
|
||||
<span th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Username error</span>
|
||||
</div>
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
|
||||
<label for="password" class="col-sm-2 control-label" th:text="#{password}">Password</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" id="password" placeholder="Password"
|
||||
name='password' th:value="*{password}"/>
|
||||
</div>
|
||||
<span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password error</span>
|
||||
</div>
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('matchingPassword')}? 'has-error'">
|
||||
<label for="matchingPassword" class="col-sm-2 control-label" th:text="#{password.confirm}">Confirm
|
||||
password</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" id="matchingPassword" placeholder="Password"
|
||||
name='matchingPassword' th:value="*{matchingPassword}"/>
|
||||
</div>
|
||||
<span th:if="${#fields.hasErrors('matchingPassword')}"
|
||||
th:errors="*{matchingPassword}">Password error</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('agree')}? 'has-error'">
|
||||
<label class="col-sm-2 control-label">Terms of use</label>
|
||||
<div class="col-sm-6">
|
||||
<div style="border: 1px solid #e5e5e5; height: 200px; overflow: auto; padding: 10px;">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('agree')}? 'has-error'">
|
||||
<div class="col-sm-6 col-sm-offset-2">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="agree" value="agree"/>Agree with the terms and
|
||||
conditions
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-6">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{sign.up}">Sign up</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user