- Added new challenges
- Added new webapplication called WebWolf to make attacks more realistic - Added WebWolf lesson to explain the concepts behind this new application
2
pom.xml
@ -172,9 +172,11 @@
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>webgoat-commons</module>
|
||||
<module>webgoat-container</module>
|
||||
<module>webgoat-lessons</module>
|
||||
<module>webgoat-server</module>
|
||||
<module>webwolf</module>
|
||||
</modules>
|
||||
|
||||
<distributionManagement>
|
||||
|
37
webgoat-commons/pom.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>webgoat-commons</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>ISO-8859-1</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,15 @@
|
||||
package org.owasp.webgoat.login;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class LoginEvent {
|
||||
private String user;
|
||||
private String cookie;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package org.owasp.webgoat.login;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class LogoutEvent {
|
||||
private String user;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.owasp.webgoat.mail;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
public class IncomingMailEvent {
|
||||
|
||||
private LocalDateTime time;
|
||||
private String contents;
|
||||
private String sender;
|
||||
private String title;
|
||||
private String recipient;
|
||||
}
|
@ -34,6 +34,23 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>local</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</profile>
|
||||
<profile>
|
||||
<id>ctf</id>
|
||||
<!-- Connect to real mongodb -->
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
@ -132,6 +149,19 @@
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@ -144,6 +174,19 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jms</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctorj</artifactId>
|
||||
@ -153,10 +196,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
|
@ -1,11 +1,24 @@
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.owasp.webgoat.login.LoginEvent;
|
||||
import org.owasp.webgoat.session.Course;
|
||||
import org.owasp.webgoat.users.WebGoatUser;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
/**
|
||||
* *************************************************************************************************
|
||||
* <p>
|
||||
@ -41,19 +54,38 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
* @since October 28, 2003
|
||||
*/
|
||||
@Controller
|
||||
@AllArgsConstructor
|
||||
public class HammerHead {
|
||||
|
||||
private final Course course;
|
||||
|
||||
public HammerHead(Course course) {
|
||||
this.course = course;
|
||||
}
|
||||
private JmsTemplate jmsTemplate;
|
||||
|
||||
/**
|
||||
* Entry point for WebGoat, redirects to the first lesson found within the course.
|
||||
*/
|
||||
@RequestMapping(path = "/attack", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
public ModelAndView attack() {
|
||||
public ModelAndView attack(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
|
||||
sendUserLoggedInMessage(request, response, authentication);
|
||||
return new ModelAndView("redirect:" + "start.mvc" + course.getFirstLesson().getLink());
|
||||
}
|
||||
|
||||
private void sendUserLoggedInMessage(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
WebGoatUser user = (WebGoatUser) authentication.getPrincipal();
|
||||
getWebGoatCookie(request).ifPresent(c -> {
|
||||
jmsTemplate.convertAndSend("webgoat", new LoginEvent(user.getUsername(), c.getValue()), m -> {
|
||||
m.setStringProperty("type", LoginEvent.class.getSimpleName());
|
||||
return m;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<Cookie> getWebGoatCookie(HttpServletRequest request) {
|
||||
for (Cookie c : request.getCookies()) {
|
||||
if (c.getName().equals("JSESSIONID")) {
|
||||
return of(c);
|
||||
}
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.activemq.broker.BrokerService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.converter.MessageType;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Configuration
|
||||
public class JmsConfig {
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
public BrokerService broker() throws Exception {
|
||||
final BrokerService broker = new BrokerService();
|
||||
broker.addConnector("tcp://localhost:61616");
|
||||
broker.addConnector("vm://localhost");
|
||||
broker.setPersistent(false);
|
||||
return broker;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConverter jacksonJmsMessageConverter(ObjectMapper objectMapper) {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
converter.setTargetType(MessageType.TEXT);
|
||||
converter.setObjectMapper(objectMapper);
|
||||
converter.setTypeIdPropertyName("_type");
|
||||
return converter;
|
||||
}
|
||||
}
|
@ -39,11 +39,9 @@ import org.owasp.webgoat.session.LabelDebugger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
@ -154,13 +152,9 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||
return slr;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HammerHead hammerHead(Course course) {
|
||||
return new HammerHead(course);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LabelDebugger labelDebugger() {
|
||||
return new LabelDebugger();
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,6 @@
|
||||
*/
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.catalina.Context;
|
||||
import org.owasp.webgoat.plugins.PluginEndpointPublisher;
|
||||
@ -49,10 +48,8 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon
|
||||
import org.springframework.boot.web.support.SpringBootServletInitializer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
@ -70,15 +67,6 @@ public class WebGoat extends SpringBootServletInitializer {
|
||||
SpringApplication.run(WebGoat.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public Jackson2ObjectMapperBuilder jacksonBuilder() {
|
||||
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
|
||||
builder.indentOutput(true);
|
||||
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Bean(name = "pluginTargetDirectory")
|
||||
public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) {
|
||||
return new File(webgoatHome);
|
||||
@ -93,7 +81,7 @@ public class WebGoat extends SpringBootServletInitializer {
|
||||
@Bean
|
||||
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||
public UserSessionData userSessionData() {
|
||||
return new UserSessionData("test","data");
|
||||
return new UserSessionData("test", "data");
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -31,6 +31,7 @@
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.owasp.webgoat.login.LogoutHandler;
|
||||
import org.owasp.webgoat.users.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -52,6 +53,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final UserService userDetailsService;
|
||||
private final LogoutHandler logoutHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
@ -69,8 +71,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.passwordParameter("password")
|
||||
.permitAll();
|
||||
security.and()
|
||||
.logout()
|
||||
.permitAll();
|
||||
.logout().deleteCookies("JSESSIONID").invalidateHttpSession(true)
|
||||
.permitAll().logoutSuccessHandler(logoutHandler);
|
||||
security.and().csrf().disable();
|
||||
|
||||
http.headers().cacheControl().disable();
|
||||
|
@ -108,4 +108,8 @@ public abstract class AssignmentEndpoint extends Endpoint {
|
||||
protected AttackResult.AttackResultBuilder failed() {
|
||||
return AttackResult.builder(messages).lessonCompleted(false).feedback("assignment.not.solved");
|
||||
}
|
||||
|
||||
protected AttackResult.AttackResultBuilder informationMessage() {
|
||||
return AttackResult.builder(messages).lessonCompleted(false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package org.owasp.webgoat.login;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.owasp.webgoat.users.WebGoatUser;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Component
|
||||
public class LogoutHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
private JmsTemplate jmsTemplate;
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
|
||||
if (authentication != null) {
|
||||
WebGoatUser user = (WebGoatUser) authentication.getPrincipal();
|
||||
jmsTemplate.convertAndSend("webgoat", new LogoutEvent(user.getUsername()), m -> {
|
||||
m.setStringProperty("type", LogoutEvent.class.getSimpleName());
|
||||
return m;
|
||||
});
|
||||
}
|
||||
super.onLogoutSuccess(httpServletRequest, httpServletResponse, authentication);
|
||||
}
|
||||
|
||||
private Optional<Cookie> findSessionCookie(Cookie[] cookies) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if ("JSESSIONID".equals(cookie.getName())) {
|
||||
return Optional.of(cookie);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -71,6 +71,7 @@ public class PluginsLoader {
|
||||
NewLesson lesson = null;
|
||||
try {
|
||||
lesson = (NewLesson) c.newInstance();
|
||||
log.trace("Lesson loaded: {}", lesson.getId());
|
||||
} catch (Exception e) {
|
||||
log.error("Error while loading:" + c, e);
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
@ -32,29 +32,16 @@ public class RegistrationController {
|
||||
}
|
||||
|
||||
@PostMapping("/register.mvc")
|
||||
public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult) {
|
||||
@SneakyThrows
|
||||
public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult, HttpServletRequest request) {
|
||||
userValidator.validate(userForm, bindingResult);
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
return "registration";
|
||||
}
|
||||
userService.addUser(userForm.getUsername(), userForm.getPassword());
|
||||
autologin(userForm.getUsername(), userForm.getPassword());
|
||||
request.login(userForm.getUsername(), userForm.getPassword());
|
||||
|
||||
return "redirect:/attack";
|
||||
}
|
||||
|
||||
private void autologin(String username, String password) {
|
||||
WebGoatUser user = userService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
|
||||
|
||||
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
||||
|
||||
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
|
||||
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
|
||||
log.debug("Login for {} successfully!", username);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -45,10 +45,11 @@ public class Scoreboard {
|
||||
}
|
||||
|
||||
private List<String> challengesSolved(UserTracker userTracker) {
|
||||
List<String> challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5");
|
||||
List<String> challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5", "Challenge6", "Challenge7", "Challenge8", "Challenge9");
|
||||
return challenges.stream()
|
||||
.map(c -> userTracker.getLessonTracker(c))
|
||||
.filter(l -> l.isPresent()).map(l -> l.get())
|
||||
.filter(l -> l.isLessonSolved())
|
||||
.map(l -> l.getLessonName())
|
||||
.map(l -> toLessonTitle(l))
|
||||
.collect(Collectors.toList());
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.owasp.webgoat.users;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/15/17.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class UserSession {
|
||||
|
||||
private WebGoatUser webGoatUser;
|
||||
@Id
|
||||
private String sessionId;
|
||||
}
|
@ -29,7 +29,12 @@ webgoat.database.driver=org.hsqldb.jdbcDriver
|
||||
webgoat.database.connection.string=jdbc:hsqldb:mem:{USER}
|
||||
webgoat.default.language=en
|
||||
|
||||
spring.jackson.serialization.indent_output=true
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
|
||||
spring.activemq.brokerUrl=tcp://localhost:61616
|
||||
|
||||
spring.data.mongodb.port=27017
|
||||
spring.data.mongodb.database=webgoat
|
||||
spring.mongodb.embedded.storage.databaseDir=${webgoat.user.directory}/mongodb/
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
#
|
||||
|
||||
lesson.completed=Congratulations. You have successfully completed this lesson.
|
||||
assignment.solved=Congratulations. You have successfully complete the assignment.
|
||||
assignment.solved=Congratulations. You have successfully completed the assignment.
|
||||
assignment.not.solved=Sorry the solution is not correct, please try again.
|
||||
RestartLesson=Restart this Lesson
|
||||
SolutionVideos=Solution Videos
|
||||
|
After Width: | Height: | Size: 56 KiB |
@ -1146,7 +1146,7 @@ div.captured-flag {
|
||||
}
|
||||
|
||||
.appseceu-banner {
|
||||
background: url('img/appseceu-17.png') no-repeat 0px 0px;
|
||||
background: url('img/owasp_logo.jpg') no-repeat 0px 0px;
|
||||
height: 117px;
|
||||
width: 1268px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="scoreboard-title">WebGoat Challenge - AppSec EU 2017</div>
|
||||
<div class="appseceu-banner">banner here</div>
|
||||
<div class="scoreboard-title">WebGoat Challenge</div>
|
||||
<div class="appseceu-banner"></div>
|
||||
<table class="scoreboard-table">
|
||||
<% _.each(rankings, function(userRanking, index) { %>
|
||||
<tr>
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.owasp.webgoat.plugins;
|
||||
|
||||
import org.apache.activemq.broker.BrokerService;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/30/17.
|
||||
*/
|
||||
@Configuration
|
||||
public class JmsTestConfig {
|
||||
|
||||
@Bean
|
||||
public BrokerService broker() throws Exception {
|
||||
return Mockito.mock(BrokerService.class);
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.embedded.LocalServerPort;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
@ -23,6 +24,7 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestPropertySource(locations = "classpath:/application-test.properties")
|
||||
@Import(JmsTestConfig.class)
|
||||
public abstract class LessonTest {
|
||||
|
||||
@LocalServerPort
|
||||
|
@ -45,7 +45,7 @@ public class Flag extends Endpoint {
|
||||
|
||||
@PostConstruct
|
||||
public void initFlags() {
|
||||
IntStream.range(1, 7).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString()));
|
||||
IntStream.range(1, 10).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString()));
|
||||
FLAGS.entrySet().stream().forEach(e -> log.debug("Flag {} {}", e.getKey(), e.getValue()));
|
||||
}
|
||||
|
||||
|
@ -14,5 +14,7 @@ public interface SolutionConstants {
|
||||
String PASSWORD_TOM = "thisisasecretfortomonly";
|
||||
String PASSWORD_LARRY = "larryknows";
|
||||
String JWT_PASSWORD = "victory";
|
||||
|
||||
String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2";
|
||||
String PASSWORD_TOM_9 = "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom";
|
||||
String TOM_EMAIL = "tom@webgoat-cloud.org";
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ 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 lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
@ -45,6 +45,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
* @since 4/8/17.
|
||||
*/
|
||||
@AssignmentPath("/challenge/3")
|
||||
@Slf4j
|
||||
public class Assignment3 extends AssignmentEndpoint {
|
||||
|
||||
@Value("${webgoat.server.directory}")
|
||||
@ -66,10 +67,11 @@ public class Assignment3 extends AssignmentEndpoint {
|
||||
@PostConstruct
|
||||
@SneakyThrows
|
||||
public void copyFile() {
|
||||
File targetDirectory = new File(webGoatHomeDirectory, "/challenges");
|
||||
File targetDirectory = new File(webGoatHomeDirectory);
|
||||
if (!targetDirectory.exists()) {
|
||||
targetDirectory.mkdir();
|
||||
}
|
||||
log.info("Copied secret.txt to: {}", targetDirectory);
|
||||
Files.write(secretContents, new File(targetDirectory, "secret.txt"), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
@ -106,14 +108,14 @@ public class Assignment3 extends AssignmentEndpoint {
|
||||
userComments.put(webSession.getUserName(), comments);
|
||||
}
|
||||
if (checkSolution(comment)) {
|
||||
attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
|
||||
attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(3)).build();
|
||||
}
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
private boolean checkSolution(Comment comment) {
|
||||
if (StringUtils.equals(comment.getText(), secretContents)) {
|
||||
comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!!");
|
||||
if (comment.getText().contains(secretContents)) {
|
||||
comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!! Check your original response where you posted the XXE attack ");
|
||||
comments.add(comment);
|
||||
return true;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ 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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@ -38,6 +39,13 @@ public class Assignment5 extends AssignmentEndpoint {
|
||||
Connection connection = DatabaseUtilities.getConnection(webSession);
|
||||
checkDatabase(connection);
|
||||
|
||||
if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) {
|
||||
return failed().feedback("required4").build();
|
||||
}
|
||||
if (!"Larry".equals(username_login)) {
|
||||
return failed().feedback("user.not.larry").feedbackArgs(username_login).build();
|
||||
}
|
||||
|
||||
PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = '" + username_login + "' and password = '" + password_login + "'");
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
|
@ -0,0 +1,84 @@
|
||||
package org.owasp.webgoat.plugin.challenge7;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.mail.IncomingMailEvent;
|
||||
import org.owasp.webgoat.plugin.SolutionConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.owasp.webgoat.plugin.Flag.FLAGS;
|
||||
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/7")
|
||||
@Slf4j
|
||||
public class Assignment7 extends AssignmentEndpoint {
|
||||
|
||||
private static final String TEMPLATE = "Hi, you requested a password reset link, please use this " +
|
||||
"<a target='_blank' href='%s:8080/WebGoat/challenge/7/reset-password/%s'>link</a> to reset your password." +
|
||||
"\n \n\n" +
|
||||
"If you did not request this password change you can ignore this message." +
|
||||
"\n" +
|
||||
"If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" +
|
||||
"\n\n" +
|
||||
"Kind regards, \nTeam WebGoat";
|
||||
|
||||
@Autowired
|
||||
private JmsTemplate jmsTemplate;
|
||||
|
||||
@GetMapping("/reset-password/{link}")
|
||||
public ResponseEntity<String> resetPassword(@PathVariable(value = "link") String link) {
|
||||
if (link.equals(SolutionConstants.ADMIN_PASSWORD_LINK)) {
|
||||
return ResponseEntity.accepted().body("<h1>Success!!</h1>" +
|
||||
"<img src='/WebGoat/images/hi-five-cat.jpg'>" +
|
||||
"<br/><br/>Here is your flag: " + "<b>" + FLAGS.get(7) + "</b>");
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("That is not the reset link for admin");
|
||||
}
|
||||
|
||||
@RequestMapping(method = POST)
|
||||
@ResponseBody
|
||||
public AttackResult sendPasswordResetLink(@RequestParam String email, HttpServletRequest request) throws URISyntaxException {
|
||||
if (StringUtils.hasText(email)) {
|
||||
String username = email.substring(0, email.indexOf("@"));
|
||||
if (StringUtils.hasText(username)) {
|
||||
URI uri = new URI(request.getRequestURL().toString());
|
||||
IncomingMailEvent mail = IncomingMailEvent.builder()
|
||||
.title("Your password reset link for challenge 7")
|
||||
.contents(String.format(TEMPLATE, uri.getScheme() + "://" + uri.getHost(), new PasswordResetLink().createPasswordReset(username, "webgoat")))
|
||||
.sender("password-reset@webgoat-cloud.net")
|
||||
.recipient(username)
|
||||
.time(LocalDateTime.now()).build();
|
||||
jmsTemplate.convertAndSend("mailbox", mail);
|
||||
}
|
||||
}
|
||||
return success().feedback("email.send").feedbackArgs(email).build();
|
||||
}
|
||||
|
||||
@RequestMapping(method = GET, value = "/.git", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
@ResponseBody
|
||||
@SneakyThrows
|
||||
public ClassPathResource git() {
|
||||
return new ClassPathResource("challenge7/git.zip");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package org.owasp.webgoat.plugin.challenge7;
|
||||
|
||||
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 Challenge7 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 "challenge7.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "Challenge7";
|
||||
}
|
||||
}
|
@ -0,0 +1,689 @@
|
||||
package org.owasp.webgoat.plugin.challenge7;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* MD5 hash generator.
|
||||
* More information about this class is available from <a target="_top" href=
|
||||
* "http://ostermiller.org/utils/MD5.html">ostermiller.org</a>.
|
||||
* <p>
|
||||
* This class takes as input a message of arbitrary length and produces
|
||||
* as output a 128-bit "fingerprint" or "message digest" of the input.
|
||||
* It is conjectured that it is computationally infeasible to produce
|
||||
* two messages having the same message digest, or to produce any
|
||||
* message having a given pre-specified target message digest. The MD5
|
||||
* algorithm is intended for digital signature applications, where a
|
||||
* large file must be "compressed" in a secure manner before being
|
||||
* encrypted with a private (secret) key under a public-key cryptosystem
|
||||
* such as RSA.
|
||||
* <p>
|
||||
* For more information see RFC1321.
|
||||
*
|
||||
* @author Santeri Paavolainen http://santtu.iki.fi/md5/
|
||||
* @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public class MD5 {
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public MD5() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Command line program that will take files as arguments
|
||||
* and output the MD5 sum for each file.
|
||||
*
|
||||
* @param args command line arguments
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
System.err.println("Please specify a file.");
|
||||
} else {
|
||||
for (String element : args) {
|
||||
try {
|
||||
System.out.println(MD5.getHashString(new File(element)) + " " + element);
|
||||
} catch (IOException x) {
|
||||
System.err.println(x.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this hash sum as an array of 16 bytes.
|
||||
*
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public byte[] getHash() {
|
||||
if (!finalState.valid) {
|
||||
finalState.copy(workingState);
|
||||
long bitCount = finalState.bitCount;
|
||||
// Compute the number of left over bits
|
||||
int leftOver = (int) (((bitCount >>> 3)) & 0x3f);
|
||||
// Compute the amount of padding to add based on number of left over bits.
|
||||
int padlen = (leftOver < 56) ? (56 - leftOver) : (120 - leftOver);
|
||||
// add the padding
|
||||
update(finalState, padding, 0, padlen);
|
||||
// add the length (computed before padding was added)
|
||||
update(finalState, encode(bitCount), 0, 8);
|
||||
finalState.valid = true;
|
||||
}
|
||||
// make a copy of the hash before returning it.
|
||||
return encode(finalState.state, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 32-character hex representation of this hash.
|
||||
*
|
||||
* @return String representation of this object's hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public String getHashString() {
|
||||
return toHex(this.getHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given byte array.
|
||||
*
|
||||
* @param b byte array for which an MD5 hash is desired.
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static byte[] getHash(byte[] b) {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(b);
|
||||
return md5.getHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given byte array.
|
||||
*
|
||||
* @param b byte array for which an MD5 hash is desired.
|
||||
* @return 32-character hex representation the data's MD5 hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static String getHashString(byte[] b) {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(b);
|
||||
return md5.getHashString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash the data on the given InputStream.
|
||||
*
|
||||
* @param in byte array for which an MD5 hash is desired.
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static byte[] getHash(InputStream in) throws IOException {
|
||||
MD5 md5 = new MD5();
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
md5.update(buffer, read);
|
||||
}
|
||||
return md5.getHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash the data on the given InputStream.
|
||||
*
|
||||
* @param in byte array for which an MD5 hash is desired.
|
||||
* @return 32-character hex representation the data's MD5 hash.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static String getHashString(InputStream in) throws IOException {
|
||||
MD5 md5 = new MD5();
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
md5.update(buffer, read);
|
||||
}
|
||||
return md5.getHashString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given file.
|
||||
*
|
||||
* @param f file for which an MD5 hash is desired.
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static byte[] getHash(File f) throws IOException {
|
||||
InputStream is = new FileInputStream(f);
|
||||
byte[] hash = getHash(is);
|
||||
is.close();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given file.
|
||||
*
|
||||
* @param f file array for which an MD5 hash is desired.
|
||||
* @return 32-character hex representation the data's MD5 hash.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static String getHashString(File f) throws IOException {
|
||||
InputStream is = new FileInputStream(f);
|
||||
String hash = getHashString(is);
|
||||
is.close();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given String.
|
||||
* The string is converted to bytes using the current
|
||||
* platform's default character encoding.
|
||||
*
|
||||
* @param s String for which an MD5 hash is desired.
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static byte[] getHash(String s) {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(s);
|
||||
return md5.getHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given String.
|
||||
* The string is converted to bytes using the current
|
||||
* platform's default character encoding.
|
||||
*
|
||||
* @param s String for which an MD5 hash is desired.
|
||||
* @return 32-character hex representation the data's MD5 hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static String getHashString(String s) {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(s);
|
||||
return md5.getHashString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given String.
|
||||
*
|
||||
* @param s String for which an MD5 hash is desired.
|
||||
* @param enc The name of a supported character encoding.
|
||||
* @return Array of 16 bytes, the hash of all updated bytes.
|
||||
* @throws UnsupportedEncodingException If the named encoding is not supported.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static byte[] getHash(String s, String enc) throws UnsupportedEncodingException {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(s, enc);
|
||||
return md5.getHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MD5 hash of the given String.
|
||||
*
|
||||
* @param s String for which an MD5 hash is desired.
|
||||
* @param enc The name of a supported character encoding.
|
||||
* @return 32-character hex representation the data's MD5 hash.
|
||||
* @throws UnsupportedEncodingException If the named encoding is not supported.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public static String getHashString(String s, String enc) throws UnsupportedEncodingException {
|
||||
MD5 md5 = new MD5();
|
||||
md5.update(s, enc);
|
||||
return md5.getHashString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the MD5 sum to its initial state.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void reset() {
|
||||
workingState.reset();
|
||||
finalState.valid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 32-character hex representation of this hash.
|
||||
*
|
||||
* @return String representation of this object's hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getHashString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with the given data.
|
||||
* <p>
|
||||
* A state may be passed into this method so that we can add padding
|
||||
* and finalize a md5 hash without limiting our ability to update
|
||||
* more data later.
|
||||
* <p>
|
||||
* If length bytes are not available to be hashed, as many bytes as
|
||||
* possible will be hashed.
|
||||
*
|
||||
* @param state Which state is updated.
|
||||
* @param buffer Array of bytes to be hashed.
|
||||
* @param offset Offset to buffer array.
|
||||
* @param length number of bytes to hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private void update(MD5State state, byte buffer[], int offset, int length) {
|
||||
|
||||
finalState.valid = false;
|
||||
|
||||
// if length goes beyond the end of the buffer, cut it short.
|
||||
if ((length + offset) > buffer.length) {
|
||||
length = buffer.length - offset;
|
||||
}
|
||||
|
||||
// compute number of bytes mod 64
|
||||
// this is what we have sitting in a buffer
|
||||
// that have not been hashed yet
|
||||
int index = (int) (state.bitCount >>> 3) & 0x3f;
|
||||
|
||||
// add the length to the count (translate bytes to bits)
|
||||
state.bitCount += length << 3;
|
||||
|
||||
int partlen = 64 - index;
|
||||
|
||||
int i = 0;
|
||||
if (length >= partlen) {
|
||||
System.arraycopy(buffer, offset, state.buffer, index, partlen);
|
||||
transform(state, decode(state.buffer, 64, 0));
|
||||
for (i = partlen; (i + 63) < length; i += 64) {
|
||||
transform(state, decode(buffer, 64, i));
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// buffer remaining input
|
||||
if (i < length) {
|
||||
for (int start = i; i < length; i++) {
|
||||
state.buffer[index + i - start] = buffer[i + offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with the given data.
|
||||
* <p>
|
||||
* If length bytes are not available to be hashed, as many bytes as
|
||||
* possible will be hashed.
|
||||
*
|
||||
* @param buffer Array of bytes to be hashed.
|
||||
* @param offset Offset to buffer array.
|
||||
* @param length number of bytes to hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(byte buffer[], int offset, int length) {
|
||||
update(workingState, buffer, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with the given data.
|
||||
* <p>
|
||||
* If length bytes are not available to be hashed, as many bytes as
|
||||
* possible will be hashed.
|
||||
*
|
||||
* @param buffer Array of bytes to be hashed.
|
||||
* @param length number of bytes to hash.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(byte buffer[], int length) {
|
||||
update(buffer, 0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with the given data.
|
||||
*
|
||||
* @param buffer Array of bytes to be hashed.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(byte buffer[]) {
|
||||
update(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this hash with a single byte.
|
||||
*
|
||||
* @param b byte to be hashed.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(byte b) {
|
||||
byte buffer[] = new byte[1];
|
||||
buffer[0] = b;
|
||||
update(buffer, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with a String.
|
||||
* The string is converted to bytes using the current
|
||||
* platform's default character encoding.
|
||||
*
|
||||
* @param s String to be hashed.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(String s) {
|
||||
update(s.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this hash with a String.
|
||||
*
|
||||
* @param s String to be hashed.
|
||||
* @param enc The name of a supported character encoding.
|
||||
* @throws UnsupportedEncodingException If the named encoding is not supported.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
public void update(String s, String enc) throws UnsupportedEncodingException {
|
||||
update(s.getBytes(enc));
|
||||
}
|
||||
|
||||
/**
|
||||
* The current state from which the hash sum
|
||||
* can be computed or updated.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private MD5State workingState = new MD5State();
|
||||
|
||||
/**
|
||||
* Cached copy of the final MD5 hash sum. This is created when
|
||||
* the hash is requested and it is invalidated when the hash
|
||||
* is updated.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private MD5State finalState = new MD5State();
|
||||
|
||||
/**
|
||||
* Temporary buffer cached here for performance reasons.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private int[] decodeBuffer = new int[16];
|
||||
|
||||
/**
|
||||
* 64 bytes of padding that can be added if the length
|
||||
* is not divisible by 64.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private static final byte padding[] = {
|
||||
(byte) 0x80, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains internal state of the MD5 class.
|
||||
* Passes MD5 test suite as defined in RFC1321.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private class MD5State {
|
||||
|
||||
/**
|
||||
* True if this state is valid.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private boolean valid = true;
|
||||
|
||||
/**
|
||||
* Reset to initial state.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private void reset() {
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
state[3] = 0x10325476;
|
||||
|
||||
bitCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 128-byte state
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private int state[] = new int[4];
|
||||
|
||||
/**
|
||||
* 64-bit count of the number of bits that have been hashed.
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private long bitCount;
|
||||
|
||||
/**
|
||||
* 64-byte buffer (512 bits) for storing to-be-hashed characters
|
||||
*
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private byte buffer[] = new byte[64];
|
||||
|
||||
private MD5State() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this state to be exactly the same as some other.
|
||||
*
|
||||
* @param from state to copy from.
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private void copy(MD5State from) {
|
||||
System.arraycopy(from.buffer, 0, this.buffer, 0, this.buffer.length);
|
||||
System.arraycopy(from.state, 0, this.state, 0, this.state.length);
|
||||
this.valid = from.valid;
|
||||
this.bitCount = from.bitCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turns array of bytes into string representing each byte as
|
||||
* a two digit unsigned hex number.
|
||||
*
|
||||
* @param hash Array of bytes to convert to hex-string
|
||||
* @return Generated hex string
|
||||
* @since ostermillerutils 1.00.00
|
||||
*/
|
||||
private static String toHex(byte hash[]) {
|
||||
StringBuffer buf = new StringBuffer(hash.length * 2);
|
||||
for (byte element : hash) {
|
||||
int intVal = element & 0xff;
|
||||
if (intVal < 0x10) {
|
||||
// append a zero before a one digit hex
|
||||
// number to make it two digits.
|
||||
buf.append("0");
|
||||
}
|
||||
buf.append(Integer.toHexString(intVal));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static int FF(int a, int b, int c, int d, int x, int s, int ac) {
|
||||
a += ((b & c) | (~b & d));
|
||||
a += x;
|
||||
a += ac;
|
||||
//return rotateLeft(a, s) + b;
|
||||
a = (a << s) | (a >>> (32 - s));
|
||||
return a + b;
|
||||
}
|
||||
|
||||
private static int GG(int a, int b, int c, int d, int x, int s, int ac) {
|
||||
a += ((b & d) | (c & ~d));
|
||||
a += x;
|
||||
a += ac;
|
||||
//return rotateLeft(a, s) + b;
|
||||
a = (a << s) | (a >>> (32 - s));
|
||||
return a + b;
|
||||
}
|
||||
|
||||
private static int HH(int a, int b, int c, int d, int x, int s, int ac) {
|
||||
a += (b ^ c ^ d);
|
||||
a += x;
|
||||
a += ac;
|
||||
//return rotateLeft(a, s) + b;
|
||||
a = (a << s) | (a >>> (32 - s));
|
||||
return a + b;
|
||||
}
|
||||
|
||||
private static int II(int a, int b, int c, int d, int x, int s, int ac) {
|
||||
a += (c ^ (b | ~d));
|
||||
a += x;
|
||||
a += ac;
|
||||
//return rotateLeft(a, s) + b;
|
||||
a = (a << s) | (a >>> (32 - s));
|
||||
return a + b;
|
||||
}
|
||||
|
||||
private static byte[] encode(long l) {
|
||||
byte[] out = new byte[8];
|
||||
out[0] = (byte) (l & 0xff);
|
||||
out[1] = (byte) ((l >>> 8) & 0xff);
|
||||
out[2] = (byte) ((l >>> 16) & 0xff);
|
||||
out[3] = (byte) ((l >>> 24) & 0xff);
|
||||
out[4] = (byte) ((l >>> 32) & 0xff);
|
||||
out[5] = (byte) ((l >>> 40) & 0xff);
|
||||
out[6] = (byte) ((l >>> 48) & 0xff);
|
||||
out[7] = (byte) ((l >>> 56) & 0xff);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static byte[] encode(int input[], int len) {
|
||||
byte[] out = new byte[len];
|
||||
int i, j;
|
||||
for (i = j = 0; j < len; i++, j += 4) {
|
||||
out[j] = (byte) (input[i] & 0xff);
|
||||
out[j + 1] = (byte) ((input[i] >>> 8) & 0xff);
|
||||
out[j + 2] = (byte) ((input[i] >>> 16) & 0xff);
|
||||
out[j + 3] = (byte) ((input[i] >>> 24) & 0xff);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private int[] decode(byte buffer[], int len, int offset) {
|
||||
int i, j;
|
||||
for (i = j = 0; j < len; i++, j += 4) {
|
||||
decodeBuffer[i] = (
|
||||
(buffer[j + offset] & 0xff)) |
|
||||
(((buffer[j + 1 + offset] & 0xff)) << 8) |
|
||||
(((buffer[j + 2 + offset] & 0xff)) << 16) |
|
||||
(((buffer[j + 3 + offset] & 0xff)) << 24
|
||||
);
|
||||
}
|
||||
return decodeBuffer;
|
||||
}
|
||||
|
||||
private static void transform(MD5State state, int[] x) {
|
||||
int a = state.state[0];
|
||||
int b = state.state[1];
|
||||
int c = state.state[2];
|
||||
int d = state.state[3];
|
||||
|
||||
/* Round 1 */
|
||||
a = FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */
|
||||
d = FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */
|
||||
c = FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */
|
||||
b = FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */
|
||||
a = FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */
|
||||
d = FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */
|
||||
c = FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */
|
||||
b = FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */
|
||||
a = FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */
|
||||
d = FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */
|
||||
c = FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
|
||||
b = FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
|
||||
a = FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */
|
||||
d = FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
|
||||
c = FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
|
||||
b = FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */
|
||||
|
||||
/* Round 2 */
|
||||
a = GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */
|
||||
d = GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */
|
||||
c = GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
|
||||
b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */
|
||||
a = GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */
|
||||
d = GG(d, a, b, c, x[10], 9, 0x02441453); /* 22 */
|
||||
c = GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
|
||||
b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */
|
||||
a = GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */
|
||||
d = GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */
|
||||
c = GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */
|
||||
b = GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */
|
||||
a = GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */
|
||||
d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */
|
||||
c = GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */
|
||||
b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */
|
||||
|
||||
/* Round 3 */
|
||||
a = HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */
|
||||
d = HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */
|
||||
c = HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
|
||||
b = HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
|
||||
a = HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */
|
||||
d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */
|
||||
c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */
|
||||
b = HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
|
||||
a = HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */
|
||||
d = HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */
|
||||
c = HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */
|
||||
b = HH(b, c, d, a, x[6], 23, 0x04881d05); /* 44 */
|
||||
a = HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */
|
||||
d = HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
|
||||
c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
|
||||
b = HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */
|
||||
|
||||
/* Round 4 */
|
||||
a = II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */
|
||||
d = II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */
|
||||
c = II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
|
||||
b = II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */
|
||||
a = II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */
|
||||
d = II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */
|
||||
c = II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
|
||||
b = II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */
|
||||
a = II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */
|
||||
d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
|
||||
c = II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */
|
||||
b = II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
|
||||
a = II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */
|
||||
d = II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
|
||||
c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */
|
||||
b = II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */
|
||||
|
||||
state.state[0] += a;
|
||||
state.state[1] += b;
|
||||
state.state[2] += c;
|
||||
state.state[3] += d;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package org.owasp.webgoat.plugin.challenge7;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* WARNING: DO NOT CHANGE FILE WITHOUT CHANGING .git contents
|
||||
*
|
||||
* @author nbaars
|
||||
* @since 8/17/17.
|
||||
*/
|
||||
public class PasswordResetLink {
|
||||
|
||||
public String createPasswordReset(String username, String key) {
|
||||
Random random = new Random();
|
||||
if (username.equalsIgnoreCase("admin")) {
|
||||
//Admin has a fix reset link
|
||||
random.setSeed(key.length());
|
||||
}
|
||||
return scramble(random, scramble(random, scramble(random, MD5.getHashString(username))));
|
||||
}
|
||||
|
||||
public static String scramble(Random random, String inputString) {
|
||||
char a[] = inputString.toCharArray();
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
int j = random.nextInt(a.length);
|
||||
char temp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = temp;
|
||||
}
|
||||
return new String(a);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args == null || args.length != 2) {
|
||||
System.out.println("Need a username and key");
|
||||
System.exit(1);
|
||||
}
|
||||
String username = args[0];
|
||||
String key = args[1];
|
||||
System.out.println("Generation password reset link for " + username);
|
||||
System.out.println("Created password reset link: " + new PasswordResetLink().createPasswordReset(username, key));
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package org.owasp.webgoat.plugin.challenge8;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.plugin.Flag;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 4/8/17.
|
||||
*/
|
||||
@AssignmentPath("/challenge/8")
|
||||
@Slf4j
|
||||
public class Assignment8 extends AssignmentEndpoint {
|
||||
|
||||
private static final Map<Integer, Integer> votes = Maps.newHashMap();
|
||||
|
||||
static {
|
||||
votes.put(1, 400);
|
||||
votes.put(2, 120);
|
||||
votes.put(3, 140);
|
||||
votes.put(4, 150);
|
||||
votes.put(5, 300);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/vote/{stars}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> vote(@PathVariable(value = "stars") int nrOfStars, HttpServletRequest request) {
|
||||
//Simple implementation of VERB Based Authentication
|
||||
String msg = "";
|
||||
if (request.getMethod().equals("GET")) {
|
||||
HashMap<String, Object> json = Maps.newHashMap();
|
||||
json.put("error", true);
|
||||
json.put("message", "Sorry but you need to login first in order to vote");
|
||||
return ResponseEntity.status(200).body(json);
|
||||
}
|
||||
Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0);
|
||||
votes.put(nrOfStars, allVotesForStar + 1);
|
||||
return ResponseEntity.ok().header("X-Flag", "Thanks for voting, your flag is: " + Flag.FLAGS.get(8)).build();
|
||||
}
|
||||
|
||||
@GetMapping("/votes/")
|
||||
public ResponseEntity<?> getVotes() {
|
||||
return ResponseEntity.ok(votes.entrySet().stream().collect(Collectors.toMap(e -> "" + e.getKey(), e -> e.getValue())));
|
||||
}
|
||||
|
||||
@GetMapping("/votes/average")
|
||||
public ResponseEntity<Map<String, Integer>> average() {
|
||||
int totalNumberOfVotes = votes.values().stream().mapToInt(i -> i.intValue()).sum();
|
||||
int categories = votes.entrySet().stream().mapToInt(e -> e.getKey() * e.getValue()).reduce(0, (a, b) -> a + b);
|
||||
Map json = Maps.newHashMap();
|
||||
json.put("average", (int) Math.ceil((double) categories / totalNumberOfVotes));
|
||||
return ResponseEntity.ok(json);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package org.owasp.webgoat.plugin.challenge8;
|
||||
|
||||
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 Challenge8 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 "challenge8.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "Challenge8";
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
package org.owasp.webgoat.plugin.challenge9;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import com.beust.jcommander.internal.Maps;
|
||||
import com.google.common.collect.EvictingQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.mail.IncomingMailEvent;
|
||||
import org.owasp.webgoat.users.UserRepository;
|
||||
import org.owasp.webgoat.users.WebGoatUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.owasp.webgoat.plugin.Flag.FLAGS;
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM_9;
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.TOM_EMAIL;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 4/8/17.
|
||||
*/
|
||||
@AssignmentPath("/challenge/9")
|
||||
@Slf4j
|
||||
public class Assignment9 extends AssignmentEndpoint {
|
||||
|
||||
private static Map<String, String> userToTomResetLink = Maps.newHashMap();
|
||||
private static Map<String, String> usersToTomPassword = Maps.newHashMap();
|
||||
private static EvictingQueue resetLinks = EvictingQueue.create(1000);
|
||||
|
||||
private static final String TEMPLATE = "Hi, you requested a password reset link, please use this " +
|
||||
"<a target='_blank' href='http://%s/WebGoat/challenge/9/reset-password/%s'>link</a> to reset your password." +
|
||||
"\n \n\n" +
|
||||
"If you did not request this password change you can ignore this message." +
|
||||
"\n" +
|
||||
"If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" +
|
||||
"\n\n" +
|
||||
"Kind regards, \nTeam WebGoat";
|
||||
|
||||
@Autowired
|
||||
private JmsTemplate jmsTemplate;
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@RequestMapping(method = POST, value = "/create-password-reset-link")
|
||||
@ResponseBody
|
||||
public AttackResult sendPasswordResetLink(@RequestParam String email, HttpServletRequest request, @CookieValue("JSESSIONID") String cookie) {
|
||||
String resetLink = UUID.randomUUID().toString();
|
||||
resetLinks.add(resetLink);
|
||||
String host = request.getHeader("host");
|
||||
if (StringUtils.hasText(email)) {
|
||||
if (email.equals(TOM_EMAIL) && host.contains("8081")) { //User indeed changed the host header.
|
||||
userToTomResetLink.put(getWebSession().getUserName(), resetLink);
|
||||
fakeClickingLinkEmail(cookie, host, resetLink);
|
||||
} else {
|
||||
sendMailToUser(email, host, resetLink);
|
||||
}
|
||||
}
|
||||
return success().feedback("email.send").feedbackArgs(email).build();
|
||||
}
|
||||
|
||||
private void sendMailToUser(@RequestParam String email, String host, String resetLink) {
|
||||
String username;
|
||||
WebGoatUser webGoatUser = userRepository.findByUsername(email.substring(0, email.indexOf("@")));
|
||||
if (webGoatUser != null) {
|
||||
username = webGoatUser.getUsername();
|
||||
IncomingMailEvent mail = IncomingMailEvent.builder()
|
||||
.title("Your password reset link for challenge 9")
|
||||
.contents(String.format(TEMPLATE, host, resetLink))
|
||||
.sender("password-reset@webgoat-cloud.net")
|
||||
.recipient(username)
|
||||
.time(LocalDateTime.now()).build();
|
||||
jmsTemplate.convertAndSend("mailbox", mail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to add the current cookie of the user otherwise we cannot distinguish in WebWolf for
|
||||
* which user we need to trace the incoming request. In normal situation this HOST will be in your
|
||||
* full control so every incoming request would be valid.
|
||||
*/
|
||||
private void fakeClickingLinkEmail(String cookie, String host, String resetLink) {
|
||||
try {
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.put(HttpHeaders.COOKIE, Lists.newArrayList("JSESSIONID=" + cookie));
|
||||
HttpEntity httpEntity = new HttpEntity(httpHeaders);
|
||||
new RestTemplate().exchange(String.format("http://%s/challenge/9/reset-password/%s", host, resetLink), HttpMethod.GET, httpEntity, Void.class);
|
||||
} catch (Exception e) {
|
||||
//don't care
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
@ResponseBody
|
||||
public AttackResult login(@RequestParam String password, @RequestParam String email) {
|
||||
if (TOM_EMAIL.equals(email)) {
|
||||
String passwordTom = usersToTomPassword.getOrDefault(getWebSession().getUserName(), PASSWORD_TOM_9);
|
||||
if (passwordTom.equals(PASSWORD_TOM_9)) {
|
||||
return failed().feedback("login_failed").build();
|
||||
} else if (passwordTom.equals(password)) {
|
||||
return success().feedback("challenge.solved").feedbackArgs(FLAGS.get(9)).build();
|
||||
}
|
||||
}
|
||||
return failed().feedback("login_failed.tom").build();
|
||||
}
|
||||
|
||||
@GetMapping("/reset-password/{link}")
|
||||
public String resetPassword(@PathVariable(value = "link") String link, Model model) {
|
||||
if (this.resetLinks.contains(link)) {
|
||||
PasswordChangeForm form = new PasswordChangeForm();
|
||||
form.setResetLink(link);
|
||||
model.addAttribute("form", form);
|
||||
return "password_reset"; //Display html page for changing password
|
||||
} else {
|
||||
return "password_link_not_found";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/change-password")
|
||||
public String changePassword(@ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) {
|
||||
if (!StringUtils.hasText(form.getPassword())) {
|
||||
bindingResult.rejectValue("password", "not.empty");
|
||||
}
|
||||
if (bindingResult.hasErrors()) {
|
||||
return "password_reset";
|
||||
}
|
||||
if (!resetLinks.contains(form.getResetLink())) {
|
||||
return "password_link_not_found";
|
||||
}
|
||||
if (checkIfLinkIsFromTom(form.getResetLink())) {
|
||||
usersToTomPassword.put(getWebSession().getUserName(), form.getPassword());
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
|
||||
private boolean checkIfLinkIsFromTom(String resetLinkFromForm) {
|
||||
String resetLink = userToTomResetLink.getOrDefault(getWebSession().getUserName(), "unknown");
|
||||
return resetLink.equals(resetLinkFromForm);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package org.owasp.webgoat.plugin.challenge9;
|
||||
|
||||
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 Challenge9 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 "challenge9.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "Challenge9";
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.owasp.webgoat.plugin.challenge9;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/18/17.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class PasswordChangeForm {
|
||||
|
||||
@NotNull
|
||||
@Size(min=6, max=10)
|
||||
private String password;
|
||||
private String resetLink;
|
||||
|
||||
}
|
BIN
webgoat-lessons/challenge/src/main/resources/challenge7/git.zip
Normal file
@ -0,0 +1,43 @@
|
||||
.btn-grey{
|
||||
background-color:#D8D8D8;
|
||||
color:#FFF;
|
||||
}
|
||||
.rating-block{
|
||||
background-color:#FAFAFA;
|
||||
border:1px solid #EFEFEF;
|
||||
padding:15px 15px 20px 15px;
|
||||
border-radius:3px;
|
||||
}
|
||||
.bold{
|
||||
font-weight:700;
|
||||
}
|
||||
.padding-bottom-7{
|
||||
padding-bottom:7px;
|
||||
}
|
||||
|
||||
.review-block{
|
||||
background-color:#FAFAFA;
|
||||
border:1px solid #EFEFEF;
|
||||
padding:15px;
|
||||
border-radius:3px;
|
||||
margin-bottom:15px;
|
||||
}
|
||||
.review-block-name{
|
||||
font-size:12px;
|
||||
margin:10px 0;
|
||||
}
|
||||
.review-block-date{
|
||||
font-size:12px;
|
||||
}
|
||||
.review-block-rate{
|
||||
font-size:13px;
|
||||
margin-bottom:15px;
|
||||
}
|
||||
.review-block-title{
|
||||
font-size:15px;
|
||||
font-weight:700;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.review-block-description{
|
||||
font-size:13px;
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!--
|
||||
** Revision history (automatically added by: /challenge/7/.git/hooks)
|
||||
|
||||
2e29cacb85ce5066b8d011bb9769b666812b2fd9 Updated copyright to 2017
|
||||
ac937c7aab89e042ca32efeb00d4ca08a95b50d6 Removed hardcoded key
|
||||
f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website
|
||||
|
||||
-->
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Challenge_7.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="text-center">
|
||||
<h3><i class="fa fa-lock fa-4x"></i></h3>
|
||||
<h2 class="text-center">Forgot Password?</h2>
|
||||
<p>You can reset your password here.</p>
|
||||
<div class="panel-body">
|
||||
|
||||
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/challenge/7"
|
||||
enctype="application/json;charset=UTF-8" role="form">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i
|
||||
class="glyphicon glyphicon-envelope color-blue"></i></span>
|
||||
<input id="email" name="email" placeholder="email address"
|
||||
class="form-control" type="email"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input name="recover-submit" class="btn btn-lg btn-primary btn-block"
|
||||
value="Reset Password" type="submit"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p>(c) 2017 WebGoat Cloud Platform</p>
|
||||
</div>
|
||||
|
||||
<input type="hidden" class="hide" name="token" id="token" value=""/>
|
||||
</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>
|
@ -0,0 +1,255 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Challenge_8.adoc"></div>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge8.css}"/>
|
||||
<script th:src="@{/lesson_js/challenge8.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">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="rating-block">
|
||||
<h4>Average user rating</h4>
|
||||
<h2 class="bold padding-bottom-7">4.3
|
||||
<small>/ 5</small>
|
||||
</h2>
|
||||
<button id="star1" onClick="doVote(1)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="star2" onClick="doVote(2)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="star3" onClick="doVote(3)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="star4" onClick="doVote(4)" type="button" class="btn btn-default btn-grey btn-sm" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button id="star5" onClick="doVote(5)" type="button" class="btn btn-default btn-grey btn-sm" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<h4>Rating breakdown</h4>
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="width:35px; line-height:1;">
|
||||
<div style="height:9px; margin:5px 0;">5 <span class="glyphicon glyphicon-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-left" style="width:180px;">
|
||||
<div class="progress" style="height:9px; margin:8px 0;">
|
||||
<div id="progressBar5" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="5"
|
||||
aria-valuemin="0" aria-valuemax="5">
|
||||
<span class="sr-only">5</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nrOfVotes5" class="pull-right" style="margin-left:10px;">0</div>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="width:35px; line-height:1;">
|
||||
<div style="height:9px; margin:5px 0;">4 <span class="glyphicon glyphicon-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-left" style="width:180px;">
|
||||
<div class="progress" style="height:9px; margin:8px 0;">
|
||||
<div id="progressBar4" class="progress-bar progress-bar-primary" role="progressbar" aria-valuenow="5"
|
||||
aria-valuemin="0" aria-valuemax="5">
|
||||
<span class="sr-only">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nrOfVotes4" class="pull-right" style="margin-left:10px;">0</div>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="width:35px; line-height:1;">
|
||||
<div style="height:9px; margin:5px 0;">3 <span class="glyphicon glyphicon-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-left" style="width:180px;">
|
||||
<div class="progress" style="height:9px; margin:8px 0;">
|
||||
<div id="progressBar3" class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="5"
|
||||
aria-valuemin="0" aria-valuemax="5">
|
||||
<span class="sr-only">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nrOfVotes3" class="pull-right" style="margin-left:10px;">0</div>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="width:35px; line-height:1;">
|
||||
<div style="height:9px; margin:5px 0;">2 <span class="glyphicon glyphicon-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-left" style="width:180px;">
|
||||
<div class="progress" style="height:9px; margin:8px 0;">
|
||||
<div id="progressBar2" class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="5"
|
||||
aria-valuemin="0" aria-valuemax="5">
|
||||
<span class="sr-only">2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nrOfVotes2" class="pull-right" style="margin-left:10px;">0</div>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="width:35px; line-height:1;">
|
||||
<div style="height:9px; margin:5px 0;">1 <span class="glyphicon glyphicon-star"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-left" style="width:180px;">
|
||||
<div class="progress" style="height:9px; margin:8px 0;">
|
||||
<div id="progressBar1" class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="5"
|
||||
aria-valuemin="0" aria-valuemax="5">
|
||||
<span class="sr-only">4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="nrOfVotes1" class="pull-right" style="margin-left:10px;">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-7">
|
||||
<hr/>
|
||||
<div id = "voteResultMsg" class="alert alert-dismissable" style="display: none;">
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
Please login or register in order to vote (comments are disabled)
|
||||
</div>
|
||||
<div class="review-block">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<img src="images/user1.png" class="img-rounded"/>
|
||||
<div class="review-block-name"><a href="#">nktailor</a></div>
|
||||
<div class="review-block-date">August 22, 2017<br/>1 day ago</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div class="review-block-rate">
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="review-block-title">WebGoat rocks!</div>
|
||||
<div class="review-block-description">This is a great tool to learn about security
|
||||
and have some fun with a couple challenges.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<img src="images/user3.png" class="img-rounded"/>
|
||||
<div class="review-block-name"><a href="#">Sarah</a></div>
|
||||
<div class="review-block-date">July 29, 2017<br/>12 day ago</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div class="review-block-rate">
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-grey btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="review-block-title">Nice</div>
|
||||
<div class="review-block-description">I liked it and learned a couple of things.
|
||||
Still some bugs sometimes though.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<img src="images/user2.png" class="img-rounded"/>
|
||||
<div class="review-block-name"><a href="#">Tom</a></div>
|
||||
<div class="review-block-date">January 27, 2017<br/>100 days ago</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div class="review-block-rate">
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-grey btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-grey btn-xs"
|
||||
aria-label="Left Align">
|
||||
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="review-block-title">WebGoat is great</div>
|
||||
<div class="review-block-description">WebGoat teaches you web security with some great
|
||||
lessons
|
||||
</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>
|
@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Challenge_9.adoc"></div>
|
||||
<script th:src="@{/lesson_js/challenge9.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">
|
||||
<h4 style="border-bottom: 1px solid #c5c5c5;">
|
||||
<i class="glyphicon glyphicon-user"></i>
|
||||
Account Access
|
||||
</h4>
|
||||
<div style="padding: 20px;" id="form-login">
|
||||
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/challenge/9/login"
|
||||
enctype="application/json;charset=UTF-8" role="form">
|
||||
<fieldset>
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon"> @ </span>
|
||||
<input class="form-control" placeholder="Email" name="email" type="email"
|
||||
required="" autofocus=""/>
|
||||
</div>
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon">
|
||||
<i class="glyphicon glyphicon-lock">
|
||||
</i>
|
||||
</span>
|
||||
<input class="form-control" placeholder="Password" name="password" type="password"
|
||||
value="" required=""/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
Access
|
||||
</button>
|
||||
<p class="help-block">
|
||||
<a class="pull-right text-muted" href="#" id="login">
|
||||
<small>Forgot your password?</small>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div style="display: none;" id="form-login">
|
||||
<h4 class="">
|
||||
Forgot your password?
|
||||
</h4>
|
||||
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/challenge/9/create-password-reset-link"
|
||||
enctype="application/json;charset=UTF-8" role="form">
|
||||
<fieldset>
|
||||
<span class="help-block">
|
||||
Email address you use to log in to your account
|
||||
<br/>
|
||||
We'll send you an email with instructions to choose a new password.
|
||||
</span>
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon">
|
||||
@
|
||||
</span>
|
||||
<input class="form-control" placeholder="Email" name="email" type="email"
|
||||
required=""/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block" id="btn-login">
|
||||
Continue
|
||||
</button>
|
||||
<p class="help-block">
|
||||
<a class="text-muted" href="#" id="forgot">
|
||||
<small>Account Access</small>
|
||||
</a>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
</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>
|
@ -5,8 +5,13 @@ challenge3.title=Photo comments
|
||||
challenge4.title=Voting
|
||||
challenge5.title=Without password
|
||||
challenge6.title=Creating a new account
|
||||
challenge7.title=Admin password reset
|
||||
challenge8.title=Without account
|
||||
challenge9.title=Changing password
|
||||
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.
|
||||
challenge.close=This is not the correct password for Larry, please try again.
|
||||
|
||||
email.send=An e-mail has been send to {0}
|
||||
|
||||
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.
|
||||
@ -15,4 +20,10 @@ input.invalid=Input for user, email and/or password is empty or too long, please
|
||||
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.
|
||||
ip.address.unknown=IP address unknown, e-mail has been sent.
|
||||
|
||||
login_failed=Login failed
|
||||
login_failed.tom=Sorry only Tom can login at the moment
|
||||
|
||||
required4=Missing username or password, please specify both.
|
||||
user.not.larry=Please try to log in as Larry not {0}.
|
After Width: | Height: | Size: 40 KiB |
BIN
webgoat-lessons/challenge/src/main/resources/images/user1.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
webgoat-lessons/challenge/src/main/resources/images/user2.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
webgoat-lessons/challenge/src/main/resources/images/user3.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,57 @@
|
||||
$(document).ready(function () {
|
||||
loadVotes();
|
||||
average();
|
||||
})
|
||||
|
||||
function loadVotes() {
|
||||
$.get("challenge/8/votes/", function (votes) {
|
||||
var totalVotes = 0;
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
totalVotes = totalVotes + votes[i];
|
||||
}
|
||||
console.log(totalVotes);
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
var percent = votes[i] * 100 / totalVotes;
|
||||
console.log(percent);
|
||||
var progressBar = $('#progressBar' + i);
|
||||
progressBar.width(Math.round(percent) * 2 + '%');
|
||||
$("#nrOfVotes" + i).html(votes[i]);
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function average() {
|
||||
$.get("challenge/8/votes/average", function (average) {
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
var number = average["average"];
|
||||
$("#star" + i).removeClass('btn-warning');
|
||||
$("#star" + i).removeClass('btn-default');
|
||||
$("#star" + i).removeClass('btn-grey');
|
||||
|
||||
if (i <= number) {
|
||||
$("#star" + i).addClass('btn-warning');
|
||||
} else {
|
||||
$("#star" + i).addClass('btn-grey');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function doVote(stars) {
|
||||
$("#voteResultMsg").hide();
|
||||
$.get("challenge/8/vote/" + stars, function (result) {
|
||||
if (result["error"]) {
|
||||
$("#voteResultMsg").addClass('alert-danger alert-dismissable');
|
||||
} else {
|
||||
$("#voteResultMsg").addClass('alert-success alert-dismissable');
|
||||
}
|
||||
$("#voteResultMsg").html(result["message"]);
|
||||
$("#voteResultMsg").show();
|
||||
})
|
||||
loadVotes();
|
||||
average();
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
$(document).ready(function() {
|
||||
$('#login').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('div#form-login').toggle('500');
|
||||
});
|
||||
$('#forgot').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('div#form-login').toggle('500');
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
Try to reset the password for admin.
|
@ -0,0 +1 @@
|
||||
Can you still vote?
|
@ -0,0 +1,3 @@
|
||||
Tom always resets his password immediately after receiving the email with the link.
|
||||
Try to reset the password of Tom (tom@webgoat-cloud.org) to your own choice and login as Tom with
|
||||
that password.
|
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<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}"/>
|
||||
<script th:src="@{/plugins/bootstrap/js/bootstrap.min.js}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="alert alert-danger">
|
||||
<h4>Password reset link is not valid please try again.</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<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}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
|
||||
<form role="form" method="POST" action="/WebGoat/challenge/9/change-password" th:object="${form}">
|
||||
<h2 class="sign_up_title">Reset your password</h2>
|
||||
<!--<div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error'">-->
|
||||
<!--<div class="form-group">-->
|
||||
<!--<label for="email" class="control-label">Email</label>-->
|
||||
<!--<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"-->
|
||||
<!--th:field="*{email}"-->
|
||||
<!--id="email" placeholder="email" name='email'/>-->
|
||||
<!--<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email error</span>-->
|
||||
<!--</div>-->
|
||||
<div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
|
||||
<input type="hidden" name="resetLink" th:field="*{resetLink}" />
|
||||
<label for="password" class="control-label" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" id="password" placeholder="Password"
|
||||
name='password' th:value="*{password}"/>
|
||||
<span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password error</span>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
<!--<div class="form-group">-->
|
||||
<!--<input type="email" required="" autofocus="" name="email" id="email" class="form-control input-lg" placeholder="Email"-->
|
||||
<!--tabindex="4"/>-->
|
||||
<!--<input type="newPassword" required="" autofocus="" name="newPassword" id="newPassword" class="form-control input-lg" placeholder="New password"-->
|
||||
<!--tabindex="4"/>-->
|
||||
<!--</div>-->
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-12">
|
||||
<button type="submit" class="btn btn-success btn-block btn-lg">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<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}"/>
|
||||
<script th:src="@{/plugins/bootstrap/js/bootstrap.min.js}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="alert alert-success">
|
||||
<h4>Password changed successfully, please login again with your new password</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -27,6 +27,7 @@
|
||||
<module>xxe</module>
|
||||
<module>idor</module>
|
||||
<module>vulnerable-components</module>
|
||||
<module>webwolf-introduction</module>
|
||||
<module>auth-bypass</module>
|
||||
<module>missing-function-ac</module>
|
||||
<!-- uncomment below to include lesson template in build, also uncomment the dependency in webgoat-server/pom.xml to have it run in the project fully -->
|
||||
|
@ -84,7 +84,7 @@
|
||||
<div class="col-lg-12">
|
||||
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="SqlInjection/attack7"
|
||||
action="SqlInjection/challenge"
|
||||
enctype="application/json;charset=UTF-8" role="form">
|
||||
<div class="form-group">
|
||||
<input type="text" name="username_login" id="username4" tabindex="1"
|
||||
@ -120,7 +120,7 @@
|
||||
</form>
|
||||
<form id="register-form" class="attack-form" accept-charset="UNKNOWN"
|
||||
method="PUT" name="form"
|
||||
action="SqlInjection/attack7"
|
||||
action="SqlInjection/challenge"
|
||||
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"
|
||||
|
11
webgoat-lessons/webwolf-introduction/pom.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>webwolf-introduction</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
</project>
|
@ -0,0 +1,48 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@AssignmentPath("/WebWolf/landing")
|
||||
public class LandingAssignment extends AssignmentEndpoint {
|
||||
|
||||
private RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public AttackResult click(String uniqueCode) {
|
||||
if (StringUtils.reverse(getWebSession().getUserName()).equals(uniqueCode)) {
|
||||
return trackProgress(success().build());
|
||||
}
|
||||
return failed().feedback("webwolf.landing_wrong").build();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/password-reset")
|
||||
public ModelAndView openPasswordReset(HttpServletRequest request) throws URISyntaxException {
|
||||
URI uri = new URI(request.getRequestURL().toString());
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.addObject("webwolfUrl", uri.getScheme() + "://" + uri.getHost() + ":8081");
|
||||
modelAndView.addObject("uniqueCode", StringUtils.reverse(getWebSession().getUserName()));
|
||||
|
||||
modelAndView.setViewName("webwolfPasswordReset");
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.mail.IncomingMailEvent;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@AssignmentPath("/WebWolf/mail")
|
||||
@AllArgsConstructor
|
||||
public class MailAssignment extends AssignmentEndpoint {
|
||||
|
||||
private JmsTemplate jmsTemplate;
|
||||
|
||||
@PostMapping("send")
|
||||
@ResponseBody
|
||||
public AttackResult sendEmail(@RequestParam String email) {
|
||||
String username = email.substring(0, email.indexOf("@"));
|
||||
if (username.equals(getWebSession().getUserName())) {
|
||||
IncomingMailEvent mailEvent = IncomingMailEvent.builder()
|
||||
.recipient(username)
|
||||
.title("Test messages from WebWolf")
|
||||
.time(LocalDateTime.now())
|
||||
.contents("This is a test message from WebWolf, your unique code is" + StringUtils.reverse(username))
|
||||
.sender("webgoat@owasp.org")
|
||||
.build();
|
||||
jmsTemplate.convertAndSend("mailbox", mailEvent);
|
||||
return informationMessage().feedback("webwolf.email_send").feedbackArgs(email).build();
|
||||
} else {
|
||||
return informationMessage().feedback("webwolf.email_mismatch").feedbackArgs(username).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public AttackResult completed(@RequestParam String uniqueCode) {
|
||||
if (uniqueCode.equals(StringUtils.reverse(getWebSession().getUserName()))) {
|
||||
return trackProgress(success().build());
|
||||
} else {
|
||||
return trackProgress(failed().feedbackArgs("webwolf.code_incorrect").feedbackArgs(uniqueCode).build());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.owasp.webgoat.lessons.Category;
|
||||
import org.owasp.webgoat.lessons.NewLesson;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ************************************************************************************************
|
||||
* 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 October 12, 2016
|
||||
*/
|
||||
public class WebWolfIntroduction extends NewLesson {
|
||||
@Override
|
||||
public Category getDefaultCategory() {
|
||||
return Category.INTRODUCTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHints() {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDefaultRanking() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "webwolf.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "WebWolfIntroduction";
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Introduction.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Uploading_files.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Receiving_mail.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<form accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/WebWolf/send"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon">
|
||||
@
|
||||
</span>
|
||||
<input class="form-control" placeholder="test1233@webgoat.org" name="email" type="email"
|
||||
required=""/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block" id="btn-login">
|
||||
Send e-mail
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<form class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/WebWolf/mail/"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
placeholder="Type in your unique code"
|
||||
name='uniqueCode'/>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Landing_page.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<a href="/WebGoat/WebWolf/landing/password-reset" target="_blank">Click here to reset your password</a>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<form class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/WebWolf/landing/"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
placeholder="Type in your unique code"
|
||||
name='uniqueCode'/>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
webwolf.title=WebWolf
|
||||
|
||||
webwolf.email_send=An email has been send to {0} please check your inbox.
|
||||
webwolf.code_incorrect=That is not the correct code: {0}, please try again.
|
||||
|
||||
|
||||
webwolf.email_mismatch=Of course you can send mail to user {0} however you will not be able to read this e-mail in WebWolf, please use your own username.
|
||||
|
||||
webwolf.landing_wrong=This is the wrong code, try to look for the uniqueCode in the parameters in WebWolf.
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 57 KiB |
@ -0,0 +1,21 @@
|
||||
== Introducing WebWolf
|
||||
|
||||
WebWolf is a separate web application which simulates an attackers machine. It makes it possible for us to
|
||||
make a clear distinction between what takes place on the attacked website and the actions you need to do as
|
||||
an "attacker". WebWolf was introduced after a couple of workshops where we received feedback about the fact there
|
||||
was no clear distinction between what was part of the "attackers" role and what was part of the "users" role on the
|
||||
website. The following items are supported in WebWolf:
|
||||
|
||||
* Hosting a file
|
||||
* Receiving email
|
||||
* Landing page for incoming requests
|
||||
|
||||
WebWolf runs as a separate web application and is started automatically when using the Docker image. If you
|
||||
are not using the Docker image you will need to download the jar file and start it:
|
||||
|
||||
```
|
||||
java -jar webwolf-<<version>>.jar
|
||||
```
|
||||
|
||||
This will start the application on port 8081, in your browser type: `http://localhost:8081/WebWolf`
|
||||
You will be redirected to the login page where you need to login with your WebGoat username and password
|
@ -0,0 +1,25 @@
|
||||
== Landing page
|
||||
|
||||
This page will show all the requests made to '/' or '/challenge'. This means
|
||||
you can use WebWolf as your landing page for harvesting cookies etc which
|
||||
is helpful when you perform a XSS lesson.
|
||||
|
||||
image::images/requests.png[caption="Figure: ", style="lesson-image"]
|
||||
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
|
||||
*For this exercise you need to login to WebWolf first.*
|
||||
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
|
||||
Suppose we tricked a user to click on a link he/she received in an email, this link will open up our crafted
|
||||
password reset link page. The user does not see any difference with the normal password reset page of the company.
|
||||
The user enters a new password and hits enter, the new password will be send to your host. In this case the new
|
||||
password will be send to WebWolf. Try to locate the unique code.
|
||||
|
||||
Please be aware after resetting the password the user will receive an error page in a real attack scenario the
|
||||
user would probably see a normal success page (this is due to a limit what we can control with WebWolf)
|
||||
|
@ -0,0 +1,18 @@
|
||||
== Your own mailbox
|
||||
|
||||
WebWolf offers a mail client which will contain the e-mail send during a lesson.
|
||||
This mailbox is user specific so each user has a separate mailbox. All e-mail
|
||||
send to {user}@.... wil end up in this inbox.
|
||||
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
|
||||
image::images/mailbox.png[caption="Figure: ", style="lesson-image"]
|
||||
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
{nbsp}
|
||||
|
||||
Try it, type in your e-mail address below and check in
|
||||
WebWolf your e-mail and type in the unique code below.
|
@ -0,0 +1,12 @@
|
||||
== Uploading files
|
||||
|
||||
In this section you can upload files these files will be available from outside
|
||||
the application. For example in a XXE attack you want to reference a DTD which you
|
||||
reference from a xml, you can use WebWolf to serve this DTD.
|
||||
|
||||
image::images/files.png[caption="Figure: ", style="lesson-image"]
|
||||
|
||||
{nbsp}
|
||||
|
||||
After uploading a file you can use the 'Link' to get the full URL to the uploaded
|
||||
file.
|
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<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}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
|
||||
<form role="form" method="POST" th:action="${webwolfUrl}">
|
||||
<h2 class="sign_up_title">Reset your password</h2>
|
||||
<input type="hidden" name="uniqueCode" th:value="${uniqueCode}"/>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" placeholder="Password"
|
||||
name='password'/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-12">
|
||||
<button type="submit" class="btn btn-success btn-block btn-lg">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/WebGoat">(c) 2017 WebGoat Company</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -70,6 +70,22 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>ctf</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-container</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
@ -154,6 +170,11 @@
|
||||
<artifactId>auth-bypass</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webwolf-introduction</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>missing-function-ac</artifactId>
|
||||
@ -162,9 +183,9 @@
|
||||
|
||||
<!--uncommment below to run/include lesson template in WebGoat Build-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>org.owasp.webgoat.lesson</groupId>-->
|
||||
<!--<artifactId>webgoat-lesson-template</artifactId>-->
|
||||
<!--<version>${project.version}</version>-->
|
||||
<!--<groupId>org.owasp.webgoat.lesson</groupId>-->
|
||||
<!--<artifactId>webgoat-lesson-template</artifactId>-->
|
||||
<!--<version>${project.version}</version>-->
|
||||
<!--</dependency>-->
|
||||
<!-- /lessons -->
|
||||
<dependency>
|
||||
|
@ -22,7 +22,9 @@
|
||||
* projects.
|
||||
* <p>
|
||||
*/
|
||||
package org.owasp.webgoat;import org.springframework.boot.SpringApplication;
|
||||
package org.owasp.webgoat;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
@ -30,7 +32,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
* @date 2/21/17
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class StartWebGoat {
|
||||
public class StartWebGoat {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WebGoat.class, args);
|
||||
|
46
webwolf/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# WebWolf
|
||||
|
||||
## Introduction
|
||||
|
||||
During workshops one of the feedback items was that in some lesson it was not clear what you controlled
|
||||
as an attacker and what was part of the lesson. To make this separation more distinct we created
|
||||
WebWolf which is completely controlled by you as the attacker and runs as a separate application.
|
||||
|
||||
Instead of using your own machine which would involve WebGoat being connected to your local network
|
||||
or internet (remember WebGoat is a vulnerable webapplication) we created WebWolf which is the the
|
||||
environment for you as an attacker.
|
||||
|
||||
At the moment WebWolf offers support for:
|
||||
|
||||
- Receiving e-mails
|
||||
- Serving files
|
||||
- Logging of incoming requests (cookies etc)
|
||||
|
||||
## Running
|
||||
|
||||
### Docker
|
||||
|
||||
If you use the Docker image of WebGoat this application will automatically be available. Use the following
|
||||
URL: http://localhost:8081/WebWolf
|
||||
|
||||
### Standalone
|
||||
|
||||
```Shell
|
||||
cd WebGoat
|
||||
git checkout develop
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
Now we are ready to run the project. WebGoat 8.x is using Spring-Boot.
|
||||
|
||||
```Shell
|
||||
mvn -pl webwolf spring-boot:run
|
||||
```
|
||||
... you should be running WebWolf on localhost:8081/WebWolf momentarily
|
||||
|
||||
|
||||
|
||||
### Mapping
|
||||
|
||||
The web application runs on '/' and the controllers and Thymeleaf templates are hardcoded to '/WebWolf' we need
|
||||
to have '/' available which acts as a landing page for incoming requests.
|
111
webwolf/pom.xml
Normal file
@ -0,0 +1,111 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>webwolf</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>LATEST</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf.extras</groupId>
|
||||
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jms</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>bootstrap</artifactId>
|
||||
<version>3.3.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>3.2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>ISO-8859-1</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
89
webwolf/src/main/java/org/owasp/webwolf/FileServer.java
Normal file
@ -0,0 +1,89 @@
|
||||
package org.owasp.webwolf;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.owasp.webwolf.user.WebGoatUser;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller for uploading a file
|
||||
*/
|
||||
@Controller
|
||||
@Slf4j
|
||||
public class FileServer {
|
||||
|
||||
@Value("${webwolf.fileserver.location}")
|
||||
private String fileLocatation;
|
||||
|
||||
@PostMapping(value = "/WebWolf/fileupload")
|
||||
@SneakyThrows
|
||||
public ModelAndView importFile(@RequestParam("file") MultipartFile myFile) {
|
||||
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
File destinationDir = new File(fileLocatation, user.getUsername());
|
||||
destinationDir.mkdirs();
|
||||
myFile.transferTo(new File(destinationDir, myFile.getOriginalFilename()));
|
||||
log.debug("File saved to {}", new File(destinationDir, myFile.getOriginalFilename()));
|
||||
Files.touch(new File(destinationDir, user.getUsername() + "_changed"));
|
||||
|
||||
ModelMap model = new ModelMap();
|
||||
model.addAttribute("uploadSuccess", "File uploaded successful");
|
||||
return new ModelAndView(
|
||||
new RedirectView("files", true),
|
||||
model
|
||||
);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
private class UploadedFile {
|
||||
private final String name;
|
||||
private final String size;
|
||||
private final String link;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/WebWolf/files")
|
||||
public ModelAndView getFiles(HttpServletRequest request) {
|
||||
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
String username = user.getUsername();
|
||||
File destinationDir = new File(fileLocatation, username);
|
||||
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.setViewName("files");
|
||||
File changeIndicatorFile = new File(destinationDir, user.getUsername() + "_changed");
|
||||
if (changeIndicatorFile.exists()) {
|
||||
modelAndView.addObject("uploadSuccess", request.getParameter("uploadSuccess"));
|
||||
}
|
||||
changeIndicatorFile.delete();
|
||||
|
||||
List<UploadedFile> uploadedFiles = Lists.newArrayList();
|
||||
File[] files = destinationDir.listFiles(File::isFile);
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
String size = FileUtils.byteCountToDisplaySize(file.length());
|
||||
String link = String.format("files/%s/%s", username, file.getName());
|
||||
uploadedFiles.add(new UploadedFile(file.getName(), size, link));
|
||||
}
|
||||
}
|
||||
|
||||
modelAndView.addObject("files", uploadedFiles);
|
||||
return modelAndView;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package org.owasp.webwolf;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/13/17.
|
||||
*/
|
||||
@Configuration
|
||||
public class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Value("${webwolf.fileserver.location}")
|
||||
private String fileLocatation;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocatation + "/");
|
||||
super.addResourceHandlers(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/login").setViewName("login");
|
||||
registry.addViewController("/WebWolf/home").setViewName("home");
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void createDirectory() {
|
||||
File file = new File(fileLocatation);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
|
||||
/**
|
||||
* ************************************************************************************************
|
||||
* 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 December 12, 2015
|
||||
*/
|
||||
package org.owasp.webwolf;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.owasp.webwolf.user.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
/**
|
||||
* Security configuration for WebGoat.
|
||||
*/
|
||||
@Configuration
|
||||
@AllArgsConstructor
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final UserService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/css/**", "/images/**", "/js/**", "/fonts/**", "/webjars/**").permitAll()
|
||||
.antMatchers("/WebWolf/**").authenticated()
|
||||
.anyRequest().permitAll();
|
||||
security.and().csrf().disable().formLogin()
|
||||
.loginPage("/login").failureUrl("/login?error=true");
|
||||
security.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.defaultSuccessUrl("/WebWolf/home", true)
|
||||
.permitAll();
|
||||
security.and()
|
||||
.logout()
|
||||
.permitAll();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService); //.passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public UserDetailsService userDetailsServiceBean() throws Exception {
|
||||
return userDetailsService;
|
||||
}
|
||||
}
|
59
webwolf/src/main/java/org/owasp/webwolf/WebWolf.java
Normal file
@ -0,0 +1,59 @@
|
||||
package org.owasp.webwolf;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webwolf.requests.WebWolfTraceRepository;
|
||||
import org.owasp.webwolf.user.WebGoatUserToCookieRepository;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.support.SpringBootServletInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
|
||||
import org.springframework.jms.config.JmsListenerContainerFactory;
|
||||
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.converter.MessageType;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
public class WebWolf extends SpringBootServletInitializer {
|
||||
|
||||
@Bean
|
||||
public TraceRepository traceRepository(WebGoatUserToCookieRepository repository) {
|
||||
return new WebWolfTraceRepository(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(WebWolf.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JmsListenerContainerFactory<?> jmsFactory(ConnectionFactory connectionFactory,
|
||||
DefaultJmsListenerContainerFactoryConfigurer configurer) {
|
||||
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
||||
// This provides all boot's default to this factory, including the message converter
|
||||
configurer.configure(factory, connectionFactory);
|
||||
// You could still override some of Boot's default if necessary.
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConverter jacksonJmsMessageConverter(ObjectMapper objectMapper) {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
converter.setTargetType(MessageType.TEXT);
|
||||
converter.setTypeIdPropertyName("_type");
|
||||
converter.setObjectMapper(objectMapper);
|
||||
return converter;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(WebWolf.class, args);
|
||||
}
|
||||
}
|
42
webwolf/src/main/java/org/owasp/webwolf/mailbox/Email.java
Normal file
@ -0,0 +1,42 @@
|
||||
package org.owasp.webwolf.mailbox;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@Document
|
||||
public class Email implements Serializable {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private LocalDateTime time;
|
||||
private String contents;
|
||||
private String sender;
|
||||
private String title;
|
||||
@Indexed
|
||||
private String recipient;
|
||||
|
||||
public String getSummary() {
|
||||
return "-" + this.contents.substring(0, 50);
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return DateTimeFormatter.ofPattern("h:mm a").format(time);
|
||||
}
|
||||
|
||||
public String getShortSender() {
|
||||
return sender.substring(0, sender.indexOf("@"));
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package org.owasp.webwolf.mailbox;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.owasp.webwolf.user.WebGoatUser;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/17/17.
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
public class MailboxController {
|
||||
|
||||
private final MailboxRepository mailboxRepository;
|
||||
|
||||
@GetMapping(value = "/WebWolf/mail")
|
||||
public ModelAndView mail() {
|
||||
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
List<Email> emails = mailboxRepository.findByRecipientOrderByTimeDesc(user.getUsername());
|
||||
if (emails != null && !emails.isEmpty()) {
|
||||
modelAndView.addObject("total", emails.size());
|
||||
modelAndView.addObject("emails", emails);
|
||||
}
|
||||
modelAndView.setViewName("mailbox");
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package org.owasp.webwolf.mailbox;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.mail.IncomingMailEvent;
|
||||
import org.owasp.webwolf.user.UserRepository;
|
||||
import org.springframework.jms.annotation.JmsListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class MailboxListener {
|
||||
|
||||
private final MailboxRepository repository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@JmsListener(destination = "mailbox", containerFactory = "jmsFactory")
|
||||
public void incomingMail(IncomingMailEvent event) {
|
||||
if (userRepository.findByUsername(event.getRecipient()) != null) {
|
||||
Email email = Email.builder()
|
||||
.contents(event.getContents())
|
||||
.sender(event.getSender())
|
||||
.time(event.getTime())
|
||||
.recipient(event.getRecipient())
|
||||
.title(event.getTitle()).build();
|
||||
repository.save(email);
|
||||
} else {
|
||||
log.trace("Mail received for unknown user: {}", event.getRecipient());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.owasp.webwolf.mailbox;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/17/17.
|
||||
*/
|
||||
public interface MailboxRepository extends MongoRepository<Email, ObjectId> {
|
||||
|
||||
List<Email> findByRecipientOrderByTimeDesc(String recipient);
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.owasp.webwolf.requests;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webwolf.user.WebGoatUser;
|
||||
import org.springframework.boot.actuate.trace.Trace;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* Controller for fetching all the HTTP requests from WebGoat to WebWolf for a specific
|
||||
* user.
|
||||
*
|
||||
* @author nbaars
|
||||
* @since 8/13/17.
|
||||
*/
|
||||
@Controller
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
@RequestMapping(value = "/WebWolf/requests")
|
||||
public class Requests {
|
||||
|
||||
private final WebWolfTraceRepository traceRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
private class Tracert {
|
||||
private final Date date;
|
||||
private final String path;
|
||||
private final String json;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ModelAndView get(HttpServletRequest request) {
|
||||
ModelAndView m = new ModelAndView("requests");
|
||||
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
List<Tracert> traces = traceRepository.findTraceForUser(user.getUsername()).stream()
|
||||
.map(t -> new Tracert(t.getTimestamp(), path(t), toJsonString(t))).collect(toList());
|
||||
m.addObject("traces", traces);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private String path(Trace t) {
|
||||
return (String) t.getInfo().getOrDefault("path", "");
|
||||
}
|
||||
|
||||
private String toJsonString(Trace t) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(t.getInfo());
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Unable to create json", e);
|
||||
}
|
||||
return "No request(s) found";
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package org.owasp.webwolf.requests;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webwolf.user.WebGoatUser;
|
||||
import org.owasp.webwolf.user.WebGoatUserCookie;
|
||||
import org.owasp.webwolf.user.WebGoatUserToCookieRepository;
|
||||
import org.springframework.boot.actuate.trace.Trace;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import static java.util.Optional.of;
|
||||
|
||||
/**
|
||||
* Keep track of all the incoming requests, we are only keeping track of request originating from
|
||||
* WebGoat and only if there is a cookie (otherwise we can never relate it back to a user).
|
||||
*
|
||||
* @author nbaars
|
||||
* @since 8/13/17.
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebWolfTraceRepository implements TraceRepository {
|
||||
|
||||
private final LoadingCache<String, ConcurrentLinkedDeque<Trace>> cookieTraces = CacheBuilder.newBuilder()
|
||||
.maximumSize(4000).build(new CacheLoader<String, ConcurrentLinkedDeque<Trace>>() {
|
||||
@Override
|
||||
public ConcurrentLinkedDeque<Trace> load(String s) throws Exception {
|
||||
return new ConcurrentLinkedDeque<>();
|
||||
}
|
||||
});
|
||||
private final WebGoatUserToCookieRepository repository;
|
||||
|
||||
public WebWolfTraceRepository(WebGoatUserToCookieRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Trace> findAll() {
|
||||
HashMap<String, Object> map = Maps.newHashMap();
|
||||
map.put("nice", "Great you found the standard Spring Boot tracing endpoint!");
|
||||
Trace trace = new Trace(new Date(), map);
|
||||
return Lists.newArrayList(trace);
|
||||
}
|
||||
|
||||
public List<Trace> findTraceForUser(String username) {
|
||||
return Lists.newArrayList(cookieTraces.getUnchecked(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Map<String, Object> map) {
|
||||
Optional<String> host = getFromHeaders("host", map);
|
||||
String path = (String) map.getOrDefault("path", "");
|
||||
if (host.isPresent() && ("/".equals(path) || path.contains("challenge"))) {
|
||||
Optional<String> cookie = getFromHeaders("cookie", map);
|
||||
cookie.ifPresent(c -> {
|
||||
Optional<String> user = findUserBasedOnCookie(c);
|
||||
user.ifPresent(u -> {
|
||||
ConcurrentLinkedDeque<Trace> traces = this.cookieTraces.getUnchecked(u);
|
||||
traces.addFirst(new Trace(new Date(), map));
|
||||
cookieTraces.put(u, traces);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> findUserBasedOnCookie(String cookiesIncomingRequest) {
|
||||
//Request from WebGoat to WebWolf will contain the session cookie of WebGoat try to map it to a user
|
||||
//this mapping is added to userSession by the CookieFilter in WebGoat code
|
||||
HttpCookie cookie = HttpCookie.parse(cookiesIncomingRequest).get(0);
|
||||
Optional<WebGoatUserCookie> userToCookie = repository.findByCookie(cookie.getValue());
|
||||
Optional<String> user = userToCookie.map(u -> u.getUsername());
|
||||
|
||||
if (!user.isPresent()) {
|
||||
//User is maybe logged in to WebWolf use this user
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.getPrincipal() instanceof WebGoatUser) {
|
||||
WebGoatUser wg = (WebGoatUser) authentication.getPrincipal();
|
||||
user = of(wg.getUsername());
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
private Optional<String> getFromHeaders(String header, Map<String, Object> map) {
|
||||
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
|
||||
if (headers != null) {
|
||||
Map<String, Object> request = (Map<String, Object>) headers.get("request");
|
||||
if (request != null) {
|
||||
return Optional.ofNullable((String) request.get(header));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.webgoat.login.LoginEvent;
|
||||
import org.owasp.webgoat.login.LogoutEvent;
|
||||
import org.springframework.jms.annotation.JmsListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class LoginListener {
|
||||
|
||||
private final WebGoatUserToCookieRepository repository;
|
||||
|
||||
@JmsListener(destination = "webgoat", containerFactory = "jmsFactory", selector = "type = 'LoginEvent'")
|
||||
public void loginEvent(LoginEvent loginEvent) {
|
||||
log.trace("Login event occurred for user: '{}'", loginEvent.getUser());
|
||||
repository.save(new WebGoatUserCookie(loginEvent.getUser(), loginEvent.getCookie()));
|
||||
}
|
||||
|
||||
@JmsListener(destination = "webgoat", containerFactory = "jmsFactory", selector = "type = 'LogoutEvent'")
|
||||
public void logoutEvent(LogoutEvent logoutEvent) {
|
||||
repository.delete(logoutEvent.getUser());
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
public interface UserRepository extends MongoRepository<WebGoatUser, String> {
|
||||
|
||||
WebGoatUser findByUsername(String username);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class UserService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
WebGoatUser webGoatUser = userRepository.findByUsername(username);
|
||||
if (webGoatUser == null) {
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
} else {
|
||||
webGoatUser.createUser();
|
||||
}
|
||||
return webGoatUser;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 3/19/17.
|
||||
*/
|
||||
@Getter
|
||||
public class WebGoatUser implements UserDetails {
|
||||
|
||||
public static final String ROLE_USER = "WEBGOAT_USER";
|
||||
public static final String ROLE_ADMIN = "WEBGOAT_ADMIN";
|
||||
|
||||
@Id
|
||||
private String username;
|
||||
private String password;
|
||||
private String role = ROLE_USER;
|
||||
@Transient
|
||||
private User user;
|
||||
|
||||
protected WebGoatUser() {
|
||||
}
|
||||
|
||||
public WebGoatUser(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
createUser();
|
||||
}
|
||||
|
||||
public void createUser() {
|
||||
this.user = new User(username, password, getAuthorities());
|
||||
}
|
||||
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.singleton(new SimpleGrantedAuthority(getRole()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return this.user.isAccountNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return this.user.isAccountNonLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return this.user.isCredentialsNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return this.user.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WebGoatUserCookie implements Serializable {
|
||||
|
||||
@Id
|
||||
private String username;
|
||||
private String cookie;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package org.owasp.webwolf.user;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 8/20/17.
|
||||
*/
|
||||
public interface WebGoatUserToCookieRepository extends MongoRepository<WebGoatUserCookie, String> {
|
||||
|
||||
Optional<WebGoatUserCookie> findByCookie(String cookie);
|
||||
}
|
41
webwolf/src/main/resources/application.properties
Normal file
@ -0,0 +1,41 @@
|
||||
server.error.include-stacktrace=always
|
||||
server.error.path=/error.html
|
||||
server.session.timeout=600
|
||||
#server.contextPath=/WebWolf
|
||||
server.port=8081
|
||||
server.session.cookie.name = WEBWOLFSESSION
|
||||
|
||||
logging.level.org.springframework=INFO
|
||||
logging.level.org.springframework.boot.devtools=WARN
|
||||
logging.level.org.owasp=DEBUG
|
||||
logging.level.org.owasp.webwolf=TRACE
|
||||
logging.level.org.apache.activemq=WARN
|
||||
|
||||
|
||||
endpoints.trace.sensitive=false
|
||||
management.trace.include=REQUEST_HEADERS,RESPONSE_HEADERS,COOKIES,ERRORS,TIME_TAKEN,PARAMETERS,QUERY_STRING
|
||||
endpoints.trace.enabled=true
|
||||
|
||||
spring.resources.cache-period=0
|
||||
spring.thymeleaf.cache=false
|
||||
|
||||
multipart.enabled=true
|
||||
multipart.file-size-threshold=0 #
|
||||
multipart.location=${java.io.tmpdir}
|
||||
multipart.max-file-size=1Mb
|
||||
multipart.max-request-size=1Mb
|
||||
|
||||
webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver
|
||||
|
||||
|
||||
spring.data.mongodb.port=27017
|
||||
spring.data.mongodb.database=webgoat
|
||||
|
||||
spring.jackson.serialization.indent_output=true
|
||||
spring.jackson.serialization.write-dates-as-timestamps=false
|
||||
|
||||
spring.activemq.broker-url=tcp://localhost:61616
|
||||
spring.activemq.in-memory=true
|
||||
|
||||
#For static file refresh ... and faster dev :D
|
||||
spring.devtools.restart.additional-paths=webwolf/src/main/resources/static/
|
87
webwolf/src/main/resources/static/css/main.css
Normal file
@ -0,0 +1,87 @@
|
||||
footer {
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
.thead-inverse th {
|
||||
color: #fff;
|
||||
background-color: #373a3c;
|
||||
}
|
||||
|
||||
.left15 {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.top5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.top7 {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.top10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.top15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.top17 {
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.top30 {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.bottom10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#accordion .panel-heading {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#accordion .panel-title > a {
|
||||
display: block;
|
||||
padding: 0.4em 0.6em;
|
||||
outline: none;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#accordion .panel-title > a.accordion-toggle::before, #accordion a[data-toggle="collapse"]::before {
|
||||
content: "\e113";
|
||||
float: left;
|
||||
font-family: 'Glyphicons Halflings';
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#accordion .panel-title > a.accordion-toggle.collapsed::before, #accordion a.collapsed[data-toggle="collapse"]::before {
|
||||
content: "\e114";
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 250px;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
padding: 1%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*Mailbox*/
|
||||
|
||||
.nav-tabs .glyphicon:not(.no-margin) { margin-right:10px; }
|
||||
.tab-pane .list-group-item:first-child {border-top-right-radius: 0px;border-top-left-radius: 0px;}
|
||||
.tab-pane .list-group-item:last-child {border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;}
|
||||
.tab-pane .list-group .checkbox { display: inline-block;margin: 0px; }
|
||||
.tab-pane .list-group input[type="checkbox"]{ margin-top: 2px; }
|
||||
.tab-pane .list-group .glyphicon { margin-right:5px; }
|
||||
.tab-pane .list-group .glyphicon:hover { color:#FFBC00; }
|
||||
a.list-group-item.read { color: #222;background-color: #F3F3F3; }
|
||||
hr { margin-top: 5px;margin-bottom: 10px; }
|
||||
.nav-pills>li>a {padding: 5px 10px;}
|
||||
|
BIN
webwolf/src/main/resources/static/images/wolf.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
80
webwolf/src/main/resources/static/images/wolf.svg
Normal file
After Width: | Height: | Size: 9.7 KiB |
15
webwolf/src/main/resources/static/js/fileUpload.js
Normal file
@ -0,0 +1,15 @@
|
||||
$(document).ready(function() {
|
||||
window.setTimeout(function () {
|
||||
$(".fileUploadAlert").fadeTo(500, 0).slideUp(500, function () {
|
||||
$(this).hide();
|
||||
});
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
$(document).on('click','.fa-files-o',function(){
|
||||
var link = $('#fileLink').attr("href");
|
||||
console.log("testing" + document.protocol + "//" + (document.hostname || document.pathname + link));
|
||||
|
||||
|
||||
document.execCommand('copy');
|
||||
});
|
10
webwolf/src/main/resources/static/js/mail.js
Normal file
@ -0,0 +1,10 @@
|
||||
$(document).ready(function () {
|
||||
$('.showMail').click(function (e) {
|
||||
e.preventDefault();
|
||||
$(this).parent().find('.contents').toggle()
|
||||
});
|
||||
});
|
||||
|
||||
function refreshEmails() {
|
||||
location.reload();
|
||||
}
|
72
webwolf/src/main/resources/templates/files.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<div th:replace="fragments/header :: header-css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="fragments/header :: header"/>
|
||||
|
||||
<script type="text/javascript" th:src="@{/js/fileUpload.js}"></script>
|
||||
|
||||
<div class="container">
|
||||
|
||||
|
||||
<div class="alert alert-info fade in">
|
||||
|
||||
<a href="#" class="close" data-dismiss="alert">×</a>
|
||||
<p>
|
||||
Upload a file which you need to host as an attacker.
|
||||
</p>
|
||||
<p>
|
||||
Each file will be available under the following url:
|
||||
http://localhost:8081/files/{username}/{filename}.
|
||||
</p>
|
||||
<p>
|
||||
You can copy and paste the location from the table below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Upload a file</strong>
|
||||
<small></small>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<!-- Standar Form -->
|
||||
<form th:action="@{/WebWolf/fileupload}" method="post" enctype="multipart/form-data">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<input type="file" name="file"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-md btn-primary">Upload files</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="fileUploadAlert alert-success top10" role="alert">
|
||||
<span th:text="${uploadSuccess}"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
<th>Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="f : ${files}">
|
||||
<td th:text="${f.name}">filename</td>
|
||||
<td th:text="${f.size}">size</td>
|
||||
<td><a th:id="fileLink" th:href="@{'/' + ${f.link}}">link</a>
|
||||
<span class="fa fa-files-o" title="Click to copy to clipboard"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
19
webwolf/src/main/resources/templates/fragments/footer.html
Normal file
@ -0,0 +1,19 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="footer">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<footer>
|
||||
© 2017 WebGoat - Use WebWolf at your own risk
|
||||
<script type="text/javascript"
|
||||
src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
51
webwolf/src/main/resources/templates/fragments/header.html
Normal file
@ -0,0 +1,51 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
|
||||
<head>
|
||||
<title>WebWolf</title>
|
||||
<div th:fragment="header-css">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
|
||||
<!--<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"/>-->
|
||||
<link rel="stylesheet" th:href="@{/css/main.css}"/>
|
||||
<script src="/webjars/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
</div>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="header">
|
||||
<nav class="navbar navbar-inverse">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" th:href="@{/WebWolf/home}">WebWolf</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a th:href="@{/WebWolf/home}">Home</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a th:href="@{/WebWolf/files}">Files</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a th:href="@{/WebWolf/mail}">Mailbox</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a th:href="@{/WebWolf/requests}">Incoming requests</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
|
||||
<li><a href="#">
|
||||
<span sec:authorize="isAuthenticated()">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
<span th:text="${#authentication.name}"></span></span></a>
|
||||
</li>
|
||||
<li><a th:href="@{/logout}">
|
||||
<span sec:authorize="isAuthenticated()">
|
||||
Sign out</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|