Merge branch 'challenge' into develop

Conflicts:
	webgoat-container/src/main/resources/static/css/main.css
This commit is contained in:
Nanne Baars 2017-05-04 03:02:00 +02:00
commit 8d3c251d04
145 changed files with 3992 additions and 965 deletions

View File

@ -132,9 +132,11 @@
<commons-lang3.version>3.4</commons-lang3.version> <commons-lang3.version>3.4</commons-lang3.version>
<commons-logging.version>1.2</commons-logging.version> <commons-logging.version>1.2</commons-logging.version>
<coveralls-maven-plugin.version>4.0.0</coveralls-maven-plugin.version> <coveralls-maven-plugin.version>4.0.0</coveralls-maven-plugin.version>
<gatling.version>2.2.5</gatling.version>
<gatling-plugin.version>2.2.4</gatling-plugin.version>
<guava.version>18.0</guava.version> <guava.version>18.0</guava.version>
<h2.version>1.4.190</h2.version> <h2.version>1.4.190</h2.version>
<hsqldb.version>1.8.0.10</hsqldb.version> <hsqldb.version>2.3.2</hsqldb.version>
<j2h.version>1.3.1</j2h.version> <j2h.version>1.3.1</j2h.version>
<jackson-core.version>2.6.3</jackson-core.version> <jackson-core.version>2.6.3</jackson-core.version>
<jackson-databind.version>2.6.3</jackson-databind.version> <jackson-databind.version>2.6.3</jackson-databind.version>
@ -155,6 +157,7 @@
<maven-source-plugin.version>3.0.1</maven-source-plugin.version> <maven-source-plugin.version>3.0.1</maven-source-plugin.version>
<maven-surefire-plugin.version>2.19</maven-surefire-plugin.version> <maven-surefire-plugin.version>2.19</maven-surefire-plugin.version>
<nexus-staging-maven-plugin.version>1.6.6</nexus-staging-maven-plugin.version> <nexus-staging-maven-plugin.version>1.6.6</nexus-staging-maven-plugin.version>
<scala.version>2.11.7</scala.version>
<sauce_junit.version>2.1.20</sauce_junit.version> <sauce_junit.version>2.1.20</sauce_junit.version>
<selenium-java.version>2.48.2</selenium-java.version> <selenium-java.version>2.48.2</selenium-java.version>
<slf4j-api.version>1.7.12</slf4j-api.version> <slf4j-api.version>1.7.12</slf4j-api.version>

View File

@ -14,6 +14,29 @@
</parent> </parent>
<profiles>
<profile>
<id>performance</id>
<build>
<plugins>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>execute</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build> <build>
<resources> <resources>
<resource> <resource>
@ -117,15 +140,24 @@
<version>1.5.4</version> <version>1.5.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>3.4.1</version> </dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version> <version>${commons-lang3.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>${gatling.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId> <artifactId>jstl</artifactId>
@ -162,7 +194,7 @@
<version>${h2.version}</version> <version>${h2.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>hsqldb</groupId> <groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version> <version>${hsqldb.version}</version>
</dependency> </dependency>
@ -171,6 +203,11 @@
<artifactId>javax.transaction-api</artifactId> <artifactId>javax.transaction-api</artifactId>
<version>${javax.transaction-api.version}</version> <version>${javax.transaction-api.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
<!-- Apache Commons Upload --> <!-- Apache Commons Upload -->
<dependency> <dependency>
@ -186,7 +223,6 @@
</dependency> </dependency>
<!-- ************* END spring MVC and related dependencies ************** --> <!-- ************* END spring MVC and related dependencies ************** -->
<!-- ************* START: Dependencies for Unit and Integration Testing ************** --> <!-- ************* START: Dependencies for Unit and Integration Testing ************** -->
<dependency> <dependency>

View File

@ -0,0 +1,36 @@
package org.owasp.webgoat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
/**
* @author nbaars
* @since 4/15/17.
*/
@Slf4j
@Configuration
@ConditionalOnExpression("'${webgoat.clean}' == 'true'")
public class CleanupLocalProgressFiles {
@Value("${webgoat.server.directory}")
private String webgoatHome;
@PostConstruct
public void clean() {
File dir = new File(webgoatHome);
if (dir.exists()) {
File[] progressFiles = dir.listFiles(f -> f.getName().endsWith(".progress"));
if (progressFiles != null) {
log.info("Removing stored user preferences...");
for (File f : progressFiles) {
f.delete();
}
}
}
}
}

View File

@ -71,6 +71,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
registry.addViewController("/login").setViewName("login"); registry.addViewController("/login").setViewName("login");
registry.addViewController("/lesson_content").setViewName("lesson_content"); registry.addViewController("/lesson_content").setViewName("lesson_content");
registry.addViewController("/start.mvc").setViewName("main_new"); registry.addViewController("/start.mvc").setViewName("main_new");
registry.addViewController("/scoreboard").setViewName("scoreboard");
} }

View File

@ -31,12 +31,14 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.owasp.webgoat.plugins.PluginEndpointPublisher; import org.owasp.webgoat.plugins.PluginEndpointPublisher;
import org.owasp.webgoat.plugins.PluginsLoader; import org.owasp.webgoat.plugins.PluginsLoader;
import org.owasp.webgoat.session.*; import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.UserSessionData;
import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.session.WebgoatContext;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -104,15 +106,6 @@ public class WebGoat extends SpringBootServletInitializer {
return new PluginsLoader(pluginEndpointPublisher).loadPlugins(); return new PluginsLoader(pluginEndpointPublisher).loadPlugins();
} }
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
@SneakyThrows
public UserTracker userTracker(@Value("${webgoat.user.directory}") final String webgoatHome, WebSession webSession) {
UserTracker userTracker = new UserTracker(webgoatHome, webSession.getUserName());
userTracker.load();
return userTracker;
}
@Bean @Bean
public EmbeddedServletContainerFactory servletContainer() { public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
@ -127,4 +120,5 @@ public class WebGoat extends SpringBootServletInitializer {
} }
} }
} }

View File

@ -27,7 +27,8 @@ package org.owasp.webgoat.assignments;
import lombok.Getter; import lombok.Getter;
import org.owasp.webgoat.i18n.PluginMessages; import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.UserSessionData; import org.owasp.webgoat.session.UserSessionData;
import org.owasp.webgoat.session.UserTracker; import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -43,7 +44,7 @@ import org.springframework.beans.factory.annotation.Autowired;
public abstract class AssignmentEndpoint extends Endpoint { public abstract class AssignmentEndpoint extends Endpoint {
@Autowired @Autowired
private UserTracker userTracker; private UserTrackerRepository userTrackerRepository;
@Autowired @Autowired
private WebSession webSession; private WebSession webSession;
@Autowired @Autowired
@ -54,11 +55,16 @@ public abstract class AssignmentEndpoint extends Endpoint {
//// TODO: 11/13/2016 events better fit? //// TODO: 11/13/2016 events better fit?
protected AttackResult trackProgress(AttackResult attackResult) { protected AttackResult trackProgress(AttackResult attackResult) {
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
if (userTracker == null) {
userTracker = new UserTracker(webSession.getUserName());
}
if (attackResult.assignmentSolved()) { if (attackResult.assignmentSolved()) {
userTracker.assignmentSolved(webSession.getCurrentLesson(), this.getClass().getSimpleName()); userTracker.assignmentSolved(webSession.getCurrentLesson(), this.getClass().getSimpleName());
} else { } else {
userTracker.assignmentFailed(webSession.getCurrentLesson()); userTracker.assignmentFailed(webSession.getCurrentLesson());
} }
userTrackerRepository.save(userTracker);
return attackResult; return attackResult;
} }

View File

@ -51,6 +51,12 @@ public class AttackResult {
return this; return this;
} }
public AttackResultBuilder lessonCompleted(boolean lessonCompleted, String resourceBundleKey) {
this.lessonCompleted = lessonCompleted;
this.feedbackResourceBundleKey = resourceBundleKey;
return this;
}
public AttackResultBuilder feedbackArgs(Object... args) { public AttackResultBuilder feedbackArgs(Object... args) {
this.feedbackArgs = args; this.feedbackArgs = args;
return this; return this;

View File

@ -1,11 +1,7 @@
package org.owasp.webgoat.lessons; package org.owasp.webgoat.lessons;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.util.List; import java.util.List;
/** /**
@ -39,14 +35,14 @@ import java.util.List;
*/ */
@AllArgsConstructor @AllArgsConstructor
@RequiredArgsConstructor @RequiredArgsConstructor
@NoArgsConstructor
@Getter @Getter
public class Assignment implements Serializable { @EqualsAndHashCode
public class Assignment {
private static final long serialVersionUID = 5410058267505412928L;
@NonNull @NonNull
private final String name; private String name;
@NonNull @NonNull
private final String path; private String path;
private List<String> hints; private List<String> hints;
} }

View File

@ -57,7 +57,7 @@ public enum Category {
WEB_SERVICES("Web Services", new Integer(1900)), WEB_SERVICES("Web Services", new Integer(1900)),
VULNERABLE_COMPONENTS("Vulnerable Components - A9", new Integer(1950)), VULNERABLE_COMPONENTS("Vulnerable Components - A9", new Integer(1950)),
ADMIN_FUNCTIONS("Admin Functions", new Integer(2000)), ADMIN_FUNCTIONS("Admin Functions", new Integer(2000)),
CHALLENGE("Challenge", new Integer(3000)); CHALLENGE("Challenges", new Integer(3000));
@Getter @Getter
private String name; private String name;

View File

@ -8,7 +8,6 @@ import org.owasp.webgoat.lessons.NewLesson;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -24,21 +23,22 @@ public class PluginResource {
private final URL location; private final URL location;
private final List<Class> classes; private final List<Class> classes;
public Optional<Class> getLesson() { public List<Class> getLessons() {
return classes.stream().filter(c -> c.getSuperclass() == NewLesson.class).findFirst(); return classes.stream().filter(c -> c.getSuperclass() == NewLesson.class).collect(Collectors.toList());
} }
public List<Class<Endpoint>> getEndpoints() { public List<Class<Endpoint>> getEndpoints() {
return classes.stream(). return classes.stream().
filter(c -> c.getSuperclass() == AssignmentEndpoint.class || c.getSuperclass() == Endpoint.class). filter(c -> c.getSuperclass() == AssignmentEndpoint.class || c.getSuperclass() == Endpoint.class).
map(c -> (Class<Endpoint>)c). map(c -> (Class<Endpoint>) c).
collect(Collectors.toList()); collect(Collectors.toList());
} }
public List<Class<AssignmentEndpoint>> getAssignments() { public List<Class<AssignmentEndpoint>> getAssignments(Class lesson) {
return classes.stream(). return classes.stream().
filter(c -> c.getSuperclass() == AssignmentEndpoint.class). filter(c -> c.getSuperclass() == AssignmentEndpoint.class).
map(c -> (Class<AssignmentEndpoint>)c). filter(c -> c.getPackage().equals(lesson.getPackage())).
map(c -> (Class<AssignmentEndpoint>) c).
collect(Collectors.toList()); collect(Collectors.toList());
} }

View File

@ -67,13 +67,18 @@ public class PluginsLoader {
List<AbstractLesson> lessons = Lists.newArrayList(); List<AbstractLesson> lessons = Lists.newArrayList();
for (PluginResource plugin : findPluginResources()) { for (PluginResource plugin : findPluginResources()) {
try { try {
Class lessonClazz = plugin.getLesson() plugin.getLessons().forEach(c -> {
.orElseThrow(() -> new PluginLoadingFailure("Plugin resource does not contain lesson")); NewLesson lesson = null;
NewLesson lesson = (NewLesson) lessonClazz.newInstance(); try {
List<Class<AssignmentEndpoint>> assignments = plugin.getAssignments(); lesson = (NewLesson) c.newInstance();
lesson.setAssignments(createAssignment(assignments)); } catch (Exception e) {
lessons.add(lesson); log.error("Error while loading:" + c, e);
pluginEndpointPublisher.publish(plugin.getEndpoints()); }
List<Class<AssignmentEndpoint>> assignments = plugin.getAssignments(c);
lesson.setAssignments(createAssignment(assignments));
lessons.add(lesson);
pluginEndpointPublisher.publish(plugin.getEndpoints());
});
} catch (Exception e) { } catch (Exception e) {
log.error("Error in loadLessons: ", e); log.error("Error in loadLessons: ", e);
} }

View File

@ -34,15 +34,18 @@ import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.LessonMenuItem; import org.owasp.webgoat.lessons.LessonMenuItem;
import org.owasp.webgoat.lessons.LessonMenuItemType; import org.owasp.webgoat.lessons.LessonMenuItemType;
import org.owasp.webgoat.session.Course; import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* <p>LessonMenuService class.</p> * <p>LessonMenuService class.</p>
@ -54,20 +57,23 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
public class LessonMenuService { public class LessonMenuService {
public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc";
private final Course course; private final Course course;
private UserTracker userTracker; private final WebSession webSession;
private UserTrackerRepository userTrackerRepository;
/** /**
* Returns the lesson menu which is used to build the left nav * Returns the lesson menu which is used to build the left nav
* *
* @return a {@link java.util.List} object. * @return a {@link java.util.List} object.
*/ */
@RequestMapping(path = "/service/lessonmenu.mvc", produces = "application/json") @RequestMapping(path = URL_LESSONMENU_MVC, produces = "application/json")
public public
@ResponseBody @ResponseBody
List<LessonMenuItem> showLeftNav() { List<LessonMenuItem> showLeftNav() {
List<LessonMenuItem> menu = new ArrayList<LessonMenuItem>(); List<LessonMenuItem> menu = new ArrayList<>();
List<Category> categories = course.getCategories(); List<Category> categories = course.getCategories();
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
for (Category category : categories) { for (Category category : categories) {
LessonMenuItem categoryItem = new LessonMenuItem(); LessonMenuItem categoryItem = new LessonMenuItem();
@ -75,6 +81,7 @@ public class LessonMenuService {
categoryItem.setType(LessonMenuItemType.CATEGORY); categoryItem.setType(LessonMenuItemType.CATEGORY);
// check for any lessons for this category // check for any lessons for this category
List<AbstractLesson> lessons = course.getLessons(category); List<AbstractLesson> lessons = course.getLessons(category);
lessons = lessons.stream().sorted(Comparator.comparing(l -> l.getTitle())).collect(Collectors.toList());
for (AbstractLesson lesson : lessons) { for (AbstractLesson lesson : lessons) {
LessonMenuItem lessonItem = new LessonMenuItem(); LessonMenuItem lessonItem = new LessonMenuItem();
lessonItem.setName(lesson.getTitle()); lessonItem.setName(lesson.getTitle());

View File

@ -7,9 +7,10 @@ import lombok.Getter;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment; import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.lessons.LessonInfoModel; import org.owasp.webgoat.lessons.LessonInfoModel;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@ -28,7 +29,7 @@ import java.util.Map;
@AllArgsConstructor @AllArgsConstructor
public class LessonProgressService { public class LessonProgressService {
private UserTracker userTracker; private UserTrackerRepository userTrackerRepository;
private WebSession webSession; private WebSession webSession;
/** /**
@ -39,6 +40,7 @@ public class LessonProgressService {
@RequestMapping(value = "/service/lessonprogress.mvc", produces = "application/json") @RequestMapping(value = "/service/lessonprogress.mvc", produces = "application/json")
@ResponseBody @ResponseBody
public Map getLessonInfo() { public Map getLessonInfo() {
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
LessonTracker lessonTracker = userTracker.getLessonTracker(webSession.getCurrentLesson()); LessonTracker lessonTracker = userTracker.getLessonTracker(webSession.getCurrentLesson());
Map json = Maps.newHashMap(); Map json = Maps.newHashMap();
String successMessage = ""; String successMessage = "";
@ -61,6 +63,7 @@ public class LessonProgressService {
@RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json") @RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
@ResponseBody @ResponseBody
public List<LessonOverview> lessonOverview() { public List<LessonOverview> lessonOverview() {
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
AbstractLesson currentLesson = webSession.getCurrentLesson(); AbstractLesson currentLesson = webSession.getCurrentLesson();
List<LessonOverview> result = Lists.newArrayList(); List<LessonOverview> result = Lists.newArrayList();
if ( currentLesson != null ) { if ( currentLesson != null ) {

View File

@ -29,25 +29,20 @@
package org.owasp.webgoat.service; package org.owasp.webgoat.service;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.Singular;
import org.apache.catalina.User;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.Course; import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* <p>ReportCardService</p> * <p>ReportCardService</p>
@ -56,22 +51,20 @@ import java.util.Map;
* @version $Id: $Id * @version $Id: $Id
*/ */
@Controller @Controller
@AllArgsConstructor
public class ReportCardService { public class ReportCardService {
private final UserTracker userTracker; private final WebSession webSession;
private final UserTrackerRepository userTrackerRepository;
private final Course course; private final Course course;
public ReportCardService(UserTracker userTracker, Course course) {
this.userTracker = userTracker;
this.course = course;
}
/** /**
* Endpoint which generates the report card for the current use to show the stats on the solved lessons * Endpoint which generates the report card for the current use to show the stats on the solved lessons
*/ */
@GetMapping(path = "/service/reportcard.mvc", produces = "application/json") @GetMapping(path = "/service/reportcard.mvc", produces = "application/json")
@ResponseBody @ResponseBody
public ReportCard reportCard() { public ReportCard reportCard() {
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
List<AbstractLesson> lessons = course.getLessons(); List<AbstractLesson> lessons = course.getLessons();
ReportCard reportCard = new ReportCard(); ReportCard reportCard = new ReportCard();
reportCard.setTotalNumberOfLessons(course.getTotalOfLessons()); reportCard.setTotalNumberOfLessons(course.getTotalOfLessons());

View File

@ -26,8 +26,9 @@ package org.owasp.webgoat.service;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -45,7 +46,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
public class RestartLessonService { public class RestartLessonService {
private final WebSession webSession; private final WebSession webSession;
private final UserTracker userTracker; private UserTrackerRepository userTrackerRepository;
/** /**
* Returns current lesson * Returns current lesson
@ -58,6 +59,8 @@ public class RestartLessonService {
AbstractLesson al = webSession.getCurrentLesson(); AbstractLesson al = webSession.getCurrentLesson();
log.debug("Restarting lesson: " + al); log.debug("Restarting lesson: " + al);
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
userTracker.reset(al); userTracker.reset(al);
userTrackerRepository.save(userTracker);
} }
} }

View File

@ -7,8 +7,6 @@ import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
/** /**
************************************************************************************************* *************************************************************************************************
@ -39,6 +37,8 @@ import org.springframework.beans.factory.annotation.Autowired;
* @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a> * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
* @version $Id: $Id * @version $Id: $Id
*/ */
//TODO: class we need to refactor to new structure, we can put the connection in the current session of the user
// start using jdbc template
public class DatabaseUtilities public class DatabaseUtilities
{ {
@ -122,7 +122,7 @@ public class DatabaseUtilities
private static Connection getHsqldbConnection(String user, WebgoatContext context) throws ClassNotFoundException, private static Connection getHsqldbConnection(String user, WebgoatContext context) throws ClassNotFoundException,
SQLException SQLException
{ {
String url = context.getDatabaseConnectionString().replaceAll("\\$\\{USER\\}", user); String url = context.getDatabaseConnectionString().replace("{USER}", user);
return DriverManager.getConnection(url, "sa", ""); return DriverManager.getConnection(url, "sa", "");
} }

View File

@ -1,181 +0,0 @@
package org.owasp.webgoat.session;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
class UserDatabase {
private Connection userDB;
private final String USER_DB_URI = "jdbc:h2:" + System.getProperty("user.dir") + File.separator + "UserDatabase";
private final String CREATE_USERS_TABLE = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255) NOT NULL UNIQUE);";
private final String CREATE_ROLES_TABLE = "CREATE TABLE IF NOT EXISTS roles (id INTEGER PRIMARY KEY AUTO_INCREMENT, rolename VARCHAR(255) NOT NULL UNIQUE);";
private final String CREATE_USER_ROLES_TABLE = "CREATE TABLE IF NOT EXISTS user_roles (id INTEGER PRIMARY KEY AUTO_INCREMENT, user_id INTEGER NOT NULL, role_id INTEGER NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id));";
private final String ADD_DEFAULT_USERS = "INSERT INTO users (username) VALUES ('webgoat'),('basic'),('guest');";
private final String ADD_DEFAULT_ROLES = "INSERT INTO roles (rolename) VALUES ('webgoat_basic'),('webgoat_admin'),('webgoat_user');";
private final String ADD_ROLE_TO_USER = "INSERT INTO user_roles (user_id, role_id) SELECT users.id, roles.id FROM users, roles WHERE users.username = ? AND roles.rolename = ?;";
private final String QUERY_ALL_USERS = "SELECT username FROM users;";
private final String QUERY_ALL_ROLES_FOR_USERNAME = "SELECT rolename FROM roles, user_roles, users WHERE roles.id = user_roles.role_id AND user_roles.user_id = users.id AND users.username = ?;";
private final String QUERY_TABLE_COUNT = "SELECT count(id) AS count FROM table;";
/**
* <p>Constructor for UserDatabase.</p>
*/
public UserDatabase() {
createDefaultTables();
if (getTableCount("users") <= 0) {
createDefaultUsers();
}
if (getTableCount("roles") <= 0) {
createDefaultRoles();
}
if (getTableCount("user_roles") <= 0) {
addDefaultRolesToDefaultUsers();
}
}
/**
* <p>open.</p>
*
* @return a boolean.
*/
public boolean open() {
try {
if (userDB == null || userDB.isClosed()) {
Class.forName("org.h2.Driver");
userDB = DriverManager.getConnection(USER_DB_URI, "webgoat_admin", "");
}
} catch (SQLException e) {
e.printStackTrace();
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* <p>close.</p>
*
* @return a boolean.
*/
public boolean close() {
try {
if (userDB != null && !userDB.isClosed())
userDB.close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* <p>getTableCount.</p>
*
* @param tableName a {@link java.lang.String} object.
* @return a int.
*/
public int getTableCount(String tableName) {
int count = 0;
try {
open();
Statement statement = userDB.createStatement();
ResultSet countResult = statement.executeQuery(QUERY_TABLE_COUNT.replace("table", tableName));
if (countResult.next()) {
count = countResult.getInt("count");
}
countResult.close();
statement.close();
close();
} catch (SQLException e) {
e.printStackTrace();
count = -1;
}
return count;
}
/**
* <p>addRoleToUser.</p>
*
* @param username a {@link java.lang.String} object.
* @param rolename a {@link java.lang.String} object.
* @return a boolean.
*/
public boolean addRoleToUser(String username, String rolename) {
try {
open();
PreparedStatement statement = userDB.prepareStatement(ADD_ROLE_TO_USER);
statement.setString(1, username);
statement.setString(2, rolename);
statement.execute();
statement.close();
close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
/*
* Methods to initialise the default state of the database.
*/
private boolean createDefaultTables() {
try {
open();
Statement statement = userDB.createStatement();
statement.execute(CREATE_USERS_TABLE);
statement.execute(CREATE_ROLES_TABLE);
statement.execute(CREATE_USER_ROLES_TABLE);
statement.close();
close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean createDefaultUsers() {
try {
open();
Statement statement = userDB.createStatement();
statement.execute(ADD_DEFAULT_USERS);
statement.close();
close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean createDefaultRoles() {
try {
open();
Statement statement = userDB.createStatement();
statement.execute(ADD_DEFAULT_ROLES);
statement.close();
close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
private void addDefaultRolesToDefaultUsers() {
addRoleToUser("webgoat", "webgoat_admin");
addRoleToUser("basic", "webgoat_user");
addRoleToUser("basic", "webgoat_basic");
addRoleToUser("guest", "webgoat_user");
}
}

View File

@ -1,154 +0,0 @@
package org.owasp.webgoat.session;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.springframework.core.serializer.DefaultDeserializer;
import java.io.*;
import java.util.Map;
import java.util.stream.Collectors;
/**
* ************************************************************************************************
* <p>
* <p>
* 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 - 20014 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.
*
* @author Bruce Mayhew <a href="http://code.google.com/p/webgoat">WebGoat</a>
* @version $Id: $Id
* @since October 29, 2003
*/
@Slf4j
public class UserTracker {
private final String webgoatHome;
private final String user;
public UserTracker(final String webgoatHome, final String user) {
this.webgoatHome = webgoatHome;
this.user = user;
}
/**
* Returns the lesson tracker for a specific lesson if available.
*
* @param lesson the lesson
* @return the optional lesson tracker
*/
public LessonTracker getLessonTracker(AbstractLesson lesson) {
return getLessonTracker(load(), lesson);
}
/**
* Returns the lesson tracker for a specific lesson if available.
*
* @param lesson the lesson
* @return the optional lesson tracker
*/
public LessonTracker getLessonTracker(Map<String, LessonTracker> storage, AbstractLesson lesson) {
LessonTracker lessonTracker = storage.get(lesson.getTitle());
if (lessonTracker == null) {
lessonTracker = new LessonTracker(lesson);
storage.put(lesson.getTitle(), lessonTracker);
save(storage);
}
return lessonTracker;
}
public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = storage.get(lesson.getTitle());
lessonTracker.incrementAttempts();
lessonTracker.assignmentSolved(assignmentName);
save(storage);
}
public void assignmentFailed(AbstractLesson lesson) {
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = storage.get(lesson.getTitle());
lessonTracker.incrementAttempts();
save(storage);
}
public Map<String, LessonTracker> load() {
File file = new File(webgoatHome, user + ".progress");
Map<String, LessonTracker> storage = Maps.newHashMap();
if (file.exists() && file.isFile()) {
try {
DefaultDeserializer deserializer = new DefaultDeserializer(Thread.currentThread().getContextClassLoader());
try (FileInputStream fis = new FileInputStream(file)) {
byte[] b = ByteStreams.toByteArray(fis);
storage = (Map<String, LessonTracker>) deserializer.deserialize(new ByteArrayInputStream(b));
}
} catch (Exception e) {
log.error("Unable to read the progress file, creating a new one...");
}
}
return storage;
}
@SneakyThrows
private void save(Map<String, LessonTracker> storage) {
File file = new File(webgoatHome, user + ".progress");
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file))) {
objectOutputStream.writeObject(storage);
objectOutputStream.flush();
}
}
public void reset(AbstractLesson al) {
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = getLessonTracker(storage, al);
lessonTracker.reset();
save(storage);
}
public int numberOfLessonsSolved() {
int numberOfLessonsSolved = 0;
Map<String, LessonTracker> storage = load();
for (LessonTracker lessonTracker : storage.values()) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
}
}
return numberOfLessonsSolved;
}
public int numberOfAssignmentsSolved() {
int numberOfAssignmentsSolved = 0;
Map<String, LessonTracker> storage = load();
for (LessonTracker lessonTracker : storage.values()) {
Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
}
return numberOfAssignmentsSolved;
}
}

View File

@ -2,6 +2,7 @@ package org.owasp.webgoat.session;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import java.sql.Connection; import java.sql.Connection;

View File

@ -1,5 +1,5 @@
package org.owasp.webgoat.session; package org.owasp.webgoat.users;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -7,7 +7,6 @@ import lombok.Getter;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment; import org.owasp.webgoat.lessons.Assignment;
import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -45,14 +44,20 @@ import java.util.stream.Collectors;
* @version $Id: $Id * @version $Id: $Id
* @since October 29, 2003 * @since October 29, 2003
*/ */
public class LessonTracker implements Serializable { public class LessonTracker {
private static final long serialVersionUID = 5410058267505412928L; @Getter
private String lessonName;
private final Set<Assignment> solvedAssignments = Sets.newHashSet(); private final Set<Assignment> solvedAssignments = Sets.newHashSet();
private final List<Assignment> allAssignments = Lists.newArrayList(); private final List<Assignment> allAssignments = Lists.newArrayList();
@Getter @Getter
private int numberOfAttempts = 0; private int numberOfAttempts = 0;
protected LessonTracker() {
//Mongo
}
public LessonTracker(AbstractLesson lesson) { public LessonTracker(AbstractLesson lesson) {
lessonName = lesson.getId();
allAssignments.addAll(lesson.getAssignments()); allAssignments.addAll(lesson.getAssignments());
} }
@ -61,7 +66,7 @@ public class LessonTracker implements Serializable {
} }
/** /**
* Mark an assingment as solved * Mark an assignment as solved
* *
* @param solvedAssignment the assignment which the user solved * @param solvedAssignment the assignment which the user solved
*/ */

View File

@ -2,7 +2,6 @@ package org.owasp.webgoat.users;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;

View File

@ -0,0 +1,61 @@
package org.owasp.webgoat.users;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.Course;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
/**
* Temp endpoint just for the CTF.
*
* @author nbaars
* @since 3/23/17.
*/
@RestController
@AllArgsConstructor
public class Scoreboard {
private final UserTrackerRepository userTrackerRepository;
private final UserRepository userRepository;
private final Course course;
private final PluginMessages pluginMessages;
@AllArgsConstructor
@Getter
private class Ranking {
private String username;
private List<String> flagsCaptured;
}
@GetMapping("/scoreboard-data")
public List<Ranking> getRankings() {
List<WebGoatUser> allUsers = userRepository.findAll();
List<Ranking> rankings = Lists.newArrayList();
for (WebGoatUser user : allUsers) {
UserTracker userTracker = userTrackerRepository.findOne(user.getUsername());
rankings.add(new Ranking(user.getUsername(), challengesSolved(userTracker)));
}
return rankings;
}
private List<String> challengesSolved(UserTracker userTracker) {
List<String> challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5");
return challenges.stream()
.map(c -> userTracker.getLessonTracker(c))
.filter(l -> l.isPresent()).map(l -> l.get())
.map(l -> l.getLessonName())
.map(l -> toLessonTitle(l))
.collect(Collectors.toList());
}
private String toLessonTitle(String id) {
String titleKey = course.getLessons().stream().filter(l -> l.getId().equals(id)).findFirst().get().getTitle();
return pluginMessages.getMessage(titleKey, titleKey);
}
}

View File

@ -1,13 +1,12 @@
package org.owasp.webgoat.users; package org.owasp.webgoat.users;
import org.owasp.webgoat.session.WebGoatUser; import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.CrudRepository;
/** /**
* @author nbaars * @author nbaars
* @since 3/19/17. * @since 3/19/17.
*/ */
public interface UserRepository extends CrudRepository<WebGoatUser, Long> { public interface UserRepository extends MongoRepository<WebGoatUser, String> {
WebGoatUser findByUsername(String username); WebGoatUser findByUsername(String username);
} }

View File

@ -1,7 +1,6 @@
package org.owasp.webgoat.users; package org.owasp.webgoat.users;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -15,6 +14,7 @@ import org.springframework.stereotype.Service;
public class UserService implements UserDetailsService { public class UserService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserTrackerRepository userTrackerRepository;
@Override @Override
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException { public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
@ -29,5 +29,6 @@ public class UserService implements UserDetailsService {
public void addUser(String username, String password) { public void addUser(String username, String password) {
userRepository.save(new WebGoatUser(username, password)); userRepository.save(new WebGoatUser(username, password));
userTrackerRepository.save(new UserTracker(username));
} }
} }

View File

@ -0,0 +1,119 @@
package org.owasp.webgoat.users;
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 java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* ************************************************************************************************
* <p>
* <p>
* 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 - 20014 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.
*
* @author Bruce Mayhew <a href="http://code.google.com/p/webgoat">WebGoat</a>
* @version $Id: $Id
* @since October 29, 2003
*/
@Slf4j
public class UserTracker {
@Id
private final String user;
private List<LessonTracker> lessonTrackers = Lists.newArrayList();
public UserTracker(final String user) {
this.user = user;
}
/**
* Returns an existing lesson tracker or create a new one based on the lesson
*
* @param lesson the lesson
* @return a lesson tracker created if not already present
*/
public LessonTracker getLessonTracker(AbstractLesson lesson) {
Optional<LessonTracker> lessonTracker = lessonTrackers
.stream().filter(l -> l.getLessonName().equals(lesson.getId())).findFirst();
if (!lessonTracker.isPresent()) {
LessonTracker newLessonTracker = new LessonTracker(lesson);
lessonTrackers.add(newLessonTracker);
return newLessonTracker;
} else {
return lessonTracker.get();
}
}
/**
* Query method for finding a specific lesson tracker based on id
*
* @param id the id of the lesson
* @return optional due to the fact we can only create a lesson tracker based on a lesson
*/
public Optional<LessonTracker> getLessonTracker(String id) {
return lessonTrackers.stream().filter(l -> l.getLessonName().equals(id)).findFirst();
}
public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
LessonTracker lessonTracker = getLessonTracker(lesson);
lessonTracker.incrementAttempts();
lessonTracker.assignmentSolved(assignmentName);
}
public void assignmentFailed(AbstractLesson lesson) {
LessonTracker lessonTracker = getLessonTracker(lesson);
lessonTracker.incrementAttempts();
}
public void reset(AbstractLesson al) {
LessonTracker lessonTracker = getLessonTracker(al);
lessonTracker.reset();
}
public int numberOfLessonsSolved() {
int numberOfLessonsSolved = 0;
for (LessonTracker lessonTracker : lessonTrackers) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
}
}
return numberOfLessonsSolved;
}
public int numberOfAssignmentsSolved() {
int numberOfAssignmentsSolved = 0;
for (LessonTracker lessonTracker : lessonTrackers) {
Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
}
return numberOfAssignmentsSolved;
}
}

View File

@ -0,0 +1,12 @@
package org.owasp.webgoat.users;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author nbaars
* @since 4/30/17.
*/
public interface UserTrackerRepository extends MongoRepository<UserTracker, String> {
}

View File

@ -1,14 +1,13 @@
package org.owasp.webgoat.session; package org.owasp.webgoat.users;
import lombok.Getter; 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.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; 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.Collection;
import java.util.Collections; import java.util.Collections;
@ -17,7 +16,6 @@ import java.util.Collections;
* @since 3/19/17. * @since 3/19/17.
*/ */
@Getter @Getter
@Entity
public class WebGoatUser implements UserDetails { public class WebGoatUser implements UserDetails {
public static final String ROLE_USER = "WEBGOAT_USER"; public static final String ROLE_USER = "WEBGOAT_USER";

View File

@ -16,6 +16,7 @@ security.enable-csrf=false
spring.resources.cache-period=0 spring.resources.cache-period=0
spring.thymeleaf.cache=false spring.thymeleaf.cache=false
webgoat.clean=true
webgoat.server.directory=${user.home}/.webgoat/ webgoat.server.directory=${user.home}/.webgoat/
webgoat.user.directory=${user.home}/.webgoat/ webgoat.user.directory=${user.home}/.webgoat/
webgoat.build.version=@project.version@ webgoat.build.version=@project.version@
@ -25,18 +26,11 @@ webgoat.emaillist=owasp-webgoat@lists.owasp.org
webgoat.feedback.address=webgoat@owasp.org webgoat.feedback.address=webgoat@owasp.org
webgoat.feedback.address.html=<A HREF=mailto:webgoat@owasp.org>webgoat@owasp.org</A> webgoat.feedback.address.html=<A HREF=mailto:webgoat@owasp.org>webgoat@owasp.org</A>
webgoat.database.driver=org.hsqldb.jdbcDriver webgoat.database.driver=org.hsqldb.jdbcDriver
webgoat.database.connection.string=jdbc:hsqldb:mem:test webgoat.database.connection.string=jdbc:hsqldb:mem:{USER}
# TODO_NB
#webgoat.database.connection.string=jdbc:hsqldb:mem:${USER}
webgoat.default.language=en webgoat.default.language=en
spring.data.mongodb.database=webgoat
spring.mongodb.embedded.storage.databaseDir=${webgoat.user.directory}/mongodb/
liquibase.change-log=classpath:db/changelog/db.changelog-master.xml #For static file refresh ... and faster dev :D
spring.datasource.url=jdbc:hsqldb:file:${user.home}/.webgoat/WebGoatDatabase;hsqldb.write_delay=false spring.devtools.restart.additional-paths=webgoat-container/src/main/resources/static/js,webgoat-container/src/main/resources/static/css
spring.datasource.driverClassName=org.hsqldb.jdbcDriver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

View File

@ -6,9 +6,7 @@
<changeSet author="WebGoat" id="init_schema"> <changeSet author="WebGoat" id="init_schema">
<createTable tableName="web_goat_user"> <createTable tableName="web_goat_user">
<column name="username" type="varchar(32)"> <column name="username" type="varchar(32)"/>
<constraints unique="true"/>
</column>
<column name="password" type="varchar(32)"/> <column name="password" type="varchar(32)"/>
<column name="role" type="varchar(32)"/> <column name="role" type="varchar(32)"/>
</createTable> </createTable>

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

View File

@ -152,7 +152,7 @@ img {
margin-left: 1.5em;*/ margin-left: 1.5em;*/
margin-right: 5px; margin-right: 5px;
margin-top: -38px; /* << don't like doing this, but otherwise it does not line up correctly */ margin-top: -38px; /* << don't like doing this, but otherwise it does not line up correctly */
color:#0F0 color:#88FB88 /* #0F0 */
} }
/* ========================================================================== /* ==========================================================================
@ -958,8 +958,52 @@ cookie-container {
cursor:pointer; cursor:pointer;
} }
.page-nav-wrapper {
display:inline-block;
width: 30px;
}
.attack-link, .page-link {
display: inline-block;
background-color: #555;
border-radius: 8px;
min-width: 20px;
text-align: center;
font-weight: bold;
padding-top:2px;
}
.attack-link.solved-true {
color:#88FB88;
}
.attack-link.solved-false {
color:#f2baba;
}
.attack-link.cur-page, .page-link.cur-page {
color:#fff;
}
.page-link {
color:#eee;
}
.page-link-wrapper {
display:inline-block;
}
.page-link-wrapper span {
margin: 3px;
}
.cur-page {
border-bottom: 2px solid #000;
color:#aaa;
}
span.show-next-page, span.show-prev-page { span.show-next-page, span.show-prev-page {
font-size: 1.3em; font-size: 1.3em;
} }
.show-prev-page { .show-prev-page {
@ -970,6 +1014,8 @@ font-size: 1.3em;
cursor:pointer; cursor:pointer;
} }
/* attack ... */
.attack-feedback { .attack-feedback {
font-weight:800; font-weight:800;
} }
@ -984,10 +1030,11 @@ font-size: 1.3em;
} }
#lesson-hint { #lesson-hint {
background-color: #ccc; background-color: #f1f1f1;
border-radius: 4px; border-radius: 4px;
border-color: #999; border-color: #4fa44c;
margin-top:4px; margin-top: 4px;
border: 2px solid #24b054;
} }
#hintsViewTop{ #hintsViewTop{
@ -1043,6 +1090,60 @@ font-size: 1.3em;
padding: 10px; padding: 10px;
} }
/* temp override
//TODO: come up with longer term solution for full-window viewing
*/
.col-md-8 { .col-md-8 {
width: 95% !important; width: 95% !important
}
/* scoreboard */
div.scoreboard-title {
font-size:xx-large;
}
.scoreboard-table tr {
}
div.scoreboard-username {
background-color: #222;
color: aliceblue;
padding: 4px;
padding-left:8px;
font-size: x-large;
border-radius:6px;
}
th.username {
padding-bottom: 6px;
}
td.user-flags {
padding-left: 8px;
padding-bottom: 6px;
}
div.captured-flag {
border-radius: 6px;
background-color: #444;
color: white;
padding: 4px;
font-size: x-large;
display: inline-block;
}
.scoreboard-page {
background-color: #e0dfdc;
padding: 20px;
}
.fa-flag {
color:red
}
.appseceu-banner {
background: url('img/appseceu-17.png') no-repeat 0px 0px;
height: 117px;
width: 1268px;
margin-bottom: 20px;
} }

View File

@ -3,12 +3,11 @@ define(['jquery',
'libs/backbone', 'libs/backbone',
'goatApp/model/LessonContentModel', 'goatApp/model/LessonContentModel',
'goatApp/view/LessonContentView', 'goatApp/view/LessonContentView',
'goatApp/view/PlanView', // 'goatApp/view/PlanView',
'goatApp/view/SourceView', // 'goatApp/view/SourceView',
'goatApp/view/SolutionView', // 'goatApp/view/SolutionView',
'goatApp/view/HintView', 'goatApp/view/HintView',
'goatApp/view/HelpControlsView', 'goatApp/view/HelpControlsView',
'goatApp/view/CookieView',
'goatApp/view/ParamView', 'goatApp/view/ParamView',
'goatApp/model/ParamModel', 'goatApp/model/ParamModel',
'goatApp/view/DeveloperControlsView', 'goatApp/view/DeveloperControlsView',
@ -27,12 +26,11 @@ define(['jquery',
Backbone, Backbone,
LessonContentModel, LessonContentModel,
LessonContentView, LessonContentView,
PlanView, // PlanView,
SourceView, // SourceView,
SolutionView, // SolutionView,
HintView, HintView,
HelpControlsView, HelpControlsView,
CookieView,
ParamView, ParamView,
ParamModel, ParamModel,
DeveloperControlsView, DeveloperControlsView,
@ -66,13 +64,29 @@ define(['jquery',
this.menuButtonView = new MenuButtonView(); this.menuButtonView = new MenuButtonView();
this.listenTo(this.lessonContentView, 'assignment:complete', this.updateMenu); this.listenTo(this.lessonContentView, 'assignment:complete', this.updateMenu);
this.listenTo(this.lessonContentView, 'assignment:complete', this.updateLessonOverview); this.listenTo(this.lessonContentView, 'assignment:complete', this.updateLessonOverview);
this.listenTo(this.lessonContentView, 'endpoints:filtered', this.filterPageHints);
}; };
this.loadLesson = function(name,pageNum) { this.filterPageHints = function(endpoints) {
//filter hints for page by
this.lessonHintView.filterHints(endpoints);
}
this.onHideHintsButton = function() {
this.helpControlsView.hideHintsButton();
}
this.onShowHintsButton = function() {
this.helpControlsView.showHintsButton();
}
this.loadLesson = function(name,pageNum) {
if (this.name === name) { if (this.name === name) {
this.listenTo(this.lessonHintView, 'hints:showButton', this.onShowHintsButton);
this.listenTo(this.lessonHintView, 'hints:hideButton', this.onHideHintsButton);
this.lessonContentView.navToPage(pageNum); this.lessonContentView.navToPage(pageNum);
this.lessonHintView.hideHints(); this.lessonHintView.hideHints();
//this.lessonHintView.selectHints();
this.titleView.render(this.lessonInfoModel.get('lessonTitle')); this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
return; return;
} }
@ -82,10 +96,10 @@ define(['jquery',
//TODO: implement lesson not found or return to welcome page? //TODO: implement lesson not found or return to welcome page?
} }
this.lessonContent.loadData({'name':name}); this.lessonContent.loadData({'name':name});
this.planView = {}; // this.planView = {};
this.solutionView = {}; // this.solutionView = {};
this.sourceView = {}; // this.sourceView = {};
this.lessonHintView = {}; // this.lessonHintView = {};
this.name = name; this.name = name;
}; };
@ -98,15 +112,13 @@ define(['jquery',
this.listenTo(this.helpControlsView,'hints:show',this.showHints); this.listenTo(this.helpControlsView,'hints:show',this.showHints);
this.listenTo(this.helpControlsView,'lessonOverview:show',this.showLessonOverview) this.listenTo(this.helpControlsView,'lessonOverview:show',this.showLessonOverview)
this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps);
this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps);
this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson); this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson);
this.listenTo(this.developerControlsView, 'dev:labels', this.restartLesson); this.listenTo(this.developerControlsView, 'dev:labels', this.restartLesson);
this.helpControlsView.render(); this.helpControlsView.render();
this.lessonOverview.hideLessonOverview(); this.lessonOverview.hideLessonOverview();
this.titleView.render(this.lessonInfoModel.get('lessonTitle')); this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
this.helpControlsView.showHideHintsButton({});
}; };
this.updateMenu = function() { this.updateMenu = function() {
@ -126,11 +138,14 @@ define(['jquery',
this.lessonContentView.model = this.lessonContent; this.lessonContentView.model = this.lessonContent;
this.lessonContentView.render(); this.lessonContentView.render();
this.planView = new PlanView(); //this.planView = new PlanView();
this.solutionView = new SolutionView(); //this.solutionView = new SolutionView();
this.sourceView = new SourceView(); //this.sourceView = new SourceView();
if (this.lessonHintView) {
this.lessonHintView.stopListening();
this.lessonHintView = null;
}
this.lessonHintView = new HintView(); this.lessonHintView = new HintView();
this.cookieView = new CookieView();
//TODO: instantiate model with values (not sure why was not working before) //TODO: instantiate model with values (not sure why was not working before)
var paramModel = new ParamModel({}); var paramModel = new ParamModel({});
@ -150,34 +165,34 @@ define(['jquery',
this.helpsLoaded[curHelp.helpElement] = curHelp.value; this.helpsLoaded[curHelp.helpElement] = curHelp.value;
}; };
this.hideShowHelps = function(showHelp) { // this.hideShowHelps = function(showHelp) {
var showId = '#lesson-' + showHelp + '-row'; // var showId = '#lesson-' + showHelp + '-row';
var contentId = '#lesson-' + showHelp + '-content'; // var contentId = '#lesson-' + showHelp + '-content';
$('.lesson-help').not(showId).hide(); // $('.lesson-help').not(showId).hide();
if (!showId) { // if (!showId) {
return; // return;
} // }
//
if ($(showId).is(':visible')) { // if ($(showId).is(':visible')) {
$(showId).hide(); // $(showId).hide();
return; // return;
} else { // } else {
//TODO: move individual .html operations into individual help views // //TODO: move individual .html operations into individual help views
switch(showHelp) { // switch(showHelp) {
case 'plan': // case 'plan':
$(contentId).html(this.planView.model.get('content')); // $(contentId).html(this.planView.model.get('content'));
break; // break;
case 'solution': // case 'solution':
$(showId).html(this.solutionView.model.get('content')); // $(showId).html(this.solutionView.model.get('content'));
break; // break;
case 'source': // case 'source':
$(contentId).html('<pre>' + this.sourceView.model.get('content') + '</pre>'); // $(contentId).html('<pre>' + this.sourceView.model.get('content') + '</pre>');
break; // break;
} // }
$(showId).show(); // $(showId).show();
GoatUtils.scrollToHelp() // GoatUtils.scrollToHelp()
} // }
}; // };
this.showHints = function() { this.showHints = function() {
this.lessonHintView.render(); this.lessonHintView.render();
@ -194,6 +209,7 @@ define(['jquery',
method:'GET' method:'GET'
}).done(function(lessonLink) { }).done(function(lessonLink) {
self.loadLesson(self.name); self.loadLesson(self.name);
self.updateMenu();
}); });
}; };

View File

@ -1,13 +1,13 @@
define(['jquery', define(['jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'goatApp/model/CookieModel'], 'goatApp/model/FlagModel'],
function($, function($,
_, _,
Backbone, Backbone,
CookieModel) { FlagModel) {
return Backbone.Collection.extend({ return Backbone.Collection.extend({
url:'service/cookie.mvc', url:'/WebGoat/scoreboard-data',
model:CookieModel model:FlagModel
}); });
}); });

View File

@ -20,7 +20,6 @@ define(['jquery',
loadData: function(options) { loadData: function(options) {
this.urlRoot = _.escape(encodeURIComponent(options.name)) + '.lesson' this.urlRoot = _.escape(encodeURIComponent(options.name)) + '.lesson'
var self = this; var self = this;
this.fetch().done(function(data) { this.fetch().done(function(data) {
self.setContent(data); self.setContent(data);
@ -32,7 +31,8 @@ define(['jquery',
loadHelps = true; loadHelps = true;
} }
this.set('content',content); this.set('content',content);
this.set('lessonUrl',document.URL); this.set('lessonUrl',document.URL.replace(/\.lesson.*/,'.lesson'));
this.set('pageNum',document.URL.replace(/.*\.lesson\/(\d{1,4})$/,'$1'));
this.trigger('content:loaded',this,loadHelps); this.trigger('content:loaded',this,loadHelps);
}, },

View File

@ -3,7 +3,7 @@ define([
function( function(
Backbone) { Backbone) {
return Backbone.Collection.extend({ return Backbone.Collection.extend({
tagName: 'ul', //tagName: 'ul',
url: 'service/lessonoverview.mvc' url: 'service/lessonoverview.mvc'
}); });
}); });

View File

@ -0,0 +1,17 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/support/goatAsyncErrorHandler',
'goatApp/view/ScoreboardView'],
function ($,
_,
Backbone,
asyncErrorHandler,
ScoreboardView) {
'use strict'
return {
initApp: function () {
scoreboard = new ScoreboardView();
}
};
});

View File

@ -0,0 +1,16 @@
<div>
<div class="page-nav-wrapper"><span class="glyphicon-class glyphicon glyphicon-circle-arrow-left show-prev-page"></span></div>
<div class="page-link-wrapper">
<% var baseUrl = overview.baseUrl; %>
<% _.each(overview.pages, function(page,index) { %>
<a href="<%=overview.baseUrl%>/<%=index%>" alt="Page <%=index++ %>">
<% if (page.content === 'assignment') { %>
<div class="<%=page.pageClass%> <%=page.solvedClass%> <%=page.curPageClass%>"><%=index++%></div>
<% } else { %>
<div class="<%=page.pageClass%> <%=page.curPageClass%>"><%=index++%></div>
<% } %>
</a>
<% }); %>
</div>
<div class="page-nav-wrapper"><span class="glyphicon-class glyphicon glyphicon-circle-arrow-right show-next-page"></span></div>
</div>

View File

@ -0,0 +1,16 @@
<div class="scoreboard-title">WebGoat Challenge - AppSec EU 2017</div>
<div class="appseceu-banner">banner here</div>
<table class="scoreboard-table">
<% _.each(rankings, function(userRanking, index) { %>
<tr>
<th class="username"> <div class="scoreboard-username"><%= index%> <%=userRanking.username %> </div></th>
<td class="user-flags"> <% _.each(userRanking.flagsCaptured, function(flag) { %>
<div class="captured-flag">
<i class="fa fa-flag" aria-hidden="true"></i>
<%=flag%> </div>
<% }); %>
</td>
</tr>
<% }); %>
</table>

View File

@ -1,34 +0,0 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/CookieCollection'],
function($,
_,
Backbone,
CookieCollection) {
return Backbone.View.extend({
el:'#cookies-view',
initialize: function() {
this.collection = new CookieCollection();
this.listenTo(this.collection,'reset',this.render)
this.collection.fetch({reset:true});
},
render: function() {
this.$el.html('')
var cookieTable;
this.collection.each(function(model) {
cookieTable = $('<table>',{'class':'cookie-table table-striped table-nonfluid'});
_.each(model.keys(), function(attribute) {
var newRow = $('<tr>');
newRow.append($('<th>',{text:_.escape(attribute)}))
newRow.append($('<td>',{text:_.escape(model.get(attribute))}));
cookieTable.append(newRow);
});
});
this.$el.append($('<h4>',{text:'Cookie/s'}));
this.$el.append(cookieTable);
}
});
});

View File

@ -12,18 +12,14 @@ function($,_,Backbone) {
this.hasPlan = options.hasPlan; this.hasPlan = options.hasPlan;
this.hasSolution = options.hasSolution; this.hasSolution = options.hasSolution;
this.hasSource = options.hasSource; this.hasSource = options.hasSource;
var self = this;
Backbone.on('navigatedToPage', function(nav) {
self.showHideHintsButton(nav)
});
}, },
showHideHintsButton: function(nav) { showHintsButton: function(nav) {
if (typeof nav['assignmentPath'] !== 'undefined') { this.$el.find('#show-hints-button').unbind().on('click',this.showHints.bind(this)).show();
this.$el.find('#show-hints-button').unbind().on('click',this.showHints.bind(this)).show(); },
} else {
$('#show-hints-button').hide(); hideHintsButton: function(){
} $('#show-hints-button').hide();
}, },
render:function(title) { render:function(title) {
@ -38,7 +34,7 @@ function($,_,Backbone) {
this.$el.find('#show-solution-button').unbind().on('click',_.bind(this.showSolution,this)).show(); this.$el.find('#show-solution-button').unbind().on('click',_.bind(this.showSolution,this)).show();
} }
this.$el.find('#show-lesson-overview-button').unbind().on('click', _.bind(this.showLessonOverview, this)).show(); //this.$el.find('#show-lesson-overview-button').unbind().on('click', _.bind(this.showLessonOverview, this)).show();
this.$el.find('#restart-lesson-button').unbind().on('click',_.bind(this.restartLesson,this)).show(); this.$el.find('#restart-lesson-button').unbind().on('click',_.bind(this.restartLesson,this)).show();
}, },

View File

@ -19,8 +19,10 @@ function($,
this.listenTo(this.collection,'loaded',this.onModelLoaded); this.listenTo(this.collection,'loaded',this.onModelLoaded);
this.hideHints(); this.hideHints();
var self = this; var self = this;
// different way to do this?
Backbone.on('navigatedToPage', function(nav){ Backbone.on('navigatedToPage', function(nav){
self.selectHints(nav) self.selectHints(nav);
// end event delegation??
}); });
}, },
@ -61,14 +63,25 @@ function($,
* *
* @param nav the json structure for navigating * @param nav the json structure for navigating
*/ */
selectHints: function(nav) { filterHints: function(endpoints) {
this.curHint = 0; this.hintsToShow = [];
var assignmentPath = nav['assignmentPath']; _.each(endpoints, this.filterHint, this);
if (assignmentPath != null) {
this.hintsToShow = this.collection.getHintsForAssignment(assignmentPath); if (this.hintsToShow.length > 0) {
this.trigger('hints:showButton');
} else { } else {
this.hintsToShow = new Array(); this.trigger('hints:hideButton');
} }
},
filterHint: function(endpoint) {
var self = this;
_.each(this.collection.models, function(hintModel) {
if (endpoint.indexOf(hintModel.get('assignmentPath')) > -1) {
self.hintsToShow.push(hintModel.get('hint'));
}
});
console.log(this.hintsToShow);
}, },
onModelLoaded: function() { onModelLoaded: function() {
@ -97,7 +110,7 @@ function($,
if(this.hintsToShow.length == 0) { if(this.hintsToShow.length == 0) {
// this.hideHints(); // this.hideHints();
} else { } else {
this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint].get('hint'))); this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint]));
} }
}, },

View File

@ -3,13 +3,15 @@ define(['jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'libs/jquery.form', 'libs/jquery.form',
'goatApp/view/ErrorNotificationView'], 'goatApp/view/ErrorNotificationView',
'goatApp/view/PaginationControlView'],
function( function(
$, $,
_, _,
Backbone, Backbone,
JQueryForm, JQueryForm,
ErrorNotificationView) { ErrorNotificationView,
PaginationControlView) {
return Backbone.View.extend({ return Backbone.View.extend({
el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM
@ -37,7 +39,7 @@ define(['jquery',
return -1; return -1;
}, },
/* initial renering */ /* initial rendering */
render: function() { render: function() {
this.$el.find('.lesson-content').html(this.model.get('content')); this.$el.find('.lesson-content').html(this.model.get('content'));
this.$el.find('.attack-feedback').hide(); this.$el.find('.attack-feedback').hide();
@ -45,31 +47,17 @@ define(['jquery',
this.makeFormsAjax(); this.makeFormsAjax();
//this.ajaxifyAttackHref(); //this.ajaxifyAttackHref();
$(window).scrollTop(0); //work-around til we get the scroll down sorted out $(window).scrollTop(0); //work-around til we get the scroll down sorted out
this.initPagination(); var startPageNum = this.model.get('pageNum');
this.initPagination(startPageNum);
}, },
initPagination: function() { initPagination: function(startPageNum) {
//get basic pagination info //get basic pagination info
this.currentPage = 0;
this.$contentPages = this.$el.find('.lesson-page-wrapper'); this.$contentPages = this.$el.find('.lesson-page-wrapper');
this.numPages = this.$contentPages.length; var currentPage = (!isNaN(startPageNum) && startPageNum && startPageNum < this.$contentPages) ? startPageNum : 0;
//init views & pagination
// this.showCurContentPage(currentPage);
this.addPaginationControls(); this.paginationControlView = new PaginationControlView(this.$contentPages,this.model.get('lessonUrl'));
if (this.numPages > 1) {
//no animation on init
this.$contentPages.hide();
this.$el.find(this.$contentPages[this.currentPage]).show();
this.showNextPageButton();
this.hidePrevPageButton();
} else if (this.numPages === 1) {
this.hideNextPageButton();
this.hidePrevPageButton();
}
},
setCurrentPage: function (pageNum) {
this.currentPage = (_.isNumber(pageNum) && pageNum < this.numPages) ? pageNum : 0;
}, },
getCurrentPage: function () { getCurrentPage: function () {
@ -160,128 +148,29 @@ define(['jquery',
this.$curOutput.show(400) this.$curOutput.show(400)
}, },
/* create, show & hide pagination controls */ showCurContentPage: function(pageNum) {
addPaginationControls: function() {
var pagingControlsDiv;
//this.$el.html();
//prev
var prevPageButton = $('<span>',{class:'glyphicon-class glyphicon glyphicon-circle-arrow-left show-prev-page'});
prevPageButton.unbind().on('click',this.decrementPageView.bind(this));
//next
var nextPageButton = $('<span>',{class:'glyphicon-class glyphicon glyphicon-circle-arrow-right show-next-page'});
nextPageButton.unbind().on('click',this.incrementPageView.bind(this));
//add to DOM
if (this.$el.find('#lesson-page-controls').length < 1) {
pagingControlsDiv = $('<div>',{class:'panel-body', id:'lesson-page-controls'});
pagingControlsDiv.append(prevPageButton);
pagingControlsDiv.append(nextPageButton);
this.$el.find('.lesson-page-controls').append(pagingControlsDiv);
}
},
showPrevPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').show();
},
hidePrevPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').hide();
},
showNextPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').show();
},
hideNextPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
},
/* increment, decrement & display handlers */
incrementPageView: function() {
if (this.currentPage < this.numPages -1) {
this.currentPage++;
window.location.href = this.model.get('lessonUrl') + '/' + this.currentPage;
//this.showCurContentPage(true);Con
}
if (this.currentPage > 0) {
this.showPrevPageButton();
}
if (this.currentPage >= this.numPages -1) {
this.hideNextPageButton();
this.showPrevPageButton;
}
},
decrementPageView: function() {
if (this.currentPage > 0) {
this.currentPage--;
window.location.href = this.model.get('lessonUrl') + '/' + this.currentPage;
//this.showCurContentPage(false);
}
if (this.currentPage < this.numPages -1) {
this.showNextPageButton();
}
if (this.currentPage == 0) {
this.hidePrevPageButton();
this.showNextPageButton()
}
},
showCurContentPage: function(isIncrement) {
this.$contentPages.hide(); this.$contentPages.hide();
this.$el.find(this.$contentPages[this.currentPage]).show(); this.$el.find(this.$contentPages[pageNum]).show();
}, },
findAssigmentEndpointOnPage: function(pageNumber) { findAssigmentEndpointsOnPage: function(pageNumber) {
var contentPage = this.$contentPages[this.currentPage]; var contentPage = this.$contentPages[pageNumber];
var form = $('form.attack-form', contentPage); var endpoints = []; //going to assume uniqueness since these are assignments
var action = form.attr('action') var pageForms = $(contentPage).find('form.attack-form');
if (action !== undefined) { for (var i=0; i<pageForms.length; i++) {
return action; endpoints.push(pageForms[i].action);
} }
console.log(endpoints);
return endpoints;
}, },
navToPage: function (pageNum) { navToPage: function (pageNum) {
this.setCurrentPage(pageNum);//provides validation this.paginationControlView.setCurrentPage(pageNum);//provides validation
this.showCurContentPage(this.currentPage); this.showCurContentPage(this.paginationControlView.currentPage);
this.hideShowNavButtons(); this.paginationControlView.render();
var assignmentPath = this.findAssigmentEndpointOnPage(pageNum); this.paginationControlView.hideShowNavButtons();
Backbone.trigger('navigatedToPage',{'pageNumber':pageNum, 'assignmentPath' : assignmentPath}); var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
}, this.trigger('endpoints:filtered',assignmentPaths);
hideShowNavButtons: function () {
//one page only
if (this.numPages === 1) {
this.hidePrevPageButton();
this.hideNextPageButton();
}
//first page
if (this.currentPage === 0) {
this.hidePrevPageButton();
if (this.numPages > 1) {
this.showNextPageButton();
}
return;
}
// > first page, but not last
if (this.currentPage > 0 && this.currentPage < this.numPages -1) {
this.showNextPageButton();
this.showPrevPageButton();
return;
}
// last page and more than one page
if (this.currentPage === this.numPages -1 && this.numPages > 1) {
this.hideNextPageButton();
this.showPrevPageButton();
return;
}
}, },
/* for testing */ /* for testing */

View File

@ -39,7 +39,7 @@ function($,
} else { } else {
this.$el.show(); this.$el.show();
} }
this.showAssignments(); //this.showAssignments();
return this; return this;
}, },

View File

@ -0,0 +1,186 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/LessonOverviewModel',
'text!templates/paging_controls.html'],
function ($,
_,
Backbone,
LessonOverviewModel,
PaginationTemplate) {
return Backbone.View.extend({
template: PaginationTemplate,
el: '#lesson-page-controls',
initialize: function ($contentPages,baseLessonUrl) {
this.$contentPages = $contentPages;
this.model = new LessonOverviewModel();
this.listenTo(this.model, 'change add remove update reset', this.render);
this.numPages = this.$contentPages.length;
this.baseUrl = baseLessonUrl;
this.model.fetch();
this.initPagination();
this.render();
},
render: function () {
this.parseLinks();
var t = _.template(this.template);
this.$el.html(t({'overview':this.lessonOverview}));
this.bindNavButtons();
this.hideShowNavButtons();
},
bindNavButtons: function() {
this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').unbind().on('click',this.incrementPageView.bind(this));
this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').unbind().on('click', this.decrementPageView.bind(this));
this.navButtonsBound = true;
},
parseLinks: function() {
var assignmentCount = this.$contentPages.find('.attack-container');
var solvedMap = {};
var pages = [];
// one pass on solved assignmets
_.each(this.model.toJSON(), function(assignment) {
if (assignment.solved) {
var key = assignment.assignment.path; //.replace(/\//g,'');
solvedMap[key] = assignment.assignment.name;
}
});
isAttackSolved = function (path) {
//strip
var newPath = path.replace(/^\/WebGoat/,'');
if (typeof solvedMap[newPath] !== 'undefined') {
return true;
}
return false;
};
var self = this;
var pages, pageClass, solved;
_.each(this.$contentPages,function(page,index) {
var curPageClass = (self.currentPage == index) ? ' cur-page' : '';
if ($(page).find('.attack-container').length < 1) { // no assignments [attacks]
pageClass = 'page-link';
pages.push({content:'content',pageClass:pageClass,curPageClass:curPageClass});
} else {
var $assignmentForms = $(page).find('.attack-container form');
// use for loop to avoid anonymous function scope hell
//var pageAssignments = {content:'attack',attacks:[]}
pageClass = 'attack-link'
var solvedClass = 'solved-true'
for (var i=0; i< $assignmentForms.length; i++) {
//normalize path
var action = $assignmentForms.attr('action');//.replace(/\//g,'');
if (action && isAttackSolved(action)) {
//pageClass = 'fa fa-check-square-o assignment-solved';
//pageAssignments.attacks.push({solved:true});
} else {
solvedClass = 'solved-false';
}
}
pages.push({solvedClass:solvedClass,content:'assignment',curPageClass:curPageClass,pageClass:pageClass});
}
});
//assign to the view
this.lessonOverview = {
baseUrl: this.baseUrl,
pages: pages
}
},
showPrevPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').show();
},
hidePrevPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').hide();
},
showNextPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').show();
},
hideNextPageButton: function() {
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
},
initPagination: function() {
//track pagination state in this view ... start at 0
this.currentPage = 0;
},
setCurrentPage: function (pageNum) {
this.currentPage = (_.isNumber(pageNum) && pageNum < this.numPages) ? pageNum : 0;
},
/* increment, decrement & display handlers */
incrementPageView: function() {
if (this.currentPage < this.numPages -1) {
this.currentPage++;
window.location.href = this.baseUrl + '/' + this.currentPage;
}
if (this.currentPage > 0) {
this.showPrevPageButton();
}
if (this.currentPage >= this.numPages -1) {
this.hideNextPageButton();
this.showPrevPageButton;
}
this.render();
},
decrementPageView: function() {
if (this.currentPage > 0) {
this.currentPage--;
window.location.href = this.baseUrl + '/' + this.currentPage;
}
if (this.currentPage < this.numPages -1) {
this.showNextPageButton();
}
if (this.currentPage == 0) {
this.hidePrevPageButton();
this.showNextPageButton()
}
this.render();
},
hideShowNavButtons: function () {
//one page only
if (this.numPages === 1) {
this.hidePrevPageButton();
this.hideNextPageButton();
}
//first page
if (this.currentPage === 0) {
this.hidePrevPageButton();
if (this.numPages > 1) {
this.showNextPageButton();
}
return;
}
// > first page, but not last
if (this.currentPage > 0 && this.currentPage < this.numPages -1) {
this.showNextPageButton();
this.showPrevPageButton();
return;
}
// last page and more than one page
if (this.currentPage === this.numPages -1 && this.numPages > 1) {
this.hideNextPageButton();
this.showPrevPageButton();
return;
}
},
});
});

View File

@ -0,0 +1,32 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/FlagsCollection',
'text!templates/scoreboard.html'],
function($,
_,
Backbone,
FlagsCollection,
ScoreboardTemplate) {
return Backbone.View.extend({
el:'#scoreboard',
initialize: function() {
this.template = ScoreboardTemplate,
this.collection = new FlagsCollection();
this.listenTo(this.collection,'reset',this.render)
this.collection.fetch({reset:true});
},
render: function() {
//this.$el.html('test');
var t = _.template(this.template);
this.$el.html(t({'rankings':this.collection.toJSON()}));
setTimeout(this.pollData.bind(this), 5000);
},
pollData: function() {
this.collection.fetch({reset:true});
}
});
});

View File

@ -0,0 +1,47 @@
//main.js
/*
/js
js/main.js << main file for require.js
--/libs/(jquery,backbone,etc.) << base libs
--/goatApp/ << base dir for goat application, js-wise
--/goatApp/model
--/goatApp/view
--/goatApp/support
--/goatApp/controller
*/
require.config({
baseUrl: "js/",
paths: {
jquery: 'libs/jquery-2.2.4.min',
jqueryui: 'libs/jquery-ui-1.10.4',
underscore: 'libs/underscore-min',
backbone: 'libs/backbone-min',
text: 'libs/text',
templates: 'goatApp/templates',
polyglot: 'libs/polyglot.min'
},
map: {
'libs/jquery-base' : {'jquery':'libs/jquery-2.2.4.min'},
'libs/jquery-vuln' : {'jquery':'libs/jquery-2.1.4.min'}
},
shim: {
"jqueryui": {
exports:"$",
deps: ['jquery']
},
underscore: {
exports: "_"
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
require(['jquery','libs/jquery-base','libs/jquery-vuln','jqueryui', 'underscore','backbone','goatApp/scoreboardApp'], function($,jqueryBase,jqueryVuln,jqueryui,_,Backbone,ScoreboardApp){
ScoreboardApp.initApp();
});

View File

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head> <head>
<meta http-equiv="Expires" CONTENT="0"/> <meta http-equiv="Expires" CONTENT="-1"/>
<meta http-equiv="Pragma" CONTENT="no-cache"/> <meta http-equiv="Pragma" CONTENT="no-cache"/>
<meta http-equiv="Cache-Control" CONTENT="no-cache"/> <meta http-equiv="Cache-Control" CONTENT="no-cache"/>
<meta http-equiv="Cache-Control" CONTENT="no-store"/> <meta http-equiv="Cache-Control" CONTENT="no-store"/>
@ -143,10 +143,7 @@
<button class="btn btn-primary btn-xs btn-danger help-button" <button class="btn btn-primary btn-xs btn-danger help-button"
id="show-hints-button" th:text="#{show.hints}">Show hints id="show-hints-button" th:text="#{show.hints}">Show hints
</button> </button>
<button class="btn btn-primary btn-xs btn-danger help-button"
id="show-lesson-overview-button" th:text="#{lesson.overview}">Lesson
overview
</button>
<button class="btn btn-xs help-button" id="restart-lesson-button" <button class="btn btn-xs help-button" id="restart-lesson-button"
th:text="#{reset.lesson}">Reset Lesson th:text="#{reset.lesson}">Reset Lesson
</button> </button>
@ -172,7 +169,7 @@
<div class="panel-body" id="lesson-overview"></div> <div class="panel-body" id="lesson-overview"></div>
</div> </div>
</div> </div>
<div class="lesson-page-controls"> <div id="lesson-page-controls">
</div> </div>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta http-equiv="Expires" CONTENT="-1"/>
<meta http-equiv="Pragma" CONTENT="no-cache"/>
<meta http-equiv="Cache-Control" CONTENT="no-cache"/>
<meta http-equiv="Cache-Control" CONTENT="no-store"/>
<!--[if lt IE 7]>
<id class="no-js lt-ie9 lt-ie8 lt-ie7"/> <![endif]-->
<!--[if IE 7]>
<id class="no-js lt-ie9 lt-ie8"/> <![endif]-->
<!--[if IE 8]>
<id class="no-js lt-ie9"/> <![endif]-->
<!--[if gt IE 8]><!-->
<!-- CSS -->
<link rel="shortcut icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<!-- Require.js used to load js asynchronously -->
<script src="js/libs/require.min.js" data-main="js/scoreboard.js"/>
<!-- main css -->
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/font-awesome.min.css}"/>
<meta http-equiv="Content-Type" content="text/id; charset=ISO-8859-1"/>
<title>WebGoat</title>
</head>
<body class="scoreboard-page">
<div id="scoreboard-wrapper">
<div id="scoreboard">
<!-- will use _ template here -->
</div>
</div>
</body>
</html>

View File

@ -30,18 +30,24 @@ import org.owasp.webgoat.i18n.Language;
import org.owasp.webgoat.i18n.Messages; import org.owasp.webgoat.i18n.Messages;
import org.owasp.webgoat.i18n.PluginMessages; import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.UserSessionData; import org.owasp.webgoat.session.UserSessionData;
import org.owasp.webgoat.session.UserTracker; import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import java.util.Locale; import java.util.Locale;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
public class AssignmentEndpointTest { public class AssignmentEndpointTest {
@Mock @Mock
protected UserTracker userTracker; protected UserTracker userTracker;
@Mock @Mock
protected UserTrackerRepository userTrackerRepository;
@Mock
protected WebSession webSession; protected WebSession webSession;
@Mock @Mock
protected UserSessionData userSessionData; protected UserSessionData userSessionData;
@ -56,7 +62,8 @@ public class AssignmentEndpointTest {
public void init(AssignmentEndpoint a) { public void init(AssignmentEndpoint a) {
messages.setBasenames("classpath:/i18n/messages", "classpath:/i18n/WebGoatLabels"); messages.setBasenames("classpath:/i18n/messages", "classpath:/i18n/WebGoatLabels");
ReflectionTestUtils.setField(a, "userTracker", userTracker); when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
ReflectionTestUtils.setField(a, "userTrackerRepository", userTrackerRepository);
ReflectionTestUtils.setField(a, "userSessionData", userSessionData); ReflectionTestUtils.setField(a, "userSessionData", userSessionData);
ReflectionTestUtils.setField(a, "webSession", webSession); ReflectionTestUtils.setField(a, "webSession", webSession);
ReflectionTestUtils.setField(a, "messages", pluginMessages); ReflectionTestUtils.setField(a, "messages", pluginMessages);

View File

@ -0,0 +1,91 @@
package org.owasp.webgoat.service;
import com.beust.jcommander.internal.Lists;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import static org.owasp.webgoat.service.LessonMenuService.URL_LESSONMENU_MVC;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
/**
* @author nbaars
* @since 4/16/17.
*/
@RunWith(MockitoJUnitRunner.class)
public class LessonMenuServiceTest {
@Mock
private Course course;
@Mock
private UserTracker userTracker;
@Mock
private UserTrackerRepository userTrackerRepository;
@Mock
private WebSession webSession;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = standaloneSetup(new LessonMenuService(course, webSession, userTrackerRepository)).build();
}
@Test
public void lessonsShouldBeOrdered() throws Exception {
NewLesson l1 = Mockito.mock(NewLesson.class);
NewLesson l2 = Mockito.mock(NewLesson.class);
when(l1.getTitle()).thenReturn("ZA");
when(l2.getTitle()).thenReturn("AA");
when(l1.getCategory()).thenReturn(Category.ACCESS_CONTROL);
when(l2.getCategory()).thenReturn(Category.ACCESS_CONTROL);
LessonTracker lessonTracker = Mockito.mock(LessonTracker.class);
when(lessonTracker.isLessonSolved()).thenReturn(false);
when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1, l2));
when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].children[0].name", CoreMatchers.is("AA")))
.andExpect(jsonPath("$[0].children[1].name", CoreMatchers.is("ZA")));
}
@Test
public void lessonCompleted() throws Exception {
NewLesson l1 = Mockito.mock(NewLesson.class);
when(l1.getTitle()).thenReturn("ZA");
when(l1.getCategory()).thenReturn(Category.ACCESS_CONTROL);
LessonTracker lessonTracker = Mockito.mock(LessonTracker.class);
when(lessonTracker.isLessonSolved()).thenReturn(true);
when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1));
when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
.andExpect(status().isOk()).andDo(print())
.andExpect(jsonPath("$[0].children[0].complete", CoreMatchers.is(true)));
}
}

View File

@ -8,9 +8,10 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment; import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@ -18,6 +19,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -63,15 +65,18 @@ public class LessonProgressServiceTest {
@Mock @Mock
private LessonTracker lessonTracker; private LessonTracker lessonTracker;
@Mock @Mock
private UserTrackerRepository userTrackerRepository;
@Mock
private WebSession websession; private WebSession websession;
@Before @Before
public void setup() { public void setup() {
Assignment assignment = new Assignment("test", "test"); Assignment assignment = new Assignment("test", "test");
when(userTracker.getLessonTracker(any())).thenReturn(lessonTracker); when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
when(websession.getCurrentLesson()).thenReturn(lesson); when(websession.getCurrentLesson()).thenReturn(lesson);
when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignment, true)); when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignment, true));
this.mockMvc = MockMvcBuilders.standaloneSetup(new LessonProgressService(userTracker, websession)).build(); this.mockMvc = MockMvcBuilders.standaloneSetup(new LessonProgressService(userTrackerRepository, websession)).build();
} }
@Test @Test

View File

@ -8,14 +8,17 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.Course; import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.session.UserTracker; import org.owasp.webgoat.users.LessonTracker;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -33,10 +36,14 @@ public class ReportCardServiceTest {
private AbstractLesson lesson; private AbstractLesson lesson;
@Mock @Mock
private LessonTracker lessonTracker; private LessonTracker lessonTracker;
@Mock
private UserTrackerRepository userTrackerRepository;
@Mock
private WebSession websession;
@Before @Before
public void setup() { public void setup() {
this.mockMvc = standaloneSetup(new ReportCardService(userTracker, course)).build(); this.mockMvc = standaloneSetup(new ReportCardService(websession, userTrackerRepository, course)).build();
} }
@Test @Test
@ -46,7 +53,8 @@ public class ReportCardServiceTest {
when(course.getTotalOfLessons()).thenReturn(1); when(course.getTotalOfLessons()).thenReturn(1);
when(course.getTotalOfAssignments()).thenReturn(10); when(course.getTotalOfAssignments()).thenReturn(10);
when(course.getLessons()).thenReturn(Lists.newArrayList(lesson)); when(course.getLessons()).thenReturn(Lists.newArrayList(lesson));
when(userTracker.getLessonTracker(any())).thenReturn(lessonTracker); when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
mockMvc.perform(MockMvcRequestBuilders.get("/service/reportcard.mvc")) mockMvc.perform(MockMvcRequestBuilders.get("/service/reportcard.mvc"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.totalNumberOfLessons", is(1))) .andExpect(jsonPath("$.totalNumberOfLessons", is(1)))

View File

@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import org.junit.Test; import org.junit.Test;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment; import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.users.LessonTracker;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -69,5 +70,18 @@ public class LessonTrackerTest {
assertThat(lessonOverview.get(a2)).isFalse(); assertThat(lessonOverview.get(a2)).isFalse();
} }
@Test
public void solvingSameAssignmentShouldNotAddItTwice() {
AbstractLesson lesson = mock(AbstractLesson.class);
Assignment a1 = new Assignment("a1", "a1");
List<Assignment> assignments = Lists.newArrayList(a1);
when(lesson.getAssignments()).thenReturn(assignments);
LessonTracker lessonTracker = new LessonTracker(lesson);
lessonTracker.assignmentSolved("a1");
lessonTracker.assignmentSolved("a1");
assertThat(lessonTracker.getLessonOverview().size()).isEqualTo(1);
}
} }

View File

@ -1,105 +0,0 @@
package org.owasp.webgoat.session;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* ************************************************************************************************
* 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 - 20014 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>
*
* @author nbaars
* @version $Id: $Id
* @since November 15, 2016
*/
public class UserTrackerTest {
private File home;
@Before
public void init() throws IOException {
home = File.createTempFile("test", "test");
home.deleteOnExit();
}
@Test
public void writeAndRead() {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentSolved(lesson, lesson.getAssignments().get(0).getName());
userTracker = new UserTracker(home.getParent(), "test");
userTracker.load();
assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isTrue();
}
@Test
public void assignmentFailedShouldIncrementAttempts() {
UserTracker userTracker = new UserTracker(home.getParent(), UUID.randomUUID().toString());
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentFailed(lesson);
userTracker.assignmentFailed(lesson);
assertThat(userTracker.getLessonTracker(lesson).getNumberOfAttempts()).isEqualTo(2);
}
@Test
public void resetShouldClearSolvedAssignment() {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentSolved(lesson, "assignment");
assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isTrue();
userTracker.reset(lesson);
assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isFalse();
}
@Test
public void totalAssignmentsSolved() {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentSolved(lesson, "assignment");
assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);
assertThat(userTracker.numberOfLessonsSolved()).isEqualTo(1);
}
}

View File

@ -14,11 +14,14 @@ public class UserServiceTest {
@Mock @Mock
private UserRepository userRepository; private UserRepository userRepository;
@Mock
private UserTrackerRepository userTrackerRepository;
@Test(expected = UsernameNotFoundException.class) @Test(expected = UsernameNotFoundException.class)
public void shouldThrowExceptionWhenUserIsNotFound() { public void shouldThrowExceptionWhenUserIsNotFound() {
when(userRepository.findByUsername(any())).thenReturn(null); when(userRepository.findByUsername(any())).thenReturn(null);
UserService userService = new UserService(userRepository); UserService userService = new UserService(userRepository, userTrackerRepository);
userService.loadUserByUsername("unknown"); userService.loadUserByUsername("unknown");
} }

View File

@ -4,7 +4,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;

View File

@ -0,0 +1,27 @@
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import org.apache.commons.lang3.RandomStringUtils
import scala.concurrent.duration._
class BasicSimulation extends Simulation {
val httpConf = http
.baseURL("http://localhost:8080/WebGoat/") // Here is the root for all relative URLs
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
val scn = scenario("Register and automatic login").
exec(session =>
session.setAll(("username", RandomStringUtils.randomAlphabetic(10)))
)
.exec(
http("Test")
.post("register.mvc")
.formParam("username", "${username}")
.formParam("password", "${username}")
.formParam("matchingPassword", "${username}")
.formParam("agree", "agree")
)
setUp(scn.inject(atOnceUsers(100)).protocols(httpConf))
}

View File

@ -9,4 +9,30 @@
<version>8.0-SNAPSHOT</version> <version>8.0-SNAPSHOT</version>
</parent> </parent>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<type>jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>

View File

@ -10,7 +10,7 @@ import java.util.List;
* @author nbaars * @author nbaars
* @since 3/21/17. * @since 3/21/17.
*/ */
public class Challenge extends NewLesson { public class ChallengeIntro extends NewLesson {
@Override @Override
public Category getDefaultCategory() { public Category getDefaultCategory() {
@ -29,7 +29,7 @@ public class Challenge extends NewLesson {
@Override @Override
public String getTitle() { public String getTitle() {
return "challenge.title"; return "challenge0.title";
} }
@Override @Override

View File

@ -0,0 +1,75 @@
package org.owasp.webgoat.plugin;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.assignments.Endpoint;
import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.UUID;
import java.util.stream.IntStream;
/**
* @author nbaars
* @since 3/23/17.
*/
@Slf4j
public class Flag extends Endpoint {
public static final Map<Integer, String> FLAGS = Maps.newHashMap();
@Autowired
private UserTrackerRepository userTrackerRepository;
@Autowired
private WebSession webSession;
@Autowired
private PluginMessages pluginMessages;
@AllArgsConstructor
private class FlagPosted {
@Getter
private boolean lessonCompleted;
}
@PostConstruct
public void initFlags() {
IntStream.range(1, 7).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString()));
FLAGS.entrySet().stream().forEach(e -> log.debug("Flag {} {}", e.getKey(), e.getValue()));
}
@Override
public String getPath() {
return "challenge/flag";
}
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public AttackResult postFlag(@RequestParam String flag) {
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
String currentChallenge = webSession.getCurrentLesson().getName();
int challengeNumber = Integer.valueOf(currentChallenge.substring(currentChallenge.length() - 1, currentChallenge.length()));
String expectedFlag = FLAGS.get(challengeNumber);
final AttackResult attackResult;
if (expectedFlag.equals(flag)) {
userTracker.assignmentSolved(webSession.getCurrentLesson(), "Assignment" + challengeNumber);
attackResult = new AttackResult.AttackResultBuilder(pluginMessages).lessonCompleted(true, "challenge.flag.correct").build();
} else {
userTracker.assignmentFailed(webSession.getCurrentLesson());
attackResult = new AttackResult.AttackResultBuilder(pluginMessages).feedback("challenge.flag.incorrect").build();
}
userTrackerRepository.save(userTracker);
return attackResult;
}
}

View File

@ -0,0 +1,18 @@
package org.owasp.webgoat.plugin;
/**
* Interface with constants so we can easily change the flags
*
* @author nbaars
* @since 3/23/17.
*/
public interface SolutionConstants {
//TODO should be random generated when starting the server
String PASSWORD = "!!webgoat_admin_1234!!";
String SUPER_COUPON_CODE = "get_it_for_free";
String PASSWORD_TOM = "thisisasecretfortomonly";
String PASSWORD_LARRY = "larryknows";
String JWT_PASSWORD = "victory";
}

View File

@ -0,0 +1,68 @@
package org.owasp.webgoat.plugin.challenge1;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.plugin.Flag;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD;
/**
* ************************************************************************************************
* 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 - 20014 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>
*
* @author WebGoat
* @version $Id: $Id
* @since August 11, 2016
*/
@AssignmentPath("/challenge/1")
public class Assignment1 extends AssignmentEndpoint {
@RequestMapping(method = RequestMethod.POST)
public
@ResponseBody
AttackResult completed(@RequestParam String username, @RequestParam String password, HttpServletRequest request) throws IOException {
boolean ipAddressKnown = true;
boolean passwordCorrect = "admin".equals(username) && PASSWORD.equals(password);
if (passwordCorrect && ipAddressKnown) {
return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(1)).build();
} else if (passwordCorrect) {
return failed().feedback("ip.address.unknown").build();
}
return failed().build();
}
public static boolean containsHeader(HttpServletRequest request) {
return StringUtils.hasText(request.getHeader("X-Forwarded-For"));
}
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge1;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge1 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge1.title";
}
@Override
public String getId() {
return "Challenge1";
}
}

View File

@ -0,0 +1,32 @@
package org.owasp.webgoat.plugin.challenge2;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.plugin.Flag;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
/**
* @author nbaars
* @since 4/6/17.
*/
@AssignmentPath("/challenge/2")
public class Assignment2 extends AssignmentEndpoint {
@RequestMapping(method = RequestMethod.POST)
public
@ResponseBody
AttackResult completed(@RequestParam String checkoutCode) throws IOException {
if (SUPER_COUPON_CODE.equals(checkoutCode)) {
return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
}
return failed().build();
}
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge2;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge2 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge2.title";
}
@Override
public String getId() {
return "Challenge2";
}
}

View File

@ -0,0 +1,68 @@
package org.owasp.webgoat.plugin.challenge2;
import com.beust.jcommander.internal.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Optional;
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
/**
* @author nbaars
* @since 4/6/17.
*/
@RestController
@RequestMapping("challenge-store")
public class ShopEndpoint {
@AllArgsConstructor
private class CheckoutCodes {
@Getter
private List<CheckoutCode> codes = Lists.newArrayList();
public Optional<CheckoutCode> get(String code) {
return codes.stream().filter(c -> c.getCode().equals(code)).findFirst();
}
}
@AllArgsConstructor
@Getter
private class CheckoutCode {
private String code;
private int discount;
}
private CheckoutCodes checkoutCodes;
public ShopEndpoint() {
List<CheckoutCode> codes = Lists.newArrayList();
codes.add(new CheckoutCode("webgoat", 25));
codes.add(new CheckoutCode("owasp", 25));
codes.add(new CheckoutCode("owasp-webgoat", 50));
this.checkoutCodes = new CheckoutCodes(codes);
}
@GetMapping(value = "/coupons/{code}", produces = MediaType.APPLICATION_JSON_VALUE)
public CheckoutCode getDiscountCode(@PathVariable String code) {
if (SUPER_COUPON_CODE.equals(code)) {
return new CheckoutCode(SUPER_COUPON_CODE, 100);
}
return checkoutCodes.get(code).orElse(new CheckoutCode("no", 0));
}
@GetMapping(value = "/coupons", produces = MediaType.APPLICATION_JSON_VALUE)
public CheckoutCodes all() {
List<CheckoutCode> all = Lists.newArrayList();
all.addAll(this.checkoutCodes.getCodes());
all.add(new CheckoutCode(SUPER_COUPON_CODE, 100));
return new CheckoutCodes(all);
}
}

View File

@ -0,0 +1,148 @@
package org.owasp.webgoat.plugin.challenge3;
import com.beust.jcommander.internal.Lists;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.plugin.Flag;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.PostConstruct;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* @author nbaars
* @since 4/8/17.
*/
@AssignmentPath("/challenge/3")
public class Assignment3 extends AssignmentEndpoint {
@Value("${webgoat.server.directory}")
private String webGoatHomeDirectory;
@Autowired
private WebSession webSession;
private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd, HH:mm:ss");
private static final Map<String, EvictingQueue<Comment>> userComments = Maps.newHashMap();
private static final EvictingQueue<Comment> comments = EvictingQueue.create(100);
private static final String secretContents = "Congratulations you may now collect your flag";
static {
comments.add(new Comment("webgoat", DateTime.now().toString(fmt), "Silly cat...."));
comments.add(new Comment("guest", DateTime.now().toString(fmt), "I think I will use this picture in one of my projects."));
comments.add(new Comment("guest", DateTime.now().toString(fmt), "Lol!! :-)."));
}
@PostConstruct
@SneakyThrows
public void copyFile() {
File targetDirectory = new File(webGoatHomeDirectory, "/challenges");
if (!targetDirectory.exists()) {
targetDirectory.mkdir();
}
Files.write(secretContents, new File(targetDirectory, "secret.txt"), Charset.defaultCharset());
}
@RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Collection<Comment> retrieveComments() {
Collection<Comment> allComments = Lists.newArrayList();
Collection<Comment> xmlComments = userComments.get(webSession.getUserName());
if (xmlComments != null) {
allComments.addAll(xmlComments);
}
allComments.addAll(comments);
return allComments;
}
@RequestMapping(method = POST, consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttackResult createNewComment(@RequestBody String commentStr, @RequestHeader("Content-Type") String contentType) throws Exception {
Comment comment = null;
AttackResult attackResult = failed().build();
if (APPLICATION_JSON_VALUE.equals(contentType)) {
comment = parseJson(commentStr);
comment.setDateTime(DateTime.now().toString(fmt));
comment.setUser(webSession.getUserName());
comments.add(comment);
}
if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) {
//Do not show these comments to all users
comment = parseXml(commentStr);
comment.setDateTime(DateTime.now().toString(fmt));
comment.setUser(webSession.getUserName());
EvictingQueue<Comment> comments = userComments.getOrDefault(webSession.getUserName(), EvictingQueue.create(100));
comments.add(comment);
userComments.put(webSession.getUserName(), comments);
}
if (checkSolution(comment)) {
attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
}
return attackResult;
}
private boolean checkSolution(Comment comment) {
if (StringUtils.equals(comment.getText(), secretContents)) {
comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!!");
comments.add(comment);
return true;
}
return false;
}
public static Comment parseXml(String xml) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Comment.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
xif.setProperty(XMLInputFactory.IS_VALIDATING, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (Comment) unmarshaller.unmarshal(xsr);
}
private Comment parseJson(String comment) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(comment, Comment.class);
} catch (IOException e) {
return new Comment();
}
}
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge3;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge3 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge3.title";
}
@Override
public String getId() {
return "Challenge3";
}
}

View File

@ -0,0 +1,24 @@
package org.owasp.webgoat.plugin.challenge3;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author nbaars
* @since 4/8/17.
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Comment {
private String user;
private String dateTime;
private String text;
}

View File

@ -0,0 +1,17 @@
package org.owasp.webgoat.plugin.challenge4;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
/**
* @author nbaars
* @since 5/3/17.
*/
@AssignmentPath("/challenge/4")
@Slf4j
public class Assignment4 extends AssignmentEndpoint {
//just empty, posting the flag will mark the challenge as done as well no need to specify an endpoint here
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge4;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge4 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge4.title";
}
@Override
public String getId() {
return "Challenge4";
}
}

View File

@ -0,0 +1,16 @@
package org.owasp.webgoat.plugin.challenge4;
/**
* @author nbaars
* @since 4/30/17.
*/
public class Views {
interface GuestView {
}
interface UserView extends GuestView {
}
interface AdminView extends UserView {
}
}

View File

@ -0,0 +1,49 @@
package org.owasp.webgoat.plugin.challenge4;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter;
import lombok.Setter;
/**
* @author nbaars
* @since 5/2/17.
*/
@Getter
public class Vote {
@JsonView(Views.GuestView.class)
private final String title;
@JsonView(Views.GuestView.class)
private final String information;
@JsonView(Views.GuestView.class)
private final String imageSmall;
@JsonView(Views.GuestView.class)
private final String imageBig;
@JsonView(Views.UserView.class)
private int numberOfVotes;
@JsonView(Views.AdminView.class)
@Setter
private String flag;
@JsonView(Views.UserView.class)
private boolean votingAllowed = true;
@JsonView(Views.UserView.class)
private long average = 0;
public Vote(String title, String information, String imageSmall, String imageBig, int numberOfVotes, int totalVotes) {
this.title = title;
this.information = information;
this.imageSmall = imageSmall;
this.imageBig = imageBig;
this.numberOfVotes = numberOfVotes;
this.average = calculateStars(totalVotes);
}
public void incrementNumberOfVotes(int totalVotes) {
this.numberOfVotes = this.numberOfVotes + 1;
this.average = calculateStars(totalVotes);
}
private long calculateStars(int totalVotes) {
return Math.round(((double) numberOfVotes / (double) totalVotes) * 4);
}
}

View File

@ -0,0 +1,124 @@
package org.owasp.webgoat.plugin.challenge4;
import com.google.common.collect.Maps;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static java.util.Comparator.comparingLong;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.owasp.webgoat.plugin.Flag.FLAGS;
import static org.owasp.webgoat.plugin.SolutionConstants.JWT_PASSWORD;
/**
* @author nbaars
* @since 4/23/17.
*/
@RestController
@RequestMapping("/votings")
public class VotesEndpoint {
private static String validUsers = "TomJerrySylvester";
private static int totalVotes = 38929;
private Map<String, Vote> votes = Maps.newHashMap();
@PostConstruct
public void initVotes() {
votes.put("Admin lost password", new Vote("Admin lost password",
"In this challenge you will need to help the admin and find the password in order to login",
"challenge1-small.png", "challenge1.png", 36000, totalVotes));
votes.put("Vote for your favourite",
new Vote("Vote for your favourite",
"In this challenge ...",
"challenge5-small.png", "challenge5.png", 30000, totalVotes));
votes.put("Get it for free",
new Vote("Get it for free",
"The objective for this challenge is to buy a Samsung phone for free.",
"challenge2-small.png", "challenge2.png", 20000, totalVotes));
votes.put("Photo comments",
new Vote("Photo comments",
"n this challenge you can comment on the photo you will need to find the flag somewhere.",
"challenge3-small.png", "challenge3.png", 10000, totalVotes));
}
@GetMapping("/login")
public void login(@RequestParam("user") String user, HttpServletResponse response) {
if (validUsers.contains(user)) {
Map<String, Object> claims = Maps.newHashMap();
claims.put("admin", "false");
claims.put("user", user);
String token = Jwts.builder()
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, JWT_PASSWORD)
.compact();
Cookie cookie = new Cookie("access_token", token);
response.addCookie(cookie);
response.setStatus(HttpStatus.OK.value());
} else {
Cookie cookie = new Cookie("access_token", "");
response.addCookie(cookie);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
@GetMapping
public MappingJacksonValue getVotes(@CookieValue(value = "access_token", required = false) String accessToken) {
MappingJacksonValue value = new MappingJacksonValue(votes.values().stream().sorted(comparingLong(Vote::getAverage).reversed()).collect(toList()));
if (StringUtils.isEmpty(accessToken)) {
value.setSerializationView(Views.GuestView.class);
} else {
try {
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
Claims claims = (Claims) jwt.getBody();
String user = (String) claims.get("user");
boolean isAdmin = Boolean.valueOf((String) claims.get("admin"));
if ("Guest".equals(user) || !validUsers.contains(user)) {
value.setSerializationView(Views.GuestView.class);
} else {
((Collection<Vote>) value.getValue()).forEach(v -> v.setFlag(FLAGS.get(4)));
value.setSerializationView(isAdmin ? Views.AdminView.class : Views.UserView.class);
}
} catch (JwtException e) {
value.setSerializationView(Views.GuestView.class);
}
}
return value;
}
@PostMapping(value = "{title}")
@ResponseBody
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<?> vote(@PathVariable String title, @CookieValue(value = "access_token", required = false) String accessToken) {
if (StringUtils.isEmpty(accessToken)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} else {
try {
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
Claims claims = (Claims) jwt.getBody();
String user = (String) claims.get("user");
if (validUsers.contains(user)) {
ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes));
return ResponseEntity.accepted().build();
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
} catch (JwtException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}
}

View File

@ -0,0 +1,92 @@
package org.owasp.webgoat.plugin.challenge5.challenge6;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.plugin.Flag;
import org.owasp.webgoat.session.DatabaseUtilities;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.sql.*;
import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* @author nbaars
* @since 4/8/17.
*/
@AssignmentPath("/challenge/5")
@Slf4j
public class Assignment5 extends AssignmentEndpoint {
//Make it more random at runtime (good luck guessing)
private static final String USERS_TABLE_NAME = "challenge_users_" + RandomStringUtils.randomAlphabetic(16);
@Autowired
private WebSession webSession;
@RequestMapping(method = POST)
@ResponseBody
public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {
Connection connection = DatabaseUtilities.getConnection(webSession);
checkDatabase(connection);
PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = '" + username_login + "' and password = '" + password_login + "'");
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(5)).build();
} else {
return failed().feedback("challenge.close").build();
}
}
private void checkDatabase(Connection connection) throws SQLException {
try {
Statement statement = connection.createStatement();
statement.execute("select 1 from " + USERS_TABLE_NAME);
} catch (SQLException e) {
createChallengeTable(connection);
}
}
private void createChallengeTable(Connection connection) {
Statement statement = null;
try {
statement = connection.createStatement();
String dropTable = "DROP TABLE " + USERS_TABLE_NAME;
statement.executeUpdate(dropTable);
} catch (SQLException e) {
log.info("Delete failed, this does not point to an error table might not have been present...");
}
log.debug("Challenge 5 - Creating tables for users {}", USERS_TABLE_NAME);
try {
String createTableStatement = "CREATE TABLE " + USERS_TABLE_NAME
+ " (" + "userid varchar(250),"
+ "email varchar(30),"
+ "password varchar(30)"
+ ")";
statement.executeUpdate(createTableStatement);
String insertData1 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('larry', 'larry@webgoat.org', 'larryknows')";
String insertData2 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('tom', 'tom@webgoat.org', '" + PASSWORD_TOM + "')";
String insertData3 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**')";
String insertData4 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('eve', 'eve@webgoat.org', '**********')";
statement.executeUpdate(insertData1);
statement.executeUpdate(insertData2);
statement.executeUpdate(insertData3);
statement.executeUpdate(insertData4);
} catch (SQLException e) {
log.error("Unable create table", e);
}
}
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge5.challenge6;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge5 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge5.title";
}
@Override
public String getId() {
return "Challenge5";
}
}

View File

@ -0,0 +1,137 @@
package org.owasp.webgoat.plugin.challenge6;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.plugin.Flag;
import org.owasp.webgoat.session.DatabaseUtilities;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.sql.*;
import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* @author nbaars
* @since 4/8/17.
*/
@AssignmentPath("/challenge/6")
@Slf4j
public class Assignment6 extends AssignmentEndpoint {
//Make it more random at runtime (good luck guessing)
private static final String USERS_TABLE_NAME = "challenge_users_6" + RandomStringUtils.randomAlphabetic(16);
@Autowired
private WebSession webSession;
public Assignment6() {
log.info("Challenge 6 tablename is: {}", USERS_TABLE_NAME);
}
@PutMapping //assignment path is bounded to class so we use different http method :-)
@ResponseBody
public AttackResult registerNewUser(@RequestParam String username_reg, @RequestParam String email_reg, @RequestParam String password_reg) throws Exception {
AttackResult attackResult = checkArguments(username_reg, email_reg, password_reg);
if (attackResult == null) {
Connection connection = DatabaseUtilities.getConnection(webSession);
checkDatabase(connection);
String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(checkUserQuery);
if (resultSet.next()) {
attackResult = failed().feedback("user.exists").feedbackArgs(username_reg).build();
} else {
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO " + USERS_TABLE_NAME + " VALUES (?, ?, ?)");
preparedStatement.setString(1, username_reg);
preparedStatement.setString(2, email_reg);
preparedStatement.setString(3, password_reg);
preparedStatement.execute();
attackResult = success().feedback("user.created").feedbackArgs(username_reg).build();
}
}
return attackResult;
}
private AttackResult checkArguments(String username_reg, String email_reg, String password_reg) {
if (StringUtils.isEmpty(username_reg) || StringUtils.isEmpty(email_reg) || StringUtils.isEmpty(password_reg)) {
return failed().feedback("input.invalid").build();
}
if (username_reg.length() > 250 || email_reg.length() > 30 || password_reg.length() > 30) {
return failed().feedback("input.invalid").build();
}
return null;
}
@RequestMapping(method = POST)
@ResponseBody
public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {
Connection connection = DatabaseUtilities.getConnection(webSession);
checkDatabase(connection);
PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = ? and password = ?");
statement.setString(1, username_login);
statement.setString(2, password_login);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next() && "tom".equals(username_login)) {
return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(6)).build();
} else {
return failed().feedback("challenge.close").build();
}
}
private void checkDatabase(Connection connection) throws SQLException {
try {
Statement statement = connection.createStatement();
statement.execute("select 1 from " + USERS_TABLE_NAME);
} catch (SQLException e) {
createChallengeTable(connection);
}
}
private void createChallengeTable(Connection connection) {
Statement statement = null;
try {
statement = connection.createStatement();
String dropTable = "DROP TABLE " + USERS_TABLE_NAME;
statement.executeUpdate(dropTable);
} catch (SQLException e) {
log.info("Delete failed, this does not point to an error table might not have been present...");
}
log.debug("Challenge 6 - Creating tables for users {}", USERS_TABLE_NAME);
try {
String createTableStatement = "CREATE TABLE " + USERS_TABLE_NAME
+ " (" + "userid varchar(250),"
+ "email varchar(30),"
+ "password varchar(30)"
+ ")";
statement.executeUpdate(createTableStatement);
String insertData1 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('larry', 'larry@webgoat.org', 'larryknows')";
String insertData2 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('tom', 'tom@webgoat.org', '" + PASSWORD_TOM + "')";
String insertData3 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('alice', 'alice@webgoat.org', 'rt*(KJ()LP())$#**')";
String insertData4 = "INSERT INTO " + USERS_TABLE_NAME + " VALUES ('eve', 'eve@webgoat.org', '**********')";
statement.executeUpdate(insertData1);
statement.executeUpdate(insertData2);
statement.executeUpdate(insertData3);
statement.executeUpdate(insertData4);
} catch (SQLException e) {
log.error("Unable create table", e);
}
}
}

View File

@ -0,0 +1,39 @@
package org.owasp.webgoat.plugin.challenge6;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.List;
/**
* @author nbaars
* @since 3/21/17.
*/
public class Challenge6 extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.CHALLENGE;
}
@Override
public List<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge6.title";
}
@Override
public String getId() {
return "Challenge6";
}
}

View File

@ -0,0 +1,33 @@
ul > li{margin-right:25px;font-weight:lighter;cursor:pointer}
li.active{border-bottom:3px solid silver;}
.item-photo{display:flex;justify-content:center;align-items:center;border-right:1px solid #f6f6f6;}
.menu-items{list-style-type:none;font-size:11px;display:inline-flex;margin-bottom:0px;margin-top:20px}
.btn-success{width:100%;border-radius:0px;}
.section{width:100%;margin-left:-15px;padding:2px;padding-left:15px;padding-right:15px;background:#f8f9f9}
.title-price{margin-top:30px;margin-bottom:0px;color:black}
.title-attr{margin-top:0px;margin-bottom:0px;color:black;}
.btn-minus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-right:0px;}
.btn-plus{cursor:pointer;font-size:7px;display:flex;align-items:center;padding:5px;padding-left:10px;padding-right:10px;border:1px solid gray;border-radius:2px;border-left:0px;}
div.section > div {width:100%;display:inline-flex;}
div.section > div > input {margin:0px;padding-left:5px;font-size:10px;padding-right:5px;max-width:18%;text-align:center;}
.attr,.attr2{cursor:pointer;margin-right:5px;height:20px;font-size:11px;padding:2px;border:1px solid gray;border-radius:2px;}
.attr.active,.attr2.active{ border:2px solid orange;}
@media (max-width: 426px) {
.container {margin-top:0px !important;}
.container > .row{padding:0px !important;}
.container > .row > .col-xs-12.col-sm-5{
padding-right:0px ;
}
.container > .row > .col-xs-12.col-sm-9 > div > p{
padding-left:0px !important;
padding-right:0px !important;
}
.container > .row > .col-xs-12.col-sm-9 > div > ul{
padding-left:10px !important;
}
.section{width:104%;}
.menu-items{padding-left:0px;}
}

View File

@ -0,0 +1,75 @@
/* Component: Posts */
.post .post-heading {
height: 95px;
padding: 20px 15px;
}
.post .post-heading .avatar {
width: 60px;
height: 60px;
display: block;
margin-right: 15px;
}
.post .post-heading .meta .title {
margin-bottom: 0;
}
.post .post-heading .meta .title a {
color: black;
}
.post .post-heading .meta .title a:hover {
color: #aaaaaa;
}
.post .post-heading .meta .time {
margin-top: 8px;
color: #999;
}
.post .post-image .image {
width:20%;
height: 40%;
}
.post .post-description {
padding: 5px;
}
.post .post-footer {
border-top: 1px solid #ddd;
padding: 15px;
}
.post .post-footer .input-group-addon a {
color: #454545;
}
.post .post-footer .comments-list {
padding: 0;
margin-top: 20px;
list-style-type: none;
}
.post .post-footer .comments-list .comment {
display: block;
width: 100%;
margin: 20px 0;
}
.post .post-footer .comments-list .comment .avatar {
width: 35px;
height: 35px;
}
.post .post-footer .comments-list .comment .comment-heading {
display: block;
width: 100%;
}
.post .post-footer .comments-list .comment .comment-heading .user {
font-size: 14px;
font-weight: bold;
display: inline;
margin-top: 0;
margin-right: 10px;
}
.post .post-footer .comments-list .comment .comment-heading .time {
font-size: 12px;
color: #aaa;
margin-top: 0;
display: inline;
}
.post .post-footer .comments-list .comment .comment-body {
margin-left: 50px;
}
.post .post-footer .comments-list .comment > .comments-list {
margin-left: 50px;
}

View File

@ -0,0 +1,12 @@
a.list-group-item {
height:auto;
}
a.list-group-item.active small {
color:#fff;
}
.stars {
margin:20px auto 1px;
}
.img-responsive {
min-width: 100%;
}

View File

@ -0,0 +1,96 @@
.panel-login {
border-color: #ccc;
-webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
-moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.2);
}
.panel-login>.panel-heading {
color: #00415d;
background-color: #fff;
border-color: #fff;
text-align:center;
}
.panel-login>.panel-heading a{
text-decoration: none;
color: #666;
font-weight: bold;
font-size: 15px;
-webkit-transition: all 0.1s linear;
-moz-transition: all 0.1s linear;
transition: all 0.1s linear;
}
.panel-login>.panel-heading a.active{
color: #029f5b;
font-size: 18px;
}
.panel-login>.panel-heading hr{
margin-top: 10px;
margin-bottom: 0px;
clear: both;
border: 0;
height: 1px;
background-image: -webkit-linear-gradient(left,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15),rgba(0, 0, 0, 0));
background-image: -moz-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
background-image: -ms-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
background-image: -o-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,0.15),rgba(0,0,0,0));
}
.panel-login input[type="text"],.panel-login input[type="email"],.panel-login input[type="password"] {
height: 45px;
border: 1px solid #ddd;
font-size: 16px;
-webkit-transition: all 0.1s linear;
-moz-transition: all 0.1s linear;
transition: all 0.1s linear;
}
.panel-login input:hover,
.panel-login input:focus {
outline:none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
border-color: #ccc;
}
.btn-login {
background-color: #59B2E0;
outline: none;
color: #fff;
font-size: 14px;
height: auto;
font-weight: normal;
padding: 14px 0;
text-transform: uppercase;
border-color: #59B2E6;
}
.btn-login:hover,
.btn-login:focus {
color: #fff;
background-color: #53A3CD;
border-color: #53A3CD;
}
.forgot-password {
text-decoration: underline;
color: #888;
}
.forgot-password:hover,
.forgot-password:focus {
text-decoration: underline;
color: #666;
}
.btn-register {
background-color: #1CB94E;
outline: none;
color: #fff;
font-size: 14px;
height: auto;
font-weight: normal;
padding: 14px 0;
text-transform: uppercase;
border-color: #1CB94A;
}
.btn-register:hover,
.btn-register:focus {
color: #fff;
background-color: #1CA347;
border-color: #1CA347;
}

View File

@ -1,12 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson --> <div class="adoc-content" th:replace="doc:Challenge_introduction.adoc"></div>
<!-- include content here, or can be placed in another location. Content will be presented via asciidocs files,
which you put in src/main/resources/plugin/lessonplans/{lang}/{fileName}.adoc -->
<div class="adoc-content" th:replace="doc:Challenge_content1.adoc"></div>
</div> </div>
</html> </html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
<img th:src="@{/images/webgoat2.png}" class="img-thumbnail"/>
</div>
<div class="panel-body">
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/1"
style="width: 200px;"
enctype="application/json;charset=UTF-8">
<div class="form-group">
<label for="exampleInputEmail1" th:text="#{username}">Username</label>
<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"
id="exampleInputEmail1" placeholder="Username" name='username' value="admin"/>
</div>
<div class="form-group">
<label for="exampleInputPassword1" th:text="#{password}">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1"
placeholder="Password"
name='password'/>
</div>
<button class="btn btn-primary btn-block" type="submit" th:text="#{sign.in}">Sign in</button>
</form>
</div>
</div>
</div>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_2.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge2.css}"/>
<script th:src="@{/lesson_js/challenge2.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/2"
enctype="application/json;charset=UTF-8">
<input id="discount" type="hidden" value="0"/>
<div class="row">
<div class="col-xs-3 item-photo">
<img style="max-width:100%;" th:src="@{/images/samsung-black.jpg}"/>
</div>
<div class="col-xs-5" style="border:0px solid gray">
<h3>Samsung Galaxy S8</h3>
<h5 style="color:#337ab7"><a href="http://www.samsung.com">Samsung</a> ·
<small style="color:#337ab7">(124421 reviews)</small>
</h5>
<h6 class="title-price">
<small>PRICE</small>
</h6>
<h3 style="margin-top:0px;"><span>US $</span><span id="price">899</span></h3>
<div class="section">
<h6 class="title-attr" style="margin-top:15px;">
<small>COLOR</small>
</h6>
<div>
<div class="attr" style="width:25px;background:lightgrey;"></div>
<div class="attr" style="width:25px;background:black;"></div>
</div>
</div>
<div class="section" style="padding-bottom:5px;">
<h6 class="title-attr">
<small>CAPACITY</small>
</h6>
<div>
<div class="attr2">64 GB</div>
<div class="attr2">128 GB</div>
</div>
</div>
<div class="section" style="padding-bottom:5px;">
<h6 class="title-attr">
<small>QUANTITY</small>
</h6>
<div>
<div class="btn-minus"><span class="glyphicon glyphicon-minus"></span></div>
<input class="quantity" value="1"/>
<div class="btn-plus"><span class="glyphicon glyphicon-plus"></span></div>
</div>
</div>
<div class="section" style="padding-bottom:5px;">
<h6 class="title-attr">
<small>CHECKOUT CODE</small>
</h6>
<!--
Checkout code: webgoat, owasp, owasp-webgoat
-->
<input name="checkoutCode" class="checkoutCode" value=""/>
</div>
<div class="section" style="padding-bottom:20px;">
<button type="submit" class="btn btn-success"><span style="margin-right:20px"
class="glyphicon glyphicon-shopping-cart"
aria-hidden="true"></span>Buy
</button>
<h6><a href="#"><span class="glyphicon glyphicon-heart-empty"
style="cursor:pointer;"></span>
Like</a></h6>
</div>
</div>
</div>
</form>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_3.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge3.css}"/>
<script th:src="@{/lesson_js/challenge3.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<div class="panel post">
<div class="post-heading">
<div class="pull-left image">
<img th:src="@{/images/avatar1.png}"
class="img-circle avatar" alt="user profile image"/>
</div>
<div class="pull-left meta">
<div class="title h5">
<a href="#"><b>John Doe</b></a>
uploaded a photo.
</div>
<h6 class="text-muted time">24 days ago</h6>
</div>
</div>
<div class="post-image">
<img th:src="@{images/cat.jpg}" class="image" alt="image post"/>
</div>
<div class="post-description">
</div>
<div class="post-footer">
<div class="input-group">
<input class="form-control" id="commentInput" placeholder="Add a comment" type="text"/>
<span class="input-group-addon">
<i id="postComment" class="fa fa-edit" style="font-size: 20px"></i>
</span>
</div>
<ul class="comments-list">
<div id="list">
</div>
</ul>
</div>
</div>
</div>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_4.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge4.css}"/>
<script th:src="@{/lesson_js/bootstrap.min.js}" language="JavaScript"></script>
<script th:src="@{/lesson_js/challenge4.js}" language="JavaScript"></script>
<div class="attack-container">
<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="well">
<div class="pull-right">
<div class="dropdown">
<button type="button" data-toggle="dropdown" class="btn btn-default dropdown-toggle">
<i class="fa fa-user"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-left">
<li role="presentation"><a role="menuitem" tabindex="-1"
onclick="javascript:login('Guest')"
th:text="Guest">current</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1"
onclick="javascript:login('Tom')"
th:text="Tom">current</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1"
onclick="javascript:login('Jerry')"
th:text="Jerry">current</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1"
onclick="javascript:login('Sylvester')"
th:text="Sylvester">current</a></li>
</ul>
</div>
<div>
<p class="text-right">Welcome back, <b><span id="name"></span></b></p>
</div>
</div>
<div>
<h3>Vote for your favorite</h3>
</div>
<div id ="votesList" class="list-group">
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_5.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge6.css}"/>
<div class="attack-container">
<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-6">
<div class="panel panel-login">
<div class="panel-heading">
<div class="row">
<div class="col-xs-6">
<a href="#" class="active" id="login-form-link">Login</a>
</div>
</div>
<hr/>
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/5"
enctype="application/json;charset=UTF-8" role="form">
<div class="form-group">
<input type="text" name="username_login" id="username4" tabindex="1"
class="form-control" placeholder="Username" value=""/>
</div>
<div class="form-group">
<input type="password" name="password_login" id="password4" tabindex="2"
class="form-control" placeholder="Password"/>
</div>
<div class="form-group text-center">
<input type="checkbox" tabindex="3" class="" name="remember" id="remember"/>
<label for="remember"> Remember me</label>
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<input type="submit" name="login-submit" id="login-submit"
tabindex="4" class="form-control btn-primary"
value="Log In"/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-lg-12">
<div class="text-center">
<a href="#" tabindex="5" class="forgot-password">Forgot
Password?</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_6.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge6.css}"/>
<script th:src="@{/lesson_js/challenge6.js}" language="JavaScript"></script>
<div class="attack-container">
<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-6">
<div class="panel panel-login">
<div class="panel-heading">
<div class="row">
<div class="col-xs-6">
<a href="#" class="active" id="login-form-link">Login</a>
</div>
<div class="col-xs-6">
<a href="#" id="register-form-link">Register</a>
</div>
</div>
<hr/>
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/6"
enctype="application/json;charset=UTF-8" role="form">
<div class="form-group">
<input type="text" name="username_login" id="username4" tabindex="1"
class="form-control" placeholder="Username" value=""/>
</div>
<div class="form-group">
<input type="password" name="password_login" id="password4" tabindex="2"
class="form-control" placeholder="Password"/>
</div>
<div class="form-group text-center">
<input type="checkbox" tabindex="3" class="" name="remember" id="remember"/>
<label for="remember"> Remember me</label>
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<input type="submit" name="login-submit" id="login-submit"
tabindex="4" class="form-control btn-primary"
value="Log In"/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-lg-12">
<div class="text-center">
<a href="#" tabindex="5" class="forgot-password">Forgot
Password?</a>
</div>
</div>
</div>
</div>
</form>
<form id="register-form" class="attack-form" accept-charset="UNKNOWN"
method="PUT" name="form"
action="/WebGoat/challenge/6"
enctype="application/json;charset=UTF-8" style="display: none;" role="form">
<div class="form-group">
<input type="text" name="username_reg" id="username" tabindex="1"
class="form-control" placeholder="Username" value=""/>
</div>
<div class="form-group">
<input type="email" name="email_reg" id="email" tabindex="1"
class="form-control" placeholder="Email Address" value=""/>
</div>
<div class="form-group">
<input type="password" name="password_reg" id="password" tabindex="2"
class="form-control" placeholder="Password"/>
</div>
<div class="form-group">
<input type="password" name="confirm_password_reg" id="confirm-password"
tabindex="2" class="form-control" placeholder="Confirm Password"/>
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<input type="submit" name="register-submit" id="register-submit"
tabindex="4" class="form-control btn btn-primary"
value="Register Now"/>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -1 +1,18 @@
challenge.title=WebGoat Challenge challenge0.title=WebGoat Challenge
challenge1.title=Admin lost password
challenge2.title=Get it for free
challenge3.title=Photo comments
challenge4.title=Voting
challenge5.title=Without password
challenge6.title=Creating a new account
challenge.solved=Congratulations, you solved the challenge. Here is your flag: {0}
challenge.close=This is not the correct password for tom, please try again.
user.exists=User {0} already exists please try to register with a different username.
user.created=User {0} created, please proceed to the login page.
input.invalid=Input for user, email and/or password is empty or too long, please fill in all field and/or limit all fields to 30 characters.
challenge.flag.correct=Congratulations you have solved the challenge!!
challenge.flag.incorrect=Sorry this is not the correct flag, please try again.
ip.address.unknown=IP address unknown, e-mail has been sent.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Some files were not shown because too many files have changed in this diff Show More