Merge tag 'v8.0.0' into develop
Release v8.0.0
This commit is contained in:
commit
2911788679
4
.gitignore
vendored
4
.gitignore
vendored
@ -42,3 +42,7 @@ webgoat-lessons/**/target
|
||||
**/.DS_Store
|
||||
webgoat-server/mongo-data/*
|
||||
webgoat-lessons/vulnerable-components/dependency-reduced-pom.xml
|
||||
**/.sts4-cache/*
|
||||
**/.vscode/*
|
||||
|
||||
/.sonatype
|
@ -20,8 +20,8 @@ git flow release publish
|
||||
Now we can make a new release, be sure you committed all your changes.
|
||||
|
||||
```
|
||||
git tag v8.0.0.M3
|
||||
git push origin v8.0.0.M3
|
||||
git tag v8.0.0.M15
|
||||
git push origin v8.0.0.M15
|
||||
```
|
||||
|
||||
Now Travis takes over and will create the release in Github and on Docker Hub.
|
||||
|
11
README.MD
11
README.MD
@ -40,6 +40,15 @@ docker pull webgoat/webgoat-8.0
|
||||
docker run -p 8080:8080 -it webgoat/webgoat-8.0 /home/webgoat/start.sh
|
||||
```
|
||||
|
||||
If you want to keep the database between Docker sessions you need to map the WebGoat data directory to a
|
||||
folder on the host system as follows:
|
||||
|
||||
```Shell
|
||||
docker run -p 8080:8080 -it -v /tmp/webgoat-data:/home/webgoat/.webgoat-${VERSION} webgoat/webgoat-8.0 /home/webgoat/start.sh
|
||||
```
|
||||
|
||||
where `${VERSION}` is for example `v8.0.0.M14`. The data will now be stored in `/tmp/webgoat-data` on your host system.
|
||||
|
||||
Wait for the Docker container to start, and run `docker ps` to verify it's running.
|
||||
|
||||
- If you are using `docker-machine`, verify the machine IP using `docker-machine env`
|
||||
@ -58,7 +67,7 @@ _Please note: this version may not be completely in sync with the develop branch
|
||||
|
||||
## 2. Standalone
|
||||
|
||||
Download the latest WebWolf release from [https://github.com/WebGoat/WebGoat/releases](https://github.com/WebGoat/WebGoat/releases)
|
||||
Download the latest WebGoat release from [https://github.com/WebGoat/WebGoat/releases](https://github.com/WebGoat/WebGoat/releases)
|
||||
|
||||
```Shell
|
||||
java -jar webgoat-server-<<version>>.jar
|
||||
|
35
docker-compose-postgres.yml
Normal file
35
docker-compose-postgres.yml
Normal file
@ -0,0 +1,35 @@
|
||||
version: '2.0'
|
||||
|
||||
services:
|
||||
webgoat:
|
||||
image: webgoat/webgoat-8.0
|
||||
user: webgoat
|
||||
environment:
|
||||
- WEBWOLF_HOST=webwolf
|
||||
- spring.datasource.url=jdbc:postgresql://webgoat_db:5432/webgoat
|
||||
- spring.datasource.username=webgoat
|
||||
- spring.datasource.password=webgoat
|
||||
- spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
- spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect
|
||||
ports:
|
||||
- "8080:8080"
|
||||
webwolf:
|
||||
image: webgoat/webwolf
|
||||
environment:
|
||||
- spring.datasource.url=jdbc:postgresql://webgoat_db:5432/webgoat
|
||||
- spring.datasource.username=webgoat
|
||||
- spring.datasource.password=webgoat
|
||||
- spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
- spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect
|
||||
ports:
|
||||
- "8081:8081"
|
||||
db:
|
||||
container_name: webgoat_db
|
||||
image: postgres:latest
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=webgoat
|
||||
- POSTGRES_USER=webgoat
|
||||
- POSTGRES_DB=webgoat
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
@ -1,15 +1,28 @@
|
||||
version: '2.0'
|
||||
version: '2.1'
|
||||
|
||||
services:
|
||||
webgoat:
|
||||
build: webgoat-server/
|
||||
command: "sh /home/webgoat/start.sh"
|
||||
image: webgoat/webgoat-8.0
|
||||
environment:
|
||||
- WEBWOLF_HOST=webwolf
|
||||
- spring.datasource.url=jdbc:hsqldb:hsql://webgoat_db:9001/webgoat
|
||||
ports:
|
||||
- "8080:8080"
|
||||
webwolf:
|
||||
build: webwolf/
|
||||
command: "sh /home/webwolf/start.sh"
|
||||
depends_on:
|
||||
- webgoat
|
||||
- db
|
||||
webwolf:
|
||||
image: webgoat/webwolf
|
||||
environment:
|
||||
- spring.datasource.url=jdbc:hsqldb:hsql://webgoat_db:9001/webgoat
|
||||
ports:
|
||||
- "8081:8081"
|
||||
- "8081:8081"
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: blacklabelops/hsqldb
|
||||
container_name: webgoat_db
|
||||
environment:
|
||||
- HSQLDB_TRACE=false
|
||||
- HSQLDB_SILENT=true
|
||||
- HSQLDB_DATABASE_NAME=webgoat
|
||||
- HSQLDB_DATABASE_ALIAS=webgoat
|
||||
|
6
pom.xml
6
pom.xml
@ -5,7 +5,7 @@
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
|
||||
<name>WebGoat Parent Pom</name>
|
||||
<description>Parent Pom for the WebGoat Project. A deliberately insecure Web Application</description>
|
||||
@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.9.RELEASE</version>
|
||||
<version>1.5.12.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<licenses>
|
||||
@ -135,7 +135,7 @@
|
||||
<gatling-plugin.version>2.2.4</gatling-plugin.version>
|
||||
<guava.version>18.0</guava.version>
|
||||
<h2.version>1.4.190</h2.version>
|
||||
<hsqldb.version>2.3.2</hsqldb.version>
|
||||
<hsqldb.version>2.3.4</hsqldb.version>
|
||||
<j2h.version>1.3.1</j2h.version>
|
||||
<jackson-core.version>2.6.3</jackson-core.version>
|
||||
<jackson-databind.version>2.6.3</jackson-databind.version>
|
||||
|
@ -17,6 +17,24 @@ elif [ ! -z "${TRAVIS_TAG}" ]; then
|
||||
#elif [ "${BRANCH}" == "develop" ]; then
|
||||
# docker build -f Dockerfile -t $REPO:snapshot .
|
||||
# docker push $REPO
|
||||
else
|
||||
echo "Skipping releasing to DockerHub because it is a build of branch ${BRANCH}"
|
||||
fi
|
||||
|
||||
|
||||
export REPO=webgoat/webwolf
|
||||
cd ..
|
||||
cd webwolf
|
||||
ls target/
|
||||
|
||||
if [ "${BRANCH}" == "master" ] && [ ! -z "${TRAVIS_TAG}" ]; then
|
||||
# If we push a tag to master this will update the LATEST Docker image and tag with the version number
|
||||
docker build --build-arg webwolf_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:latest -t $REPO:${TRAVIS_TAG} .
|
||||
docker push $REPO
|
||||
elif [ ! -z "${TRAVIS_TAG}" ]; then
|
||||
# Creating a tag build we push it to Docker with that tag
|
||||
docker build --build-arg webwolf_version=${TRAVIS_TAG:1} -f Dockerfile -t $REPO:${TRAVIS_TAG} -t $REPO:latest .
|
||||
docker push $REPO
|
||||
else
|
||||
echo "Skipping releasing to DockerHub because it is a build of branch ${BRANCH}"
|
||||
fi
|
@ -10,7 +10,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat</groupId>
|
||||
<artifactId>webgoat-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
<profiles>
|
||||
|
@ -35,6 +35,7 @@ import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.asciidoctor.Asciidoctor;
|
||||
import org.asciidoctor.extension.JavaExtensionRegistry;
|
||||
import org.owasp.webgoat.asciidoc.WebGoatVersionMacro;
|
||||
import org.owasp.webgoat.asciidoc.WebWolfMacro;
|
||||
import org.owasp.webgoat.i18n.Language;
|
||||
import org.thymeleaf.TemplateProcessingParameters;
|
||||
@ -86,6 +87,7 @@ public class AsciiDoctorTemplateResolver extends TemplateResolver {
|
||||
StringWriter writer = new StringWriter();
|
||||
JavaExtensionRegistry extensionRegistry = asciidoctor.javaExtensionRegistry();
|
||||
extensionRegistry.inlineMacro("webWolfLink", WebWolfMacro.class);
|
||||
extensionRegistry.inlineMacro("webGoatVersion", WebGoatVersionMacro.class);
|
||||
|
||||
asciidoctor.convert(new InputStreamReader(is), writer, createAttributes());
|
||||
return new ByteArrayInputStream(writer.getBuffer().toString().getBytes(UTF_8));
|
||||
|
@ -130,6 +130,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||
@Bean
|
||||
public PluginMessages pluginMessages(Messages messages, Language language) {
|
||||
PluginMessages pluginMessages = new PluginMessages(messages, language);
|
||||
pluginMessages.setDefaultEncoding("UTF-8");
|
||||
pluginMessages.setBasenames("i18n/WebGoatLabels");
|
||||
return pluginMessages;
|
||||
}
|
||||
@ -142,6 +143,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||
@Bean
|
||||
public Messages messageSource(Language language) {
|
||||
Messages messages = new Messages(language);
|
||||
messages.setDefaultEncoding("UTF-8");
|
||||
messages.setBasename("classpath:i18n/messages");
|
||||
return messages;
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package org.owasp.webgoat.asciidoc;
|
||||
|
||||
import org.asciidoctor.ast.AbstractBlock;
|
||||
import org.asciidoctor.extension.InlineMacroProcessor;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebGoatVersionMacro extends InlineMacroProcessor {
|
||||
|
||||
public WebGoatVersionMacro(String macroName, Map<String, Object> config) {
|
||||
super(macroName, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String process(AbstractBlock parent, String target, Map<String, Object> attributes) {
|
||||
return EnvironmentExposure.getEnv().getProperty("webgoat.build.version");
|
||||
}
|
||||
}
|
@ -10,6 +10,12 @@ import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usage in asciidoc:
|
||||
* <p>
|
||||
* webWolfLink:here[] will display a href with here as text
|
||||
* webWolfLink:landing[noLink] will display the complete url, for example: http://WW_HOST:WW_PORT/landing
|
||||
*/
|
||||
public class WebWolfMacro extends InlineMacroProcessor {
|
||||
|
||||
public WebWolfMacro(String macroName, Map<String, Object> config) {
|
||||
@ -20,9 +26,17 @@ public class WebWolfMacro extends InlineMacroProcessor {
|
||||
protected String process(AbstractBlock parent, String target, Map<String, Object> attributes) {
|
||||
Environment env = EnvironmentExposure.getEnv();
|
||||
String hostname = determineHost(env.getProperty("webwolf.host"), env.getProperty("webwolf.port"));
|
||||
|
||||
if (displayCompleteLinkNoFormatting(attributes)) {
|
||||
return hostname + (hostname.endsWith("/") ? "" : "/") + target;
|
||||
}
|
||||
return "<a href=\"" + hostname + "\" target=\"_blank\">" + target + "</a>";
|
||||
}
|
||||
|
||||
private boolean displayCompleteLinkNoFormatting(Map<String, Object> attributes) {
|
||||
return attributes.values().stream().filter(a -> a.equals("noLink")).findFirst().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the remote address from received from the browser first. This way it will also work if you run
|
||||
* the browser in a Docker container and WebGoat on your local machine.
|
||||
|
@ -55,7 +55,7 @@ public abstract class AssignmentEndpoint extends Endpoint {
|
||||
|
||||
//// TODO: 11/13/2016 events better fit?
|
||||
protected AttackResult trackProgress(AttackResult attackResult) {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
if (userTracker == null) {
|
||||
userTracker = new UserTracker(webSession.getUserName());
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
package org.owasp.webgoat.lessons;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.*;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -37,19 +35,30 @@ import java.util.List;
|
||||
* @version $Id: $Id
|
||||
* @since November 25, 2016
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@Entity
|
||||
public class Assignment {
|
||||
@NonNull
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
private String name;
|
||||
@NonNull
|
||||
private String path;
|
||||
@Transient
|
||||
private List<String> hints;
|
||||
|
||||
private Assignment() {
|
||||
//Hibernate
|
||||
}
|
||||
|
||||
public Assignment(String name, String path) {
|
||||
this(name, path, Lists.newArrayList());
|
||||
}
|
||||
|
||||
public Assignment(String name, String path, List<String> hints) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.hints = hints;
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public enum Category {
|
||||
INSECURE_CONFIGURATION("Insecure Configuration", new Integer(600)),
|
||||
INSECURE_COMMUNICATION("Insecure Communication", new Integer(700)),
|
||||
INSECURE_STORAGE("Insecure Storage", new Integer(800)),
|
||||
INSECURE_DESERIALIZATION("Insecure Deserialization", new Integer(850)),
|
||||
REQUEST_FORGERIES("Request Forgeries", new Integer(900)),
|
||||
VULNERABLE_COMPONENTS("Vulnerable Components - A9", new Integer(950)),
|
||||
AJAX_SECURITY("AJAX Security", new Integer(1000)),
|
||||
|
@ -2,7 +2,6 @@ package org.owasp.webgoat.lessons;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
|
||||
/**
|
||||
* <p>LessonInfoModel class.</p>
|
||||
|
@ -73,7 +73,7 @@ public class LessonMenuService {
|
||||
List<LessonMenuItem> showLeftNav() {
|
||||
List<LessonMenuItem> menu = new ArrayList<>();
|
||||
List<Category> categories = course.getCategories();
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
|
||||
for (Category category : categories) {
|
||||
LessonMenuItem categoryItem = new LessonMenuItem();
|
||||
|
@ -40,17 +40,19 @@ public class LessonProgressService {
|
||||
@RequestMapping(value = "/service/lessonprogress.mvc", produces = "application/json")
|
||||
@ResponseBody
|
||||
public Map getLessonInfo() {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
LessonTracker lessonTracker = userTracker.getLessonTracker(webSession.getCurrentLesson());
|
||||
Map json = Maps.newHashMap();
|
||||
String successMessage = "";
|
||||
boolean lessonCompleted = false;
|
||||
if (lessonTracker != null) {
|
||||
lessonCompleted = lessonTracker.isLessonSolved();
|
||||
successMessage = "LessonCompleted"; //@todo we still use this??
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
if (webSession.getCurrentLesson() != null) {
|
||||
LessonTracker lessonTracker = userTracker.getLessonTracker(webSession.getCurrentLesson());
|
||||
String successMessage = "";
|
||||
boolean lessonCompleted = false;
|
||||
if (lessonTracker != null) {
|
||||
lessonCompleted = lessonTracker.isLessonSolved();
|
||||
successMessage = "LessonCompleted"; //@todo we still use this??
|
||||
}
|
||||
json.put("lessonCompleted", lessonCompleted);
|
||||
json.put("successMessage", successMessage);
|
||||
}
|
||||
json.put("lessonCompleted", lessonCompleted);
|
||||
json.put("successMessage", successMessage);
|
||||
return json;
|
||||
}
|
||||
|
||||
@ -63,7 +65,7 @@ public class LessonProgressService {
|
||||
@RequestMapping(value = "/service/lessonoverview.mvc", produces = "application/json")
|
||||
@ResponseBody
|
||||
public List<LessonOverview> lessonOverview() {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
AbstractLesson currentLesson = webSession.getCurrentLesson();
|
||||
List<LessonOverview> result = Lists.newArrayList();
|
||||
if ( currentLesson != null ) {
|
||||
|
@ -32,6 +32,7 @@ import com.google.common.collect.Lists;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.owasp.webgoat.i18n.PluginMessages;
|
||||
import org.owasp.webgoat.lessons.AbstractLesson;
|
||||
import org.owasp.webgoat.session.Course;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
@ -57,6 +58,7 @@ public class ReportCardService {
|
||||
private final WebSession webSession;
|
||||
private final UserTrackerRepository userTrackerRepository;
|
||||
private final Course course;
|
||||
private final PluginMessages pluginMessages;
|
||||
|
||||
/**
|
||||
* Endpoint which generates the report card for the current use to show the stats on the solved lessons
|
||||
@ -64,7 +66,7 @@ public class ReportCardService {
|
||||
@GetMapping(path = "/service/reportcard.mvc", produces = "application/json")
|
||||
@ResponseBody
|
||||
public ReportCard reportCard() {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
List<AbstractLesson> lessons = course.getLessons();
|
||||
ReportCard reportCard = new ReportCard();
|
||||
reportCard.setTotalNumberOfLessons(course.getTotalOfLessons());
|
||||
@ -74,7 +76,7 @@ public class ReportCardService {
|
||||
for (AbstractLesson lesson : lessons) {
|
||||
LessonTracker lessonTracker = userTracker.getLessonTracker(lesson);
|
||||
LessonStatistics lessonStatistics = new LessonStatistics();
|
||||
lessonStatistics.setName(lesson.getTitle());
|
||||
lessonStatistics.setName(pluginMessages.getMessage(lesson.getTitle()));
|
||||
lessonStatistics.setNumberOfAttempts(lessonTracker.getNumberOfAttempts());
|
||||
lessonStatistics.setSolved(lessonTracker.isLessonSolved());
|
||||
reportCard.lessonStatistics.add(lessonStatistics);
|
||||
|
@ -59,7 +59,7 @@ public class RestartLessonService {
|
||||
AbstractLesson al = webSession.getCurrentLesson();
|
||||
log.debug("Restarting lesson: " + al);
|
||||
|
||||
UserTracker userTracker = userTrackerRepository.findOne(webSession.getUserName());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName());
|
||||
userTracker.reset(al);
|
||||
userTrackerRepository.save(userTracker);
|
||||
}
|
||||
|
@ -81,6 +81,39 @@ public class CreateDB {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the Method
|
||||
*
|
||||
* @param connection Description of the Parameter
|
||||
* @throws SQLException Description of the Exception
|
||||
*/
|
||||
private void createJWTKeys(Connection connection) throws SQLException {
|
||||
Statement statement = connection.createStatement();
|
||||
|
||||
// Drop servers table
|
||||
try {
|
||||
String dropTable = "DROP TABLE jwt_keys";
|
||||
statement.executeUpdate(dropTable);
|
||||
} catch (SQLException e) {
|
||||
System.out.println("Info - Could not drop jwtkeys table");
|
||||
}
|
||||
|
||||
// Create the new table
|
||||
try {
|
||||
String createTableStatement = "CREATE TABLE jwt_keys"
|
||||
+ " (" + "id varchar(20),"
|
||||
+ "key varchar(20))";
|
||||
statement.executeUpdate(createTableStatement);
|
||||
|
||||
String insertData1 = "INSERT INTO jwt_keys VALUES ('webgoat_key', 'qwertyqwerty1234')";
|
||||
String insertData2 = "INSERT INTO jwt_keys VALUES ('webwolf_key', 'doesnotreallymatter')";
|
||||
statement.executeUpdate(insertData1);
|
||||
statement.executeUpdate(insertData2);
|
||||
} catch (SQLException e) {
|
||||
System.out.println("Error creating product table " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Description of the Method
|
||||
@ -975,6 +1008,7 @@ public class CreateDB {
|
||||
createTanTable(connection);
|
||||
createMFEImagesTable(connection);
|
||||
createModifyWithSQLLessonTable(connection);
|
||||
createJWTKeys(connection);
|
||||
System.out.println("Success: creating tables.");
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +47,11 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Entity
|
||||
public class LessonTracker {
|
||||
@Getter
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@Getter
|
||||
private String lessonName;
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private final Set<Assignment> solvedAssignments = Sets.newHashSet();
|
||||
|
@ -38,7 +38,7 @@ public class Scoreboard {
|
||||
List<WebGoatUser> allUsers = userRepository.findAll();
|
||||
List<Ranking> rankings = Lists.newArrayList();
|
||||
for (WebGoatUser user : allUsers) {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(user.getUsername());
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(user.getUsername());
|
||||
rankings.add(new Ranking(user.getUsername(), challengesSolved(userTracker)));
|
||||
}
|
||||
return rankings;
|
||||
|
@ -50,6 +50,9 @@ import java.util.stream.Collectors;
|
||||
public class UserTracker {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@Column(name = "username")
|
||||
private String user;
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private Set<LessonTracker> lessonTrackers = Sets.newHashSet();
|
||||
|
@ -8,5 +8,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
*/
|
||||
public interface UserTrackerRepository extends JpaRepository<UserTracker, String> {
|
||||
|
||||
UserTracker findByUser(String user);
|
||||
|
||||
}
|
||||
|
@ -3,13 +3,16 @@ server.error.path=/error.html
|
||||
server.session.timeout=600
|
||||
server.contextPath=/WebGoat
|
||||
server.port=8080
|
||||
server.address=127.0.0.1
|
||||
|
||||
spring.datasource.url=jdbc:hsqldb:file:${webgoat.server.directory}/data/webgoat
|
||||
spring.datasource.url=jdbc:hsqldb:hsql://localhost:9001/webgoat
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.HSQLDialect
|
||||
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
|
||||
|
||||
|
||||
logging.level.org.springframework=WARN
|
||||
logging.level.org.springframework.boot.devtools=WARN
|
||||
logging.level.org.springframework=INFO
|
||||
logging.level.org.springframework.boot.devtools=INFO
|
||||
logging.level.org.owasp=DEBUG
|
||||
logging.level.org.owasp.webgoat=TRACE
|
||||
|
||||
@ -19,9 +22,10 @@ security.enable-csrf=false
|
||||
spring.resources.cache-period=0
|
||||
spring.thymeleaf.cache=false
|
||||
|
||||
webgoat.start.hsqldb=true
|
||||
webgoat.clean=false
|
||||
webgoat.server.directory=${user.home}/.webgoat/
|
||||
webgoat.user.directory=${user.home}/.webgoat/
|
||||
webgoat.server.directory=${user.home}/.webgoat-${webgoat.build.version}/
|
||||
webgoat.user.directory=${user.home}/.webgoat-${webgoat.build.version}/
|
||||
webgoat.build.version=@project.version@
|
||||
webgoat.build.number=@build.number@
|
||||
webgoat.email=webgoat@owasp.org
|
||||
@ -35,7 +39,7 @@ webgoat.default.language=en
|
||||
webwolf.host=${WEBWOLF_HOST:localhost}
|
||||
webwolf.port=${WEBWOLF_PORT:8081}
|
||||
webwolf.url=http://${webwolf.host}:${webwolf.port}/WebWolf
|
||||
webworf.url.landingpage=http://${webwolf.host}:${webwolf.port}/landing
|
||||
webwolf.url.landingpage=http://${webwolf.host}:${webwolf.port}/landing
|
||||
webwolf.url.mail=http://${webwolf.host}:${webwolf.port}/mail
|
||||
|
||||
spring.jackson.serialization.indent_output=true
|
||||
|
@ -1066,6 +1066,7 @@ span.show-next-page, span.show-prev-page {
|
||||
|
||||
/* ATTACK DISPLAY */
|
||||
.attack-container {
|
||||
position: relative;
|
||||
background-color: #f1f1f1;
|
||||
border: 2px solid #a66;
|
||||
border-radius: 12px;
|
||||
@ -1150,4 +1151,16 @@ div.captured-flag {
|
||||
height: 117px;
|
||||
width: 1268px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#content {
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.webwolf-enabled {
|
||||
position:absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
width: 42px;
|
||||
height: 47px;
|
||||
}
|
@ -73,6 +73,7 @@ define(['jquery',
|
||||
}
|
||||
|
||||
this.loadLesson = function(name,pageNum) {
|
||||
|
||||
if (this.name === name) {
|
||||
this.listenToOnce(this.lessonHintView, 'hints:showButton', this.onShowHintsButton);
|
||||
this.listenTo(this.lessonHintView, 'hints:hideButton', this.onHideHintsButton);
|
||||
@ -83,15 +84,15 @@ define(['jquery',
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageNum && !this.name) {
|
||||
//placeholder
|
||||
}
|
||||
|
||||
this.helpsLoaded = {};
|
||||
if (typeof(name) === 'undefined' || name === null) {
|
||||
//TODO: implement lesson not found or return to welcome page?
|
||||
}
|
||||
this.lessonContent.loadData({'name':name});
|
||||
// this.planView = {};
|
||||
// this.solutionView = {};
|
||||
// this.sourceView = {};
|
||||
// this.lessonHintView = {};
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
@ -124,15 +125,8 @@ define(['jquery',
|
||||
this.helpControlsView = null;
|
||||
this.lessonContentView.model = this.lessonContent;
|
||||
this.lessonContentView.render();
|
||||
|
||||
//this.planView = new PlanView();
|
||||
//this.solutionView = new SolutionView();
|
||||
//this.sourceView = new SourceView();
|
||||
if (this.lessonHintView) {
|
||||
this.lessonHintView.stopListening();
|
||||
this.lessonHintView = null;
|
||||
}
|
||||
this.lessonHintView = new HintView();
|
||||
//TODO: consider moving hintView as child of lessonContentView ...
|
||||
this.createLessonHintView();
|
||||
|
||||
//TODO: instantiate model with values (not sure why was not working before)
|
||||
var paramModel = new ParamModel({});
|
||||
@ -148,40 +142,23 @@ define(['jquery',
|
||||
this.lessonProgressModel.completed();
|
||||
};
|
||||
|
||||
this.createLessonHintView = function () {
|
||||
if (this.lessonHintView) {
|
||||
this.lessonHintView.stopListening();
|
||||
this.lessonHintView = null;
|
||||
}
|
||||
this.lessonHintView = new HintView();
|
||||
}
|
||||
|
||||
this.addCurHelpState = function (curHelp) {
|
||||
this.helpsLoaded[curHelp.helpElement] = curHelp.value;
|
||||
};
|
||||
|
||||
// this.hideShowHelps = function(showHelp) {
|
||||
// var showId = '#lesson-' + showHelp + '-row';
|
||||
// var contentId = '#lesson-' + showHelp + '-content';
|
||||
// $('.lesson-help').not(showId).hide();
|
||||
// if (!showId) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if ($(showId).is(':visible')) {
|
||||
// $(showId).hide();
|
||||
// return;
|
||||
// } else {
|
||||
// //TODO: move individual .html operations into individual help views
|
||||
// switch(showHelp) {
|
||||
// case 'plan':
|
||||
// $(contentId).html(this.planView.model.get('content'));
|
||||
// break;
|
||||
// case 'solution':
|
||||
// $(showId).html(this.solutionView.model.get('content'));
|
||||
// break;
|
||||
// case 'source':
|
||||
// $(contentId).html('<pre>' + this.sourceView.model.get('content') + '</pre>');
|
||||
// break;
|
||||
// }
|
||||
// $(showId).show();
|
||||
// GoatUtils.scrollToHelp()
|
||||
// }
|
||||
// };
|
||||
|
||||
this.showHintsView = function() {
|
||||
if (!this.lessonHintView) {
|
||||
this.createLessonHintView();
|
||||
}
|
||||
//
|
||||
this.lessonHintView.render();
|
||||
if (this.lessonHintView.getHintsCount > 0) {
|
||||
this.helpControlsView.showHintsButton();
|
||||
|
@ -1,29 +1,33 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/MenuModel'],
|
||||
function($,_,Backbone,MenuModel) {
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/MenuModel'],
|
||||
function ($, _, Backbone, MenuModel) {
|
||||
|
||||
return Backbone.Collection.extend({
|
||||
model: MenuModel,
|
||||
url:'service/lessonmenu.mvc',
|
||||
initialize: function () {
|
||||
var self = this;
|
||||
this.fetch();
|
||||
},
|
||||
return Backbone.Collection.extend({
|
||||
model: MenuModel,
|
||||
url: 'service/lessonmenu.mvc',
|
||||
|
||||
onDataLoaded: function() {
|
||||
this.trigger('menuData:loaded');
|
||||
},
|
||||
initialize: function () {
|
||||
var self = this;
|
||||
this.fetch();
|
||||
setInterval(function () {
|
||||
this.fetch()
|
||||
}.bind(this), 5000);
|
||||
},
|
||||
|
||||
fetch: function() {
|
||||
var self=this;
|
||||
Backbone.Collection.prototype.fetch.apply(this,arguments).then(
|
||||
function(data) {
|
||||
this.models = data;
|
||||
self.onDataLoaded();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
onDataLoaded: function () {
|
||||
this.trigger('menuData:loaded');
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var self = this;
|
||||
Backbone.Collection.prototype.fetch.apply(this, arguments).then(
|
||||
function (data) {
|
||||
this.models = data;
|
||||
self.onDataLoaded();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
@ -67,7 +67,7 @@ define(['jquery',
|
||||
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
success: function (data) {
|
||||
//devs leave stuff like this in all the time
|
||||
console.log('phone home said ' + data);
|
||||
console.log('phone home said ' + JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ define(['jquery',
|
||||
self.navToPage(page);
|
||||
}
|
||||
});
|
||||
setInterval(function () {
|
||||
this.updatePagination();
|
||||
}.bind(this), 5000);
|
||||
},
|
||||
|
||||
findPage: function(assignment) {
|
||||
@ -56,11 +59,13 @@ define(['jquery',
|
||||
var currentPage = (!isNaN(startPageNum) && startPageNum && startPageNum < this.$contentPages) ? startPageNum : 0;
|
||||
//init views & pagination
|
||||
this.showCurContentPage(currentPage);
|
||||
this.paginationControlView = new PaginationControlView(this.$contentPages,this.model.get('lessonUrl'));
|
||||
this.paginationControlView = new PaginationControlView(this.$contentPages,this.model.get('lessonUrl'),startPageNum);
|
||||
},
|
||||
|
||||
updatePagination: function() {
|
||||
this.paginationControlView.updateCollection();
|
||||
if ( this.paginationControlView != undefined ) {
|
||||
this.paginationControlView.updateCollection();
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentPage: function () {
|
||||
@ -85,6 +90,8 @@ define(['jquery',
|
||||
var prepareDataFunctionName = $(curForm).attr('prepareData');
|
||||
var callbackFunctionName = $(curForm).attr('callback');
|
||||
var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||
var additionalHeadersFunctionName = $(curForm).attr('additionalHeaders');
|
||||
var additionalHeaders = (typeof webgoat.customjs[additionalHeadersFunctionName] === 'function') ? webgoat.customjs[additionalHeadersFunctionName]() : function() {};
|
||||
var successCallBackFunctionName = $(curForm).attr('successCallback');
|
||||
var failureCallbackFunctionName = $(curForm).attr('failureCallback');
|
||||
var callbackFunction = (typeof webgoat.customjs[callbackFunctionName] === 'function') ? webgoat.customjs[callbackFunctionName] : function() {};
|
||||
@ -99,6 +106,7 @@ define(['jquery',
|
||||
$.ajax({
|
||||
//data:submitData,
|
||||
url:formUrl,
|
||||
headers: additionalHeaders,
|
||||
method:formMethod,
|
||||
contentType:contentType,
|
||||
data: submitData,
|
||||
@ -146,14 +154,23 @@ define(['jquery',
|
||||
return false;
|
||||
},
|
||||
|
||||
removeSlashesFromJSON: function(str) {
|
||||
// slashes are leftover escapes from JSON serialization by server
|
||||
// for every two char sequence starting with backslash,
|
||||
// replace them in the text with second char only
|
||||
return str.replace(/\\(.)/g, "$1");
|
||||
},
|
||||
|
||||
renderFeedback: function(feedback) {
|
||||
this.$curFeedback.html(polyglot.t(feedback) || "");
|
||||
var s = this.removeSlashesFromJSON(feedback);
|
||||
this.$curFeedback.html(polyglot.t(s) || "");
|
||||
this.$curFeedback.show(400)
|
||||
|
||||
},
|
||||
|
||||
renderOutput: function(output) {
|
||||
this.$curOutput.html(polyglot.t(output) || "");
|
||||
var s = this.removeSlashesFromJSON(output);
|
||||
this.$curOutput.html(polyglot.t(s) || "");
|
||||
this.$curOutput.show(400)
|
||||
},
|
||||
|
||||
@ -173,13 +190,19 @@ define(['jquery',
|
||||
return endpoints;
|
||||
},
|
||||
|
||||
onNavToPage: function(pageNum) {
|
||||
var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
|
||||
this.trigger('endpoints:filtered',assignmentPaths);
|
||||
},
|
||||
|
||||
navToPage: function (pageNum) {
|
||||
this.paginationControlView.setCurrentPage(pageNum);//provides validation
|
||||
this.showCurContentPage(this.paginationControlView.currentPage);
|
||||
this.paginationControlView.render();
|
||||
this.paginationControlView.hideShowNavButtons();
|
||||
var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
|
||||
this.trigger('endpoints:filtered',assignmentPaths);
|
||||
this.onNavToPage(pageNum);
|
||||
//var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
|
||||
//this.trigger('endpoints:filtered',assignmentPaths);
|
||||
},
|
||||
|
||||
/* for testing */
|
||||
|
@ -12,14 +12,14 @@ define(['jquery',
|
||||
template: PaginationTemplate,
|
||||
el: '#lesson-page-controls',
|
||||
|
||||
initialize: function ($contentPages,baseLessonUrl) {
|
||||
initialize: function ($contentPages,baseLessonUrl,initPageNum) {
|
||||
this.$contentPages = $contentPages;
|
||||
this.collection = new LessonOverviewCollection();
|
||||
this.listenTo(this.collection, 'reset', this.render);
|
||||
this.numPages = this.$contentPages.length;
|
||||
this.baseUrl = baseLessonUrl;
|
||||
this.collection.fetch({reset:true});
|
||||
this.initPagination();
|
||||
this.initPagination(initPageNum);
|
||||
//this.render();
|
||||
},
|
||||
|
||||
@ -117,9 +117,9 @@ define(['jquery',
|
||||
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
|
||||
},
|
||||
|
||||
initPagination: function() {
|
||||
//track pagination state in this view ... start at 0
|
||||
this.currentPage = 0;
|
||||
initPagination: function(initPageNum) {
|
||||
//track pagination state in this view ... start at 0 .. unless a pageNum was provided
|
||||
this.currentPage = !initPageNum ? 0 : initPageNum;
|
||||
},
|
||||
|
||||
setCurrentPage: function (pageNum) {
|
||||
|
@ -30,7 +30,7 @@ require.config({
|
||||
shim: {
|
||||
"jqueryui": {
|
||||
exports:"$",
|
||||
deps: ['jquery']
|
||||
deps: ['libs/jquery-2.1.4.min']
|
||||
},
|
||||
underscore: {
|
||||
exports: "_"
|
||||
|
@ -76,24 +76,25 @@
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#"
|
||||
th:text="#{version}">Version: <span
|
||||
th:text="${@environment.getProperty('webgoat.build.version')}"></span></a>
|
||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">
|
||||
<span th:text="#{version}">Version:</span><span>: </span>
|
||||
<span th:text="${@environment.getProperty('webgoat.build.version')}"></span></a>
|
||||
</li>
|
||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#"
|
||||
th:text="#{build}">Build:
|
||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">
|
||||
<span th:text="#{build}">Build:</span><span>: </span>
|
||||
<span th:text="${@environment.getProperty('webgoat.build.number')}"></span></a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div style="display:inline" id="settings">
|
||||
<!--<button type="button" id="admin-button" class="btn btn-default right_nav_button" title="Administrator">-->
|
||||
<!--<i class="fa fa-cog"></i>-->
|
||||
<!--</button>-->
|
||||
<button type="button" id="report-card-button" class="btn btn-default right_nav_button button-up"
|
||||
th:title="#{report.card}">
|
||||
<a href="#reportCard"><i class="fa fa-bar-chart-o"></i></a>
|
||||
</button>
|
||||
<a href="#reportCard">
|
||||
<button type="button" id="report-card-button" class="btn btn-default right_nav_button button-up"
|
||||
th:title="#{report.card}">
|
||||
<i class="fa fa-bar-chart-o"></i>
|
||||
</button>
|
||||
</a>
|
||||
<!--<button type="button" id="user-management" class="btn btn-default right_nav_button"-->
|
||||
<!--title="User management">-->
|
||||
<!--<i class="fa fa-users"></i>-->
|
||||
|
@ -62,7 +62,7 @@ public class AssignmentEndpointTest {
|
||||
|
||||
public void init(AssignmentEndpoint a) {
|
||||
messages.setBasenames("classpath:/i18n/messages", "classpath:/i18n/WebGoatLabels");
|
||||
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
|
||||
when(userTrackerRepository.findByUser(anyString())).thenReturn(userTracker);
|
||||
ReflectionTestUtils.setField(a, "userTrackerRepository", userTrackerRepository);
|
||||
ReflectionTestUtils.setField(a, "userSessionData", userSessionData);
|
||||
ReflectionTestUtils.setField(a, "webSession", webSession);
|
||||
|
@ -63,7 +63,7 @@ public class LessonMenuServiceTest {
|
||||
when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1, l2));
|
||||
when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
|
||||
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
|
||||
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
|
||||
when(userTrackerRepository.findByUser(anyString())).thenReturn(userTracker);
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
|
||||
.andExpect(status().isOk())
|
||||
@ -81,7 +81,7 @@ public class LessonMenuServiceTest {
|
||||
when(course.getLessons(any())).thenReturn(Lists.newArrayList(l1));
|
||||
when(course.getCategories()).thenReturn(Lists.newArrayList(Category.ACCESS_CONTROL));
|
||||
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
|
||||
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
|
||||
when(userTrackerRepository.findByUser(anyString())).thenReturn(userTracker);
|
||||
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get(URL_LESSONMENU_MVC))
|
||||
|
@ -72,7 +72,7 @@ public class LessonProgressServiceTest {
|
||||
@Before
|
||||
public void setup() {
|
||||
Assignment assignment = new Assignment("test", "test");
|
||||
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
|
||||
when(userTrackerRepository.findByUser(anyString())).thenReturn(userTracker);
|
||||
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
|
||||
when(websession.getCurrentLesson()).thenReturn(lesson);
|
||||
when(lessonTracker.getLessonOverview()).thenReturn(Maps.newHashMap(assignment, true));
|
||||
|
@ -6,6 +6,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.i18n.PluginMessages;
|
||||
import org.owasp.webgoat.lessons.AbstractLesson;
|
||||
import org.owasp.webgoat.session.Course;
|
||||
import org.owasp.webgoat.session.WebSession;
|
||||
@ -40,10 +41,13 @@ public class ReportCardServiceTest {
|
||||
private UserTrackerRepository userTrackerRepository;
|
||||
@Mock
|
||||
private WebSession websession;
|
||||
@Mock
|
||||
private PluginMessages pluginMessages;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.mockMvc = standaloneSetup(new ReportCardService(websession, userTrackerRepository, course)).build();
|
||||
this.mockMvc = standaloneSetup(new ReportCardService(websession, userTrackerRepository, course, pluginMessages)).build();
|
||||
when(pluginMessages.getMessage(anyString())).thenReturn("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -53,7 +57,7 @@ public class ReportCardServiceTest {
|
||||
when(course.getTotalOfLessons()).thenReturn(1);
|
||||
when(course.getTotalOfAssignments()).thenReturn(10);
|
||||
when(course.getLessons()).thenReturn(Lists.newArrayList(lesson));
|
||||
when(userTrackerRepository.findOne(anyString())).thenReturn(userTracker);
|
||||
when(userTrackerRepository.findByUser(anyString())).thenReturn(userTracker);
|
||||
when(userTracker.getLessonTracker(any(AbstractLesson.class))).thenReturn(lessonTracker);
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/service/reportcard.mvc"))
|
||||
.andExpect(status().isOk())
|
||||
|
@ -62,7 +62,7 @@ public class UserTrackerRepositoryTest {
|
||||
|
||||
userTrackerRepository.save(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
userTracker = userTrackerRepository.findByUser("test");
|
||||
Assertions.assertThat(userTracker.getLessonTracker("test")).isNotNull();
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ public class UserTrackerRepositoryTest {
|
||||
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
userTracker = userTrackerRepository.findByUser("test");
|
||||
Assertions.assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ public class UserTrackerRepositoryTest {
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
||||
userTracker = userTrackerRepository.findOne("test");
|
||||
userTracker = userTrackerRepository.findByUser("test");
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTracker.assignmentFailed(lesson);
|
||||
userTrackerRepository.saveAndFlush(userTracker);
|
||||
|
@ -1 +1,15 @@
|
||||
<configuration />
|
||||
<configuration />
|
||||
|
||||
<!--
|
||||
Enable below if you want to debug a unit test and see why the controller fails the configuration above is there
|
||||
to keep the Travis build going otherwise it fails with too much logging.
|
||||
//TODO we should use a different Spring profile for Travis
|
||||
-->
|
||||
|
||||
<!--
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<logger name="org.springframework.web" level="DEBUG"/>
|
||||
</configuration>
|
||||
|
||||
-->
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
|
||||
|
@ -46,7 +46,6 @@ public class Flag extends Endpoint {
|
||||
@PostConstruct
|
||||
public void initFlags() {
|
||||
IntStream.range(1, 10).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString()));
|
||||
FLAGS.entrySet().stream().forEach(e -> log.debug("Flag {} {}", e.getKey(), e.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -10,11 +10,8 @@ public interface SolutionConstants {
|
||||
|
||||
//TODO should be random generated when starting the server
|
||||
String PASSWORD = "!!webgoat_admin_1234!!";
|
||||
String SUPER_COUPON_CODE = "get_it_for_free";
|
||||
String PASSWORD_TOM = "thisisasecretfortomonly";
|
||||
String PASSWORD_LARRY = "larryknows";
|
||||
String JWT_PASSWORD = "victory";
|
||||
String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2";
|
||||
String PASSWORD_TOM_9 = "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom";
|
||||
String TOM_EMAIL = "tom@webgoat-cloud.org";
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
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";
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:Challenge_2.adoc"></div>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge2.css}"/>
|
||||
<script th:src="@{/lesson_js/challenge2.js}" language="JavaScript"></script>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<form class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/challenge/2"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
<input id="discount" type="hidden" value="0"/>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-3 item-photo">
|
||||
<img style="max-width:100%;" th:src="@{/images/samsung-black.jpg}"/>
|
||||
</div>
|
||||
<div class="col-xs-5" style="border:0px solid gray">
|
||||
<h3>Samsung Galaxy S8</h3>
|
||||
<h5 style="color:#337ab7"><a href="http://www.samsung.com">Samsung</a> ·
|
||||
<small style="color:#337ab7">(124421 reviews)</small>
|
||||
</h5>
|
||||
|
||||
<h6 class="title-price">
|
||||
<small>PRICE</small>
|
||||
</h6>
|
||||
<h3 style="margin-top:0px;"><span>US $</span><span id="price">899</span></h3>
|
||||
|
||||
<div class="section">
|
||||
<h6 class="title-attr" style="margin-top:15px;">
|
||||
<small>COLOR</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="attr" style="width:25px;background:lightgrey;"></div>
|
||||
<div class="attr" style="width:25px;background:black;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>CAPACITY</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="attr2">64 GB</div>
|
||||
<div class="attr2">128 GB</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>QUANTITY</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="btn-minus"><span class="glyphicon glyphicon-minus"></span></div>
|
||||
<input class="quantity" value="1"/>
|
||||
<div class="btn-plus"><span class="glyphicon glyphicon-plus"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>CHECKOUT CODE</small>
|
||||
</h6>
|
||||
<!--
|
||||
Checkout code: webgoat, owasp, owasp-webgoat
|
||||
-->
|
||||
<input name="checkoutCode" class="checkoutCode" value=""/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section" style="padding-bottom:20px;">
|
||||
<button type="submit" class="btn btn-success"><span style="margin-right:20px"
|
||||
class="glyphicon glyphicon-shopping-cart"
|
||||
aria-hidden="true"></span>Buy
|
||||
</button>
|
||||
<h6><a href="#"><span class="glyphicon glyphicon-heart-empty"
|
||||
style="cursor:pointer;"></span>
|
||||
Like</a></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<br/>
|
||||
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
|
||||
style="font-size:20px"></i></div>
|
||||
<input type="text" class="form-control" id="flag" name="flag"
|
||||
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
|
||||
</div>
|
||||
<div class="input-group" style="margin-top: 10px">
|
||||
<button type="submit" class="btn btn-primary">Submit flag</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</html>
|
@ -1,109 +0,0 @@
|
||||
<!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>
|
@ -22,8 +22,7 @@ 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}.
|
@ -1,10 +0,0 @@
|
||||
$(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');
|
||||
});
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
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.
|
@ -1,49 +0,0 @@
|
||||
package org.owasp.webgoat.plugin.challenge2;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpointTest;
|
||||
import org.owasp.webgoat.plugin.Flag;
|
||||
import org.owasp.webgoat.plugin.SolutionConstants;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 5/2/17.
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class Assignment2Test extends AssignmentEndpointTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Assignment2 assignment2 = new Assignment2();
|
||||
init(assignment2);
|
||||
new Flag().initFlags();
|
||||
this.mockMvc = standaloneSetup(assignment2).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/challenge/2")
|
||||
.param("checkoutCode", SolutionConstants.SUPER_COUPON_CODE))
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("flag: " + Flag.FLAGS.get(2))))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongCouponCode() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/challenge/2")
|
||||
.param("checkoutCode", "test"))
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -56,7 +56,7 @@ public class ClientSideFiltering extends NewLesson {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Client side filtering";
|
||||
return "client.side.filtering.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.owasp.webgoat.plugin.challenge2;
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.plugin.Flag;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@ -11,22 +11,23 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 4/6/17.
|
||||
*/
|
||||
@AssignmentPath("/challenge/2")
|
||||
public class Assignment2 extends AssignmentEndpoint {
|
||||
@AssignmentPath("/clientSideFiltering/getItForFree")
|
||||
@AssignmentHints({"client.side.filtering.free.hint1", "client.side.filtering.free.hint2", "client.side.filtering.free.hint3"})
|
||||
public class ClientSideFilteringFreeAssignment extends AssignmentEndpoint {
|
||||
|
||||
public static final String SUPER_COUPON_CODE = "get_it_for_free";
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public
|
||||
@ResponseBody
|
||||
AttackResult completed(@RequestParam String checkoutCode) throws IOException {
|
||||
AttackResult completed(@RequestParam String checkoutCode) {
|
||||
if (SUPER_COUPON_CODE.equals(checkoutCode)) {
|
||||
return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build();
|
||||
return trackProgress(success().build());
|
||||
}
|
||||
return failed().build();
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat.plugin.challenge2;
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import lombok.AllArgsConstructor;
|
||||
@ -12,21 +12,21 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
|
||||
import static org.owasp.webgoat.plugin.ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 4/6/17.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("challenge-store")
|
||||
@RequestMapping("/clientSideFiltering/challenge-store")
|
||||
public class ShopEndpoint {
|
||||
|
||||
@AllArgsConstructor
|
||||
private class CheckoutCodes {
|
||||
|
||||
@Getter
|
||||
private List<CheckoutCode> codes = Lists.newArrayList();
|
||||
private List<CheckoutCode> codes;
|
||||
|
||||
public Optional<CheckoutCode> get(String code) {
|
||||
return codes.stream().filter(c -> c.getCode().equals(code)).findFirst();
|
@ -73,7 +73,96 @@
|
||||
<!-- ... of course, you can move them if you want to, but that will not look consistent to other lessons -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:ClientSideFiltering_final.adoc"></div>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/clientSideFilteringFree.css}"/>
|
||||
<script th:src="@{/lesson_js/clientSideFilteringFree.js}" language="JavaScript"></script>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<form class="attack-form" accept-charset="UNKNOWN"
|
||||
method="POST" name="form"
|
||||
action="/WebGoat/clientSideFiltering/getItForFree"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
<input id="discount" type="hidden" value="0"/>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-3 item-photo">
|
||||
<img style="max-width:100%;" th:src="@{/images/samsung-black.jpg}"/>
|
||||
</div>
|
||||
<div class="col-xs-5" style="border:0px solid gray">
|
||||
<h3>Samsung Galaxy S8</h3>
|
||||
<h5 style="color:#337ab7"><a href="http://www.samsung.com">Samsung</a> ·
|
||||
<small style="color:#337ab7">(124421 reviews)</small>
|
||||
</h5>
|
||||
|
||||
<h6 class="title-price">
|
||||
<small>PRICE</small>
|
||||
</h6>
|
||||
<h3 style="margin-top:0px;"><span>US $</span><span id="price">899</span></h3>
|
||||
|
||||
<div class="section">
|
||||
<h6 class="title-attr" style="margin-top:15px;">
|
||||
<small>COLOR</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="attr" style="width:25px;background:lightgrey;"></div>
|
||||
<div class="attr" style="width:25px;background:black;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>CAPACITY</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="attr2">64 GB</div>
|
||||
<div class="attr2">128 GB</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>QUANTITY</small>
|
||||
</h6>
|
||||
<div>
|
||||
<div class="btn-minus"><span class="glyphicon glyphicon-minus"></span></div>
|
||||
<input class="quantity" value="1"/>
|
||||
<div class="btn-plus"><span class="glyphicon glyphicon-plus"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="padding-bottom:5px;">
|
||||
<h6 class="title-attr">
|
||||
<small>CHECKOUT CODE</small>
|
||||
</h6>
|
||||
<!--
|
||||
Checkout code: webgoat, owasp, owasp-webgoat
|
||||
-->
|
||||
<input name="checkoutCode" class="checkoutCode" value=""/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section" style="padding-bottom:20px;">
|
||||
<button type="submit" class="btn btn-success"><span style="margin-right:20px"
|
||||
class="glyphicon glyphicon-shopping-cart"
|
||||
aria-hidden="true"></span>Buy
|
||||
</button>
|
||||
<h6><a href="#"><span class="glyphicon glyphicon-heart-empty"
|
||||
style="cursor:pointer;"></span>
|
||||
Like</a></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</html>
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
client.side.filtering.title=Client side filtering
|
||||
ClientSideFilteringSelectUser=Select user:
|
||||
ClientSideFilteringUserID=User ID
|
||||
ClientSideFilteringFirstName=First Name
|
||||
@ -25,3 +26,7 @@ ClientSideFilteringHint10=Stage 2: Your filter operator should look something li
|
||||
ClientSideFilteringInstructions1=STAGE 1: You are logged in as Moe Stooge, CSO of Goat Hills Financial. You have access to everyone in the company's information, except the CEO, . Or at least you shouldn't have access to the CEO's information. For this exercise, examine the contents of the page to see what extra information you can find.
|
||||
ClientSideFilteringInstructions2=STAGE 2: Now, fix the problem. Modify the server to only return results that Moe Stooge is allowed to see.
|
||||
ClientSideFiltering.incorrect=This is not the salary from Neville Bartholomew...
|
||||
|
||||
client.side.filtering.free.hint1=Look through the webpage inspect the sources etc
|
||||
client.side.filtering.free.hint2=Try to see the flow of request from the page to the backend
|
||||
client.side.fiterling.free.hint3=One of the responses contains the answer
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -38,7 +38,7 @@ $(document).ready(function () {
|
||||
})
|
||||
$(".checkoutCode").on("blur", function () {
|
||||
var checkoutCode = $(".checkoutCode").val();
|
||||
$.get("challenge-store/coupons/" + checkoutCode, function (result, status) {
|
||||
$.get("clientSideFiltering/challenge-store/coupons/" + checkoutCode, function (result, status) {
|
||||
var discount = result.discount;
|
||||
if (discount > 0) {
|
||||
$('#discount').text(discount);
|
@ -0,0 +1,49 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.plugins.LessonTest;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.owasp.webgoat.plugin.ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
|
||||
/**
|
||||
* @author nbaars
|
||||
* @since 5/2/17.
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ClientSideFilteringFreeAssignmentTest extends LessonTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
ClientSideFiltering clientSideFiltering = new ClientSideFiltering();
|
||||
when(webSession.getCurrentLesson()).thenReturn(clientSideFiltering);
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
when(webSession.getUserName()).thenReturn("unit-test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/clientSideFiltering/getItForFree")
|
||||
.param("checkoutCode", SUPER_COUPON_CODE))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongCouponCode() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.post("/clientSideFiltering/getItForFree")
|
||||
.param("checkoutCode", "test"))
|
||||
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
|
||||
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.owasp.webgoat.plugin.challenge2;
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
@ -9,7 +9,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
|
||||
import static org.owasp.webgoat.plugin.ClientSideFilteringFreeAssignment.SUPER_COUPON_CODE;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
|
||||
@ -30,28 +30,28 @@ public class ShopEndpointTest {
|
||||
|
||||
@Test
|
||||
public void getSuperCoupon() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/" + SUPER_COUPON_CODE))
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/clientSideFiltering/challenge-store/coupons/" + SUPER_COUPON_CODE))
|
||||
.andExpect(jsonPath("$.code", CoreMatchers.is(SUPER_COUPON_CODE)))
|
||||
.andExpect(jsonPath("$.discount", CoreMatchers.is(100)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCoupon() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/webgoat"))
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/clientSideFiltering/challenge-store/coupons/webgoat"))
|
||||
.andExpect(jsonPath("$.code", CoreMatchers.is("webgoat")))
|
||||
.andExpect(jsonPath("$.discount", CoreMatchers.is(25)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void askForUnknownCouponCode() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/does-not-exists"))
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/clientSideFiltering/challenge-store/coupons/does-not-exists"))
|
||||
.andExpect(jsonPath("$.code", CoreMatchers.is("no")))
|
||||
.andExpect(jsonPath("$.discount", CoreMatchers.is(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchAllTheCouponsShouldContainGetItForFree() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/"))
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/clientSideFiltering/challenge-store/coupons/"))
|
||||
.andExpect(jsonPath("$.codes[3].code", is("get_it_for_free")));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -60,7 +60,7 @@ public class CrossSiteScripting extends NewLesson {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Cross Site Scripting";
|
||||
return "xss.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,4 +1,5 @@
|
||||
# XSS success, failure messages and hints
|
||||
xss.title=Cross Site Scripting
|
||||
xss-reflected-5a-success=well done, but alerts aren't very impressive are they? Please continue.
|
||||
xss-reflected-5a-failure=Try again. We do want to see this specific javascript (in case you are trying to do something more fancy)
|
||||
xss-reflected-5b-success=Correct ... because <ul><li>The script was not triggered by the URL/QueryString</li><li>Even if you use the attack URL in a new tab, it won't execute (becuase of response type). Try it if you like.</li></ul>
|
||||
|
@ -33,8 +33,10 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpointTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
@ -80,13 +82,13 @@ public class StoredXssCommentsTest extends AssignmentEndpointTest {
|
||||
*/
|
||||
|
||||
//Ensures it is vulnerable
|
||||
// @Test
|
||||
// public void isNotEncoded() throws Exception {
|
||||
// //do get to get comments after posting xss payload
|
||||
// ResultActions taintedResults = mockMvc.perform(MockMvcRequestBuilders.get("/CrossSiteScripting/stored-xss"));
|
||||
// taintedResults.andExpect(jsonPath("$[0].text",CoreMatchers.is(CoreMatchers.containsString("<script>console.warn('unit test me')</script>"))));
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void isNotEncoded() throws Exception {
|
||||
//do get to get comments after posting xss payload
|
||||
ResultActions taintedResults = mockMvc.perform(MockMvcRequestBuilders.get("/CrossSiteScripting/stored-xss"));
|
||||
MvcResult mvcResult = taintedResults.andReturn();
|
||||
assert(mvcResult.getResponse().getContentAsString().contains("<script>console.warn"));
|
||||
}
|
||||
|
||||
//Could be used to test an encoding solution ... commented out so build will pass. Uncommenting will fail build, but leaving in as positive Security Unit Test
|
||||
// @Test
|
||||
@ -95,4 +97,4 @@ public class StoredXssCommentsTest extends AssignmentEndpointTest {
|
||||
// ResultActions taintedResults = mockMvc.perform(MockMvcRequestBuilders.get("/CrossSiteScripting/stored-xss"));
|
||||
// taintedResults.andExpect(jsonPath("$[0].text",CoreMatchers.is(CoreMatchers.containsString("<scriptgt;"))));
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
</project>
|
@ -6,30 +6,31 @@ import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.owasp.webgoat.session.UserSessionData;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Created by jason on 9/29/17.
|
||||
*/
|
||||
|
||||
@AssignmentPath("/csrf/confirm-flag-1")
|
||||
@AssignmentHints({"csrf-get.hint1","csrf-get.hint2","csrf-get.hint3","csrf-get.hint4"})
|
||||
@AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"})
|
||||
public class CSRFConfirmFlag1 extends AssignmentEndpoint {
|
||||
|
||||
@Autowired
|
||||
UserSessionData userSessionData;
|
||||
|
||||
@PostMapping(produces = {"application/json"})
|
||||
public @ResponseBody AttackResult completed(String confirmFlagVal) {
|
||||
public @ResponseBody
|
||||
AttackResult completed(String confirmFlagVal) {
|
||||
|
||||
if (confirmFlagVal.equals(userSessionData.getValue("csrf-get-success").toString())) {
|
||||
return success().feedback("csrf-get-null-referer.success").output("Correct, the flag was " + userSessionData.getValue("csrf-get-success")).build();
|
||||
Object userSessionDataStr = userSessionData.getValue("csrf-get-success");
|
||||
if (userSessionDataStr != null && confirmFlagVal.equals(userSessionDataStr.toString())) {
|
||||
return trackProgress(
|
||||
success().feedback("csrf-get-null-referer.success").output("Correct, the flag was " + userSessionData.getValue("csrf-get-success")).build()
|
||||
);
|
||||
}
|
||||
|
||||
return failed().feedback("").build();
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class CSRFFeedback extends AssignmentEndpoint {
|
||||
try {
|
||||
objectMapper.readValue(feedback.getBytes(), Map.class);
|
||||
} catch (IOException e) {
|
||||
return failed().feedback(ExceptionUtils.getStackTrace(e)).build();
|
||||
return failed().feedback(ExceptionUtils.getStackTrace(e)).build();
|
||||
}
|
||||
boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().equals(MediaType.TEXT_PLAIN_VALUE);
|
||||
correctCSRF &= hostOrRefererDifferentHost(request);
|
||||
@ -64,8 +64,12 @@ public class CSRFFeedback extends AssignmentEndpoint {
|
||||
|
||||
private boolean hostOrRefererDifferentHost(HttpServletRequest request) {
|
||||
String referer = request.getHeader("referer");
|
||||
String host = request.getHeader("host");
|
||||
return !StringUtils.contains(referer, host);
|
||||
String origin = request.getHeader("origin");
|
||||
if (referer != null) {
|
||||
return !referer.contains(origin);
|
||||
} else {
|
||||
return true; //this case referer is null or origin does not matter we cannot compare so we return true which should of course be false
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestContainsWebGoatCookie(Cookie[] cookies) {
|
||||
|
@ -31,7 +31,7 @@ public class CSRFGetFlag extends Endpoint {
|
||||
@ResponseBody
|
||||
public Map<String, Object> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
|
||||
Map<String,Object> response = new HashMap<>();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host");
|
||||
// String origin = (req.getHeader("origin") == null) ? "NULL" : req.getHeader("origin");
|
||||
@ -40,22 +40,32 @@ public class CSRFGetFlag extends Endpoint {
|
||||
String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer");
|
||||
String[] refererArr = referer.split("/");
|
||||
|
||||
if (referer.equals("NULL") && req.getParameter("csrf").equals("true")) {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success",true);
|
||||
response.put("message",pluginMessages.getMessage("csrf-get-null-referer.success"));
|
||||
response.put("flag",userSessionData.getValue("csrf-get-success"));
|
||||
} else if (refererArr[2].equals(host)) {
|
||||
|
||||
|
||||
if (referer.equals("NULL")) {
|
||||
if (req.getParameter("csrf").equals("true")) {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success", true);
|
||||
response.put("message", pluginMessages.getMessage("csrf-get-null-referer.success"));
|
||||
response.put("flag", userSessionData.getValue("csrf-get-success"));
|
||||
} else {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success", true);
|
||||
response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success"));
|
||||
response.put("flag", userSessionData.getValue("csrf-get-success"));
|
||||
}
|
||||
} else if (refererArr[2].equals(host)) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Appears the request came from the original host");
|
||||
response.put("flag", null);
|
||||
} else {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success",true);
|
||||
response.put("message",pluginMessages.getMessage("csrf-get-other-referer.success"));
|
||||
response.put("flag",userSessionData.getValue("csrf-get-success"));
|
||||
response.put("success", true);
|
||||
response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success"));
|
||||
response.put("flag", userSessionData.getValue("csrf-get-success"));
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -1,69 +0,0 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.owasp.webgoat.assignments.Endpoint;
|
||||
import org.owasp.webgoat.i18n.PluginMessages;
|
||||
import org.owasp.webgoat.session.UserSessionData;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by jason on 9/30/17.
|
||||
*/
|
||||
|
||||
public class CSRFGetXhrFlag extends Endpoint {
|
||||
|
||||
@Autowired
|
||||
UserSessionData userSessionData;
|
||||
@Autowired
|
||||
private PluginMessages pluginMessages;
|
||||
|
||||
@RequestMapping(produces = {"application/json"}, method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Map<String, Object> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
|
||||
Map<String,Object> response = new HashMap<>();
|
||||
|
||||
String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host");
|
||||
// String origin = (req.getHeader("origin") == null) ? "NULL" : req.getHeader("origin");
|
||||
// Integer serverPort = (req.getServerPort() < 1) ? 0 : req.getServerPort();
|
||||
// String serverName = (req.getServerName() == null) ? "NULL" : req.getServerName();
|
||||
String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer");
|
||||
String[] refererArr = referer.split("/");
|
||||
|
||||
if (referer.equals("NULL") && req.getParameter("csrf").equals("true")) {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success",true);
|
||||
response.put("message",pluginMessages.getMessage("csrf-get-null-referer.success"));
|
||||
response.put("flag",userSessionData.getValue("csrf-get-success"));
|
||||
} else if (refererArr[2].equals(host)) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Appears the request came from the original host");
|
||||
response.put("flag", null);
|
||||
} else {
|
||||
Random random = new Random();
|
||||
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
|
||||
response.put("success",true);
|
||||
response.put("message",pluginMessages.getMessage("csrf-get-other-referer.success"));
|
||||
response.put("flag",userSessionData.getValue("csrf-get-success"));
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/csrf/get-xhr-flag";
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ public class CSRFLogin extends AssignmentEndpoint {
|
||||
}
|
||||
|
||||
private void markAssignmentSolvedWithRealUser(String username) {
|
||||
UserTracker userTracker = userTrackerRepository.findOne(username);
|
||||
UserTracker userTracker = userTrackerRepository.findByUser(username);
|
||||
userTracker.assignmentSolved(getWebSession().getCurrentLesson(), this.getClass().getSimpleName());
|
||||
userTrackerRepository.save(userTracker);
|
||||
}
|
||||
|
@ -115,22 +115,13 @@ public class ForgedReviews extends AssignmentEndpoint {
|
||||
userReviews.put(webSession.getUserName(), reviews);
|
||||
//short-circuit
|
||||
if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {
|
||||
return failed().feedback("csrf-you-forgot-something").build();
|
||||
return trackProgress(failed().feedback("csrf-you-forgot-something").build());
|
||||
}
|
||||
//we have the spoofed files
|
||||
if (referer != "NULL" && refererArr[2].equals(host) ) {
|
||||
return (failed().feedback("csrf-same-host").build());
|
||||
return trackProgress(failed().feedback("csrf-same-host").build());
|
||||
} else {
|
||||
return (success().feedback("csrf-review.success").build()); //feedback("xss-stored-comment-failure")
|
||||
}
|
||||
}
|
||||
|
||||
private Review parseJson(String comment) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
return mapper.readValue(comment, Review.class);
|
||||
} catch (IOException e) {
|
||||
return new Review();
|
||||
return trackProgress(success().feedback("csrf-review.success").build()); //feedback("xss-stored-comment-failure")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
<form accept-charset="UNKNOWN" id="basic-csrf-get"
|
||||
method="GET" name="form1"
|
||||
target="_blank"
|
||||
successCallback=""
|
||||
action="/WebGoat/csrf/basic-get-flag"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
@ -26,10 +27,12 @@
|
||||
<div class="adoc-content" th:replace="doc:CSRF_Basic_Get-1.adoc"></div>
|
||||
|
||||
<div class="attack-container">
|
||||
<img th:src="@{/images/wolf-enabled.png}" class="webwolf-enabled"/>
|
||||
<div class="assignment-success">
|
||||
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
|
||||
</i>
|
||||
</div>
|
||||
<br/>
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-1"
|
||||
method="POST" name="form2"
|
||||
successCallback=""
|
||||
@ -40,7 +43,10 @@
|
||||
<input type="text" length="6" name="confirmFlagVal" value=""/>
|
||||
|
||||
<input name="submit" value="Submit" type="submit"/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</form>
|
||||
|
||||
<div class="attack-feedback"></div>
|
||||
@ -56,9 +62,9 @@
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/reviews.css}"/>
|
||||
<script th:src="@{/lesson_js/csrf-review.js}" language="JavaScript"></script>
|
||||
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
|
||||
<div class="attack-container">
|
||||
<img th:src="@{/images/wolf-enabled.png}" class="webwolf-enabled"/>
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<div class="container-fluid">
|
||||
<div class="panel post">
|
||||
<div class="post-heading">
|
||||
@ -133,65 +139,71 @@
|
||||
padding: 7px;
|
||||
margin-top:7px;
|
||||
padding:5px;">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="well well-sm">
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="csrf-feedback"
|
||||
method="POST"
|
||||
prepareData="feedback"
|
||||
action="/WebGoat/csrf/feedback/message"
|
||||
contentType="application/json">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Name</label>
|
||||
<input type="text" class="form-control" name="name" id="name"
|
||||
placeholder="Enter name"
|
||||
required="required"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">
|
||||
Email Address</label>
|
||||
<div class="input-group">
|
||||
<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-8">
|
||||
<div class="well well-sm">
|
||||
<form class="attack-form" accept-charset="UNKNOWN" id="csrf-feedback"
|
||||
method="POST"
|
||||
prepareData="feedback"
|
||||
action="/WebGoat/csrf/feedback/message"
|
||||
contentType="application/json">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Name</label>
|
||||
<input type="text" class="form-control" name="name" id="name"
|
||||
placeholder="Enter name"
|
||||
required="required"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">
|
||||
Email Address</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-envelope"></span>
|
||||
</span>
|
||||
<input type="email" name="email" class="form-control" id="email"
|
||||
placeholder="Enter email"
|
||||
required="required"/></div>
|
||||
<input type="email" name="email" class="form-control" id="email"
|
||||
placeholder="Enter email"
|
||||
required="required"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject">
|
||||
Subject</label>
|
||||
<select id="subject" name="subject" class="form-control"
|
||||
required="required">
|
||||
<option value="na" selected="">Choose One:</option>
|
||||
<option value="service">General Customer Service</option>
|
||||
<option value="suggestions">Suggestions</option>
|
||||
<option value="product">Product Support</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject">
|
||||
Subject</label>
|
||||
<select id="subject" name="subject" class="form-control" required="required">
|
||||
<option value="na" selected="">Choose One:</option>
|
||||
<option value="service">General Customer Service</option>
|
||||
<option value="suggestions">Suggestions</option>
|
||||
<option value="product">Product Support</option>
|
||||
</select>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Message</label>
|
||||
<textarea name="message" id="message" class="form-control" rows="9"
|
||||
cols="25"
|
||||
required="required"
|
||||
placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary pull-right" id="btnContactUs">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
Message</label>
|
||||
<textarea name="message" id="message" class="form-control" rows="9" cols="25"
|
||||
required="required"
|
||||
placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary pull-right" id="btnContactUs">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -211,7 +223,6 @@
|
||||
<input name="submit" value="Submit" type="submit"/>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@ csrf-review-hint3=This one has a weak anti-CSRF protection, but you do need to o
|
||||
|
||||
csrf-feedback-hint1=Look at the content-type.
|
||||
csrf-feedback-hint2=Try to post the same message with content-type text/plain
|
||||
csrf-feedback-hint3=The json can be put into a hidden field inside
|
||||
csrf-feedback-hint3=The json can be put into a hidden field inside
|
||||
|
||||
csrf-feedback-invalid-json=Invalid JSON received.
|
||||
csrf-feedback-success=Congratulations you have found the correct solution, the flag is: {0}
|
||||
|
@ -8,11 +8,11 @@ In this assignment you need to achieve to POST the following JSON message to our
|
||||
|
||||
[source]
|
||||
----
|
||||
POST /csrf/feedback HTTP/1.1
|
||||
POST /csrf/feedback/message HTTP/1.1
|
||||
|
||||
{
|
||||
"name" : "WebGoat",
|
||||
"email" : "webgoat@webgoat.org"
|
||||
"email" : "webgoat@webgoat.org",
|
||||
"content" : "WebGoat is the best!!"
|
||||
}
|
||||
----
|
||||
|
@ -13,9 +13,14 @@ match and this will ensure the server the request is running on the same domain.
|
||||
|
||||
Remember the session cookie should always be defined with http-only flag.
|
||||
|
||||
Another effective defense can be to add a custom request header to each call. This will work if all the interactions
|
||||
== Custom headers not safe
|
||||
|
||||
Another defense can be to add a custom request header to each call. This will work if all the interactions
|
||||
with the server are performed with JavaScript. On the server side you only need to check the presence of this header
|
||||
if this header is not present deny the request.
|
||||
Some frameworks offer this implementation by default however researcer Alex Infuhr found out that this can be bypassed
|
||||
as well. You can read about: http://insert-blogspot.nl/2018/05/adobe-reader-pdf-client-side-request.html?m=1[Adobe Reader PDF - Client Side Request Injection]
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,7 +4,20 @@ The impact is limited only by what the logged in user can do (if the site/functi
|
||||
The areas that are really prone to CSRF attacks are IoT devices and 'smart' appliances. Sadly, many consumer-grade routers
|
||||
have also proven vulnerable to CSRF.
|
||||
|
||||
== CSRF Solution
|
||||
== CSRF solutions
|
||||
|
||||
=== Same site cookie attribute
|
||||
|
||||
This is a new extension which modern browsers support which limits the scope of the cookie such that it will only be
|
||||
attached to requests if those requests are 'same-site'
|
||||
For example requests for `http://webgoat.org/something` will attach same-site cookies if the request is initiated from
|
||||
`webgoat.org`.
|
||||
There are two modes, strict and lax. The first one does not allow cross site request, this means when you are on
|
||||
github.com and you want to like it through Facebook (and Facebook specifies same-site as strict) you will be
|
||||
redirected to the login page, because the browser does not attach the cookie for Facebook.
|
||||
More information can be found here: www.sjoerdlangkemper.nl/2016/04/14/preventin-csrf-with-samesite-cookie-attribute/
|
||||
|
||||
=== Other protections
|
||||
|
||||
Fortunately, many (web) application frameworks now come with built in support to handle CSRF attacks. For example, Spring and
|
||||
Tomcat have this on by default. As long as you don't turn it off (like it is in WebGoat), you should be safe from CSRF attacks.
|
||||
|
@ -3,7 +3,7 @@
|
||||
A lot of web applications implement no protection against CSRF they are somehow protected by the fact that
|
||||
they only work with `application/json` as content type. The only way to make a request with this content-type from the
|
||||
browser is with a XHR request. Before the browser can make such a request a preflight request will be made towards
|
||||
the server (remember the CSRF request will be cross origin). If the preflight response does not allow the cross origin
|
||||
the server (remember the CSRF request will be cross origin). If the pre-flight response does not allow the cross origin
|
||||
request the browser will not make the call.
|
||||
|
||||
To make a long answer short: this is *not* a valid protection against CSRF.
|
||||
|
@ -46,13 +46,10 @@ public class CSRFFeedbackTest extends LessonTest {
|
||||
mockMvc.perform(post("/csrf/feedback/message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.cookie(new Cookie("JSESSIONID", "test"))
|
||||
.header("host", "localhost:8080")
|
||||
.header("origin", "localhost:8080")
|
||||
.header("referer", "webgoat.org")
|
||||
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
|
||||
.andExpect(jsonPath("lessonCompleted", is(true)))
|
||||
.andExpect(jsonPath("feedback", StringContains.containsString("the flag is: ")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
||||
|
@ -8,6 +8,7 @@ http-basics.hints.http_basic_quiz.1=Turn on Show Parameters or other features
|
||||
http-basics.hints.http_basic_quiz.2=Try to intercept the request with <a href='https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project' title='Link to ZAP'>OWASP ZAP</a>
|
||||
|
||||
|
||||
http-basics.empty=Try again, name cannot be empty.
|
||||
http-basics.reversed=The server has reversed your name: {0}
|
||||
|
||||
http-basics.close=Try again: but this time enter a value before hitting go.
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@ -48,7 +48,7 @@ public class HttpBasicsInterceptRequest extends AssignmentEndpoint {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
public @ResponseBody
|
||||
AttackResult completed(HttpServletRequest request) throws IOException {
|
||||
AttackResult completed(HttpServletRequest request) {
|
||||
String header = null;
|
||||
String param = null;
|
||||
if (request != null && (header = request.getHeader("x-request-intercepted")) != null
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>8.0.0.M3</version>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
</project>
|
27
webgoat-lessons/insecure-deserialization/pom.xml
Executable file
27
webgoat-lessons/insecure-deserialization/pom.xml
Executable file
@ -0,0 +1,27 @@
|
||||
<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>insecure-deserialization</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<parent>
|
||||
<groupId>org.owasp.webgoat.lesson</groupId>
|
||||
<artifactId>webgoat-lessons-parent</artifactId>
|
||||
<version>v8.0.0.M15</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>4.1.3.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,63 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import org.owasp.webgoat.lessons.Category;
|
||||
import org.owasp.webgoat.lessons.NewLesson;
|
||||
|
||||
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 InsecureDeserialization extends NewLesson {
|
||||
@Override
|
||||
public Category getDefaultCategory() {
|
||||
return Category.INSECURE_DESERIALIZATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHints() {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDefaultRanking() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "insecure-deserialization.title";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "InsecureDeserialization";
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.owasp.webgoat.plugin;
|
||||
|
||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||
import org.owasp.webgoat.assignments.AttackResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* *************************************************************************************************
|
||||
*
|
||||
*
|
||||
* This file is part of WebGoat, an Open Web Application Security Project
|
||||
* utility. For details, please see http://www.owasp.org/
|
||||
*
|
||||
* Copyright (c) 2002 - 20014 Bruce Mayhew
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Getting Source ==============
|
||||
*
|
||||
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository
|
||||
* for free software projects.
|
||||
*
|
||||
* For details, please see http://webgoat.github.io
|
||||
*
|
||||
* @author Bruce Mayhew <a href="http://code.google.com/p/webgoat">WebGoat</a>
|
||||
* @created October 28, 2003
|
||||
*/
|
||||
@AssignmentPath("/InsecureDeserialization/task")
|
||||
public class InsecureDeserializationTask extends AssignmentEndpoint {
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public
|
||||
@ResponseBody
|
||||
AttackResult completed(@RequestParam String token) throws IOException {
|
||||
String b64token;
|
||||
byte [] data;
|
||||
ObjectInputStream ois;
|
||||
Object o;
|
||||
long before, after;
|
||||
int delay;
|
||||
|
||||
b64token = token.replace('-', '+').replace('_', '/');
|
||||
try {
|
||||
data = Base64.getDecoder().decode(b64token);
|
||||
ois = new ObjectInputStream( new ByteArrayInputStream(data) );
|
||||
} catch (Exception e) {
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
|
||||
before = System.currentTimeMillis();
|
||||
try {
|
||||
o = ois.readObject();
|
||||
} catch (Exception e) {
|
||||
o = null;
|
||||
}
|
||||
after = System.currentTimeMillis();
|
||||
ois.close();
|
||||
|
||||
delay = (int)(after - before);
|
||||
if ( delay > 7000 ) {
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
if ( delay < 3000 ) {
|
||||
return trackProgress(failed().build());
|
||||
}
|
||||
return trackProgress(success().build());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:InsecureDeserialization_Intro.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:InsecureDeserialization_WhatIs.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:InsecureDeserialization_SimpleExploit.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<div class="adoc-content" th:replace="doc:InsecureDeserialization_GadgetChain.adoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="lesson-page-wrapper">
|
||||
<!-- stripped down without extra comments -->
|
||||
<div class="adoc-content" th:replace="doc:InsecureDeserialization_Task.adoc"></div>
|
||||
<div class="attack-container">
|
||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||
<script th:src="@{/lesson_js/credentials.js}"
|
||||
language="JavaScript"></script>
|
||||
<form class="attack-form" accept-charset="UNKNOWN" name="task"
|
||||
method="POST"
|
||||
action="/WebGoat/InsecureDeserialization/task"
|
||||
enctype="application/json;charset=UTF-8">
|
||||
|
||||
<input type="textarea" rows="4" cols="40" value="" name="token" placeholder="token"/>
|
||||
<input type="submit" value="Submit" />
|
||||
|
||||
</form>
|
||||
<div class="attack-feedback"></div>
|
||||
<div class="attack-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
</html>
|
@ -0,0 +1,4 @@
|
||||
insecure-deserialization.title=Insecure Deserialization
|
||||
|
||||
insecure-deserialization.intercept.success=Dangerous object received!
|
||||
insecure-deserialization.intercept.failure=Try again
|
@ -0,0 +1,6 @@
|
||||
function submit_secret_credentials() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp['open']('POST', '#attack/307/100', true);
|
||||
//sending the request is obfuscated, to descourage js reading
|
||||
var _0xb7f9=["\x43\x61\x70\x74\x61\x69\x6E\x4A\x61\x63\x6B","\x42\x6C\x61\x63\x6B\x50\x65\x61\x72\x6C","\x73\x74\x72\x69\x6E\x67\x69\x66\x79","\x73\x65\x6E\x64"];xhttp[_0xb7f9[3]](JSON[_0xb7f9[2]]({username:_0xb7f9[0],password:_0xb7f9[1]}))
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
== What is a Gadgets Chain
|
||||
|
||||
It is weird (but it could happen) to find a gadget that runs dangerous actions itself when is deserialized. However, it is much easier to find a gadget that runs action on other gadget when it is deserializaded, and that second gadget runs more actions on a third gadget, and so on until a real dangerous action is triggered. That set of gadgets that can be used in a deserialization process to achieve dangerous actions is called "Gadget Chain".
|
||||
|
||||
Finding gadgets to build gadget chains is an active topic for security researchers. This kind of research usually requires to spend a big amount of time reading code.
|
@ -0,0 +1,10 @@
|
||||
|
||||
== Concept
|
||||
|
||||
This lesson describes what is Serialization and how it can be manipulated to perform tasks that were not the original intent of the developer.
|
||||
|
||||
== Goals
|
||||
* The user should have a basic understanding of Java programming language
|
||||
* The user will be able to detect insecure deserialization vulnerabilities
|
||||
* The user will be able to exploit insecure deserialization vulnerabilities
|
||||
* Exploiting deserialization is slightly different in other programming languages such as PHP or Python, but the key concepts learnt here also applies to all of them
|
@ -0,0 +1,46 @@
|
||||
== The Simplest Exploit
|
||||
|
||||
=== Vulnerable code
|
||||
|
||||
The following is a well-known example for a Java Deserialization vulnerability.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
InputStream is = request.getInputStream();
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
AcmeObject acme = (AcmeObject)ois.readObject();
|
||||
----
|
||||
|
||||
It is expecting an `AcmeObject` object, but it will execute `readObject()` before the casting ocurs.
|
||||
If an attacker finds the proper class implementing dangerous operations in `readObject()`, he could serialize that object and force the vulnerable application to performe those actions.
|
||||
|
||||
=== Class included in ClassPath
|
||||
|
||||
Attackers need to find a class in the classpath that supports serialization and with dangerous implementations on `readObject()`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class GadgetObject implements Serializable {
|
||||
String cmd;
|
||||
|
||||
private void readObject( ObjectInputStream stream ) throws Exception {
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Exploit
|
||||
|
||||
If the java class shown above exists, attackers can serialize that object and obtain Remote Code Execution.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
GadgetObject go = new GadgetObject();
|
||||
go.cmd = "touch /tmp/pwned.txt";
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(go);
|
||||
oos.flush();
|
||||
byte[] exploit = bos.toByteArray();
|
||||
----
|
@ -0,0 +1,8 @@
|
||||
=== Let's try
|
||||
The following input box receives a serialized object (a string) and it deserialzes it.
|
||||
|
||||
```
|
||||
rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l
|
||||
```
|
||||
|
||||
Try to change this serialized object in order to delay the page response for exactly 5 seconds.
|
@ -0,0 +1,23 @@
|
||||
== What is Serialization
|
||||
|
||||
Serialization is the process of turning some object into a data format that can be restored later. People often serialize objects in order to save them to storage, or to send as part of communications. Deserialization is the reverse of that process taking data structured from some format, and rebuilding it into an object. Today, the most popular data format for serializing data is JSON. Before that, it was XML.
|
||||
|
||||
----
|
||||
a:4:{i:0;i:132;i:1;s:7:"Mallory";i:2;s:4:"user"; i:3;s:32:"b6a8b3bea87fe0e05022f8f3c88bc960";}
|
||||
----
|
||||
|
||||
=== Native Serialization
|
||||
|
||||
Many programming languages offer a native capability for serializing objects. These native formats usually offer more features than JSON or XML, including customizability of the serialization process. Unfortunately, the features of these native deserialization mechanisms can be repurposed for malicious effect when operating on untrusted data. Attacks against deserializers have been found to allow denial-of-service, access control, and remote code execution attacks.
|
||||
|
||||
=== Known Affected Programming Languages
|
||||
* PHP
|
||||
* Python
|
||||
* Ruby
|
||||
* Java
|
||||
* C
|
||||
* C++
|
||||
|
||||
=== Data, not Code
|
||||
|
||||
ONLY data is serialized. Code is not serialized itself. Deserialization creates a new object and copies all the data from the byte stream, in order to obtain and object identical to the object that was serialized.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user