目录
1.XSS攻击
1.1 存储型 XSS(Stored XSS)
1.2 反射型 XSS(Reflected XSS)
1.3 DOM 型 XSS(DOM-based XSS)
1.4 修复方案
2.日志伪造
2.1 伪造方法
2.2 解决方法
2.3 验证结果
3.ESAPI
第一步:引入Jar
第二步:加入配置文件
第三步:测试
4.点击劫持漏洞
5.检测到目标URL存在http host头攻击漏洞
7.过滤request中包含XSS代码
1.XSS攻击
XSS(跨站脚本攻击)是一种常见的网络安全漏洞,攻击者利用这种漏洞在受害者的网页上注入恶意的客户端脚本,从而在受害者的浏览器上执行恶意代码。这些恶意脚本通常是 JavaScript。攻击者利用 XSS 可以窃取用户的会话信息、篡改网页内容、重定向用户到恶意网站等。XSS 攻击通常分为存储型 XSS、反射型 XSS 和 DOM 型 XSS 三种类型,每种类型攻击的方式和影响略有不同。为了防止 XSS 攻击,开发人员需要在开发过程中采取一些安全措施,如过滤用户输入、对输出进行编码等。
1.1 存储型 XSS(Stored XSS)
存储型 XSS 是指恶意脚本被存储在服务器端,并在用户访问包含这些恶意脚本的页面时被执行。
攻击者通常会将恶意代码存储在数据库、文件或其他服务器端资源中,然后等待用户访问包含这些存储的数据的页面。
当用户请求这些页面时,服务器会从存储中检索数据,并将恶意代码包含在响应中,从而导致浏览器执行恶意脚本。
1.2 反射型 XSS(Reflected XSS)
反射型 XSS 是指恶意脚本作为参数包含在受害者的请求中,然后服务器将该参数的值包含在响应中,导致浏览器执行恶意脚本。
攻击者通常会诱使受害者点击包含恶意脚本的链接,或者将包含恶意脚本的参数发送给受害者。
当受害者点击链接或提交表单时,恶意脚本会作为参数发送到服务器。服务器在处理请求时,将恶意脚本包含在响应中,从而导致浏览器执行恶意脚本。
1.3 DOM 型 XSS(DOM-based XSS)
DOM 型 XSS 是指恶意脚本通过修改页面的 DOM 结构来执行的 XSS 攻击。
攻击者通常会构造一个特制的 URL,当受害者访问该 URL 时,恶意脚本会被执行。
浏览器解析 URL 并执行其中的脚本,而不是服务器返回的响应。恶意脚本直接修改了页面的 DOM 结构,导致恶意行为发生。
防范这些类型的 XSS 攻击需要采取一系列安全措施,如输入验证、输出编码、内容安全策略(CSP)等。
1.4 修复方案
针对不同类型的 XSS 攻击,有不同的修复方案。以下是一些常见的修复方案:
存储型 XSS:
进行严格的输入验证,包括对用户输入进行过滤和转义,以防止恶意脚本被存储到服务器上。
对于用户提交的内容,应该进行适当的编码,如 HTML 编码、JavaScript 编码等,以防止恶意脚本在页面上执行。
反射型 XSS:
同样需要进行严格的输入验证和输出编码,确保用户输入的内容不会直接插入到响应中。
对于从 URL 参数中获取的值,应该进行过滤和转义,以防止恶意脚本被执行。
DOM 型 XSS:
避免直接使用用户输入的内容来修改页面的 DOM 结构。
使用安全的 API 来操作 DOM,例如通过 document.createElement()、document.createTextNode() 等方法来创建 DOM 元素和文本节点,而不是直接通过 innerHTML 或 document.write() 等方法插入 HTML 代码。
除了以上的具体修复方案,还有一些通用的安全措施可以帮助防止 XSS 攻击:
实施内容安全策略(Content Security Policy,CSP),限制页面加载的资源和执行的脚本,防止恶意脚本的执行。
使用安全的框架和库来开发应用程序,这些框架和库通常提供了内置的安全功能,如自动转义用户输入、防止 XSS 攻击等。
对于敏感操作,如用户认证、支付等,使用 HTTPS 协议来加密通信,防止中间人攻击和会话劫持。
2.日志伪造
日志伪造指的是对系统、应用程序或者网络中的日志信息进行篡改、伪造或者欺骗,以达到掩盖、修改或者误导真实事件的目的。这种行为可能是恶意的,旨在隐藏攻击者的活动,模糊攻击的痕迹,或者误导系统管理员、安全团队等人员的调查方向。
日志伪造可能涉及以下行为:
修改日志内容: 攻击者可能访问日志文件,并直接修改其中的内容,以删除或修改有关攻击行为的记录。
篡改日志时间戳: 攻击者可能更改日志中事件发生的时间戳,以使事件看起来发生在不同的时间,或者与其他事件同时发生。
注入虚假日志记录: 攻击者可能通过注入虚假的日志记录来模糊事件的真实性。这可能包括添加不存在的事件,或者伪造与真实事件相关的日志信息。
欺骗性日志信息: 攻击者可能编写欺骗性的日志信息,试图误导分析人员,使其得出错误的结论,或者转移调查的注意力。
对于保护系统免受日志伪造的影响,通常采取以下措施:
日志加密和完整性保护: 使用加密技术确保日志文件的完整性和保密性,以防止未经授权的访问和修改。
安全日志管理实践: 实施良好的安全日志管理实践,包括限制对日志文件的访问权限,并记录对日志文件的任何更改。
实时监控和警报: 配置实时监控系统来检测异常日志活动,并在发现异常时发出警报。
日志审计和分析: 定期审计和分析日志文件,以检测潜在的日志伪造行为,并及时采取相应的应对措施。
通过这些措施,可以增强系统对于日志伪造行为的检测和防范能力,提高系统的安全性和可靠性。
2.1 伪造方法
示例代码如下:
@Slf4j
public class SafeLoggerTest {
@Test
@SneakyThrows
public void testLog() {
String username;
// 正常值
username = "Tom";
log.info("User {} login success!", username);
// 伪造值
username = "Tom login success!\r\n2023-11-24 09:28:20,131 [main] INFO pdfc.framework.log.SafeLoggerTest:22 - User 伪造账号";
log.info("User {} login success!", username);
}
}
运行结果如下:
可以看到第3行日志为伪造日志,日志分析系统处理时难以识别
2.2 解决方法
需要将对应的定义进行替换,如
private static final Logger log = LoggerFactory.getLogger(SafeLoggerTest.class);
替换为
private static final SafeLogger log =
SafeLoggerFactory.getLogger(SafeLoggerTest.class.getName());
2.3 验证结果
日志输出为
可以看到第2行直接输出成一行,问题解决。
完整代码:
SafeLoggerFactory.java
import org.slf4j.LoggerFactory;
/**
* 安全的LoggerFactory
*
*/
public class SafeLoggerFactory {
public static SafeLogger getLogger(String name) {
return new SafeLogger(LoggerFactory.getLogger(name));
}
public static SafeLogger getLogger(Class<?> clazz) {
return getLogger(clazz.getName());
}
}
SafeLogger.java
import org.slf4j.Logger;
import org.slf4j.Marker;
import java.io.StringWriter;
/**
* 安全的Logger类,抵御日志伪造
*
*/
public class SafeLogger implements Logger {
private final Logger loggerWrapper;
public SafeLogger(Logger logger) {
this.loggerWrapper = logger;
}
/**
* 转义可变参数列表
*
* @param input 可变参
* @return 转义后的对象
*/
private Object translateVarParams(Object... input) {
Object result;
if (input == null) {
result = null;
} else if (input instanceof Object[]) {
for (int i = 0; i < input.length; i++) {
input[i] = translate(input[i]);
}
result = input;
} else {
result = translate(input);
}
return result;
}
/**
* 转义对象(只支持字符串)
*
* @param input 传入对象
* @return 转义后的对象
*/
private Object translate(Object input) {
Object result = input;
if (input != null && input instanceof String) {
result = translate((String) input);
}
return result;
}
/**
* 转义字符串
*
* @param input 传入字符串
* @return 转义后的字符串
*/
private String translate(String input) {
int len = input.length();
StringWriter writer = new StringWriter(len * 2);
for (int i = 0; i < len; i++) {
char chr = input.charAt(i);
// 转义Java控制字符
switch (chr) {
case '\b':
writer.append("\\b");
break;
case '\n':
writer.append("\\n");
break;
case '\t':
writer.append("\\t");
break;
case '\f':
writer.append("\\f");
break;
case '\r':
writer.append("\\r");
break;
default:
writer.append(chr);
}
}
return writer.toString();
}
@Override
public String getName() {
return loggerWrapper.getName();
}
@Override
public boolean isTraceEnabled() {
return loggerWrapper.isTraceEnabled();
}
@Override
public void trace(String msg) {
loggerWrapper.trace(translate(msg));
}
@Override
public void trace(String format, Object arg) {
loggerWrapper.trace(translate(format), translate(arg));
}
@Override
public void trace(String format, Object arg1, Object arg2) {
loggerWrapper.trace(translate(format), translate(arg1), translate(arg2));
}
@Override
public void trace(String format, Object... arguments) {
loggerWrapper.trace(translate(format), translateVarParams(arguments));
}
@Override
public void trace(String msg, Throwable t) {
loggerWrapper.trace(translate(msg), translate(t));
}
@Override
public boolean isTraceEnabled(Marker marker) {
return loggerWrapper.isTraceEnabled(marker);
}
@Override
public void trace(Marker marker, String msg) {
loggerWrapper.trace(marker, translate(msg));
}
@Override
public void trace(Marker marker, String format, Object arg) {
loggerWrapper.trace(marker, translate(format), translate(arg));
}
@Override
public void trace(Marker marker, String format, Object arg1, Object arg2) {
loggerWrapper.trace(marker, translate(format), translate(arg1), translate(arg2));
}
@Override
public void trace(Marker marker, String format, Object... argArray) {
loggerWrapper.trace(marker, translate(format), translateVarParams(argArray));
}
@Override
public void trace(Marker marker, String msg, Throwable t) {
loggerWrapper.trace(marker, translate(msg), translate(t));
}
@Override
public boolean isDebugEnabled() {
return loggerWrapper.isDebugEnabled();
}
@Override
public void debug(String msg) {
loggerWrapper.debug(translate(msg));
}
@Override
public void debug(String format, Object arg) {
loggerWrapper.debug(translate(format), translate(arg));
}
@Override
public void debug(String format, Object arg1, Object arg2) {
loggerWrapper.debug(translate(format), translate(arg1), translate(arg2));
}
@Override
public void debug(String format, Object... arguments) {
loggerWrapper.debug(translate(format), translateVarParams(arguments));
}
@Override
public void debug(String msg, Throwable t) {
loggerWrapper.debug(translate(msg), translate(t));
}
@Override
public boolean isDebugEnabled(Marker marker) {
return loggerWrapper.isDebugEnabled(marker);
}
@Override
public void debug(Marker marker, String msg) {
loggerWrapper.debug(marker, translate(msg));
}
@Override
public void debug(Marker marker, String format, Object arg) {
loggerWrapper.debug(marker, translate(format), translate(arg));
}
@Override
public void debug(Marker marker, String format, Object arg1, Object arg2) {
loggerWrapper.debug(marker, translate(format), translate(arg1), translate(arg2));
}
@Override
public void debug(Marker marker, String format, Object... arguments) {
loggerWrapper.debug(marker, translate(format), translateVarParams(arguments));
}
@Override
public void debug(Marker marker, String msg, Throwable t) {
loggerWrapper.debug(marker, translate(msg), translate(t));
}
@Override
public boolean isInfoEnabled() {
return loggerWrapper.isInfoEnabled();
}
@Override
public void info(String msg) {
loggerWrapper.info(translate(msg));
}
@Override
public void info(String format, Object arg) {
loggerWrapper.info(translate(format), translate(arg));
}
@Override
public void info(String format, Object arg1, Object arg2) {
loggerWrapper.info(translate(format), translate(arg1), translate(arg2));
}
@Override
public void info(String format, Object... arguments) {
loggerWrapper.info(translate(format), translateVarParams(arguments));
}
@Override
public void info(String msg, Throwable t) {
loggerWrapper.info(translate(msg), translate(t));
}
@Override
public boolean isInfoEnabled(Marker marker) {
return loggerWrapper.isInfoEnabled(marker);
}
@Override
public void info(Marker marker, String msg) {
loggerWrapper.info(marker, translate(msg));
}
@Override
public void info(Marker marker, String format, Object arg) {
loggerWrapper.info(marker, translate(format), translate(arg));
}
@Override
public void info(Marker marker, String format, Object arg1, Object arg2) {
loggerWrapper.info(marker, translate(format), translate(arg1), translate(arg2));
}
@Override
public void info(Marker marker, String format, Object... arguments) {
loggerWrapper.info(marker, translate(format), translateVarParams(arguments));
}
@Override
public void info(Marker marker, String msg, Throwable t) {
loggerWrapper.info(marker, translate(msg), translate(t));
}
@Override
public boolean isWarnEnabled() {
return loggerWrapper.isWarnEnabled();
}
@Override
public void warn(String msg) {
loggerWrapper.warn(translate(msg));
}
@Override
public void warn(String format, Object arg) {
loggerWrapper.warn(translate(format), translate(arg));
}
@Override
public void warn(String format, Object... arguments) {
loggerWrapper.warn(translate(format), translateVarParams(arguments));
}
@Override
public void warn(String format, Object arg1, Object arg2) {
loggerWrapper.warn(translate(format), translate(arg1), translate(arg2));
}
@Override
public void warn(String msg, Throwable t) {
loggerWrapper.warn(translate(msg), translate(t));
}
@Override
public boolean isWarnEnabled(Marker marker) {
return loggerWrapper.isWarnEnabled(marker);
}
@Override
public void warn(Marker marker, String msg) {
loggerWrapper.warn(marker, translate(msg));
}
@Override
public void warn(Marker marker, String format, Object arg) {
loggerWrapper.warn(marker, translate(format), translate(arg));
}
@Override
public void warn(Marker marker, String format, Object arg1, Object arg2) {
loggerWrapper.warn(marker, translate(format), translate(arg1), translate(arg2));
}
@Override
public void warn(Marker marker, String format, Object... arguments) {
loggerWrapper.warn(marker, translate(format), translateVarParams(arguments));
}
@Override
public void warn(Marker marker, String msg, Throwable t) {
loggerWrapper.warn(marker, translate(msg), translate(t));
}
@Override
public boolean isErrorEnabled() {
return loggerWrapper.isErrorEnabled();
}
@Override
public void error(String msg) {
loggerWrapper.error(translate(msg));
}
@Override
public void error(String format, Object arg) {
loggerWrapper.error(translate(format), translate(arg));
}
@Override
public void error(String format, Object arg1, Object arg2) {
loggerWrapper.error(translate(format), translate(arg1), translate(arg2));
}
@Override
public void error(String format, Object... arguments) {
loggerWrapper.error(translate(format), translateVarParams(arguments));
}
@Override
public void error(String msg, Throwable t) {
loggerWrapper.error(translate(msg), translate(t));
}
@Override
public boolean isErrorEnabled(Marker marker) {
return loggerWrapper.isErrorEnabled(marker);
}
@Override
public void error(Marker marker, String msg) {
loggerWrapper.error(marker, translate(msg));
}
@Override
public void error(Marker marker, String format, Object arg) {
loggerWrapper.error(marker, translate(format), translate(arg));
}
@Override
public void error(Marker marker, String format, Object arg1, Object arg2) {
loggerWrapper.error(marker, translate(format), translate(arg1), translate(arg2));
}
@Override
public void error(Marker marker, String format, Object... arguments) {
loggerWrapper.error(marker, translate(format), translateVarParams(arguments));
}
@Override
public void error(Marker marker, String msg, Throwable t) {
loggerWrapper.error(marker, translate(msg), translate(t));
}
}
3.ESAPI
ESAPI 是一个用于 Web 应用程序安全的开源软件项目,全称为 "Enterprise Security API"。它由 OWASP(Open Web Application Security Project)组织开发和维护,旨在提供一组易于使用的安全功能和接口,帮助开发人员构建更安全的 Web 应用程序。
ESAPI 提供了一系列的安全功能,包括输入验证、输出编码、会话管理、加密和访问控制等,帮助开发人员防御常见的 Web 安全威胁,如 XSS(跨站脚本攻击)、CSRF(跨站请求伪造)、SQL 注入等。通过使用 ESAPI,开发人员可以轻松地集成标准化的安全控制,提高他们的应用程序对安全漏洞的抵御能力。
总的来说,ESAPI 是一个强大的工具,旨在帮助开发人员构建更安全的 Web 应用程序,减少安全漏洞的风险,并保护用户数据的安全。
ESAPI是owasp提供的一套API级别的web应用解决方案。简单的说,ESAPI就是为了编写出更加安全的代码而设计出来的一些API,方便使用者调用,从而方便的编写安全的代码
其官方网站为:https://www.owasp.org/,其有很多针对不同语言的版本,其J2ee的版本需要jre1.5及以上支持
第一步:引入Jar
Maven
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
<version>2.1.0.1</version>
</dependency>
直接下载Jar
Maven Central Repository Search
第二步:加入配置文件
在工程的资源文件目录下增加配置文件ESAPI.properties及validation.properties,文件内容可为空。如果为空则都取默认值。建议参考以下文件内容设置
ESAPI.properties
# \u662F\u5426\u8981\u6253\u5370\u914D\u7F6E\u5C5E\u6027,\u9ED8\u8BA4\u4E3Atrue
ESAPI.printProperties=true
ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController
ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator
ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor
ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor
ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities
ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector
ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory
ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer
ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator
#===========================================================================
# ESAPI Encoder
Encoder.AllowMultipleEncoding=false
Encoder.AllowMixedEncoding=false
Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec
#===========================================================================
# ESAPI \u52A0\u5BC6\u6A21\u5757
Encryptor.PreferredJCEProvider=
Encryptor.EncryptionAlgorithm=AES
Encryptor.CipherTransformation=AES/CBC/PKCS5Padding
Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC
Encryptor.cipher_modes.additional_allowed=CBC
Encryptor.EncryptionKeyLength=128
Encryptor.ChooseIVMethod=random
Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f
Encryptor.CipherText.useMAC=true
Encryptor.PlainText.overwrite=true
Encryptor.HashAlgorithm=SHA-512
Encryptor.HashIterations=1024
Encryptor.DigitalSignatureAlgorithm=SHA1withDSA
Encryptor.DigitalSignatureKeyLength=1024
Encryptor.RandomAlgorithm=SHA1PRNG
Encryptor.CharacterEncoding=UTF-8
Encryptor.KDF.PRF=HmacSHA256
#===========================================================================
# ESAPI Http\u5DE5\u5177
HttpUtilities.UploadDir=C:\\ESAPI\\testUpload
HttpUtilities.UploadTempDir=C:\\temp
# Force flags on cookies, if you use HttpUtilities to set cookies
HttpUtilities.ForceHttpOnlySession=false
HttpUtilities.ForceSecureSession=false
HttpUtilities.ForceHttpOnlyCookies=true
HttpUtilities.ForceSecureCookies=true
# Maximum size of HTTP headers
HttpUtilities.MaxHeaderSize=4096
# File upload configuration
HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll
HttpUtilities.MaxUploadFileBytes=500000000
# Using UTF-8 throughout your stack is highly recommended. That includes your database driver,
# container, and any other technologies you may be using. Failure to do this may expose you
# to Unicode transcoding injection attacks. Use of UTF-8 does not hinder internationalization.
HttpUtilities.ResponseContentType=text/html; charset=UTF-8
# This is the name of the cookie used to represent the HTTP session
# Typically this will be the default "JSESSIONID"
HttpUtilities.HttpSessionIdName=JSESSIONID
#===========================================================================
# ESAPI Executor
Executor.WorkingDirectory=
Executor.ApprovedExecutables=
#===========================================================================
# ESAPI Logging
# Set the application name if these logs are combined with other applications
Logger.ApplicationName=ExampleApplication
# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true
Logger.LogEncodingRequired=false
# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.
Logger.LogApplicationName=true
# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.
Logger.LogServerIP=true
# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you
# want to place it in a specific directory.
Logger.LogFileName=ESAPI_logging_file
# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)
Logger.MaxLogFileSize=10000000
#===========================================================================
# ESAPI Intrusion Detection
IntrusionDetector.Disable=false
IntrusionDetector.event.test.count=2
IntrusionDetector.event.test.interval=10
IntrusionDetector.event.test.actions=disable,log
IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1
IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1
IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout
IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10
IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5
IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout
IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2
IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10
IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout
#===========================================================================
# ESAPI \u6821\u9A8C\u5668
#\u6821\u9A8C\u5668\u7684\u914D\u7F6E\u6587\u4EF6
Validator.ConfigurationFile=validation.properties
# Validators used by ESAPI
Validator.AccountName=^[a-zA-Z0-9]{3,20}$
Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$
Validator.RoleName=^[a-z]{1,20}$
#the word TEST below should be changed to your application
#name - only relative URL's are supported
Validator.Redirect=^\\/test.*$
# Global HTTP Validation Rules
# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]
Validator.HTTPScheme=^(http|https)$
Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$
Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$
Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$
Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$
Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$
# Note that max header name capped at 150 in SecurityRequestWrapper!
Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,50}$
Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$
Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$
Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$
Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$
Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
Validator.HTTPURL=^.*$
Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$
# Validation of file related input
Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
# Validation of dates. Controls whether or not 'lenient' dates are accepted.
# See DataFormat.setLenient(boolean flag) for further details.
Validator.AcceptLenientDates=false
validation.properties
# 校验某个字段的正则表达式
Validator.SafeString=^[.\\p{Alnum}\\p{Space}]{0,1024}$
Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$
Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&%\\$#_]*)?$
Validator.CreditCard=^(\\d{4}[- ]?){3}\\d{4}$
Validator.SSN=^(?!000)([0-6]\\d{2}|7([0-6]\\d|7[012]))([ -]?)(?!00)\\d\\d\\3(?!0000)\\d{4}$
#允许通过 /path1/path2/.../ 格式的路径以及 Windows 和 Linux 路径
Validator.SitePath=^(?:\/(?:[^\/\s?#]+\/)+|(?:(?:[a-zA-Z]:|\/)[^\?\#]*\/)+)$
第三步:测试
ESAPITest .java
import org.junit.Test;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.ValidationErrorList;
import org.owasp.esapi.Validator;
import org.owasp.esapi.codecs.MySQLCodec;
import org.owasp.esapi.errors.ValidationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* ESAPI 是一个用于 Web 应用程序安全的开源软件项目,全称为 "Enterprise Security API"。它由 OWASP(Open Web Application Security Project)组织开发和维护,旨在提供一组易于使用的安全功能和接口,帮助开发人员构建更安全的 Web 应用程序。
* ESAPI 提供了一系列的安全功能,包括输入验证、输出编码、会话管理、加密和访问控制等,帮助开发人员防御常见的 Web 安全威胁,如 XSS(跨站脚本攻击)、CSRF(跨站请求伪造)、SQL 注入等。通过使用 ESAPI,开发人员可以轻松地集成标准化的安全控制,提高他们的应用程序对安全漏洞的抵御能力。
* https://blog.csdn.net/frog4/article/details/81876462
* <dependency>
* <groupId>org.owasp.esapi</groupId>
* <artifactId>esapi</artifactId>
* <version>2.1.0.1</version>
* </dependency>
*/
public class ESAPITest {
static ValidationErrorList errors = new ValidationErrorList();
@Test
public void validPath() {
String path="//techwebsite/webfile/";
//String path="/path with space/invalid#chars/";
try {
boolean isValid =ESAPI.validator().isValidInput("Path validation", path, "SitePath", 100, false);
System.out.println(isValid);
} catch (Exception e) {
System.out.println("输入路径无效:" + e.getMessage());
}
}
//针对xss漏洞
@Test
public void encodeForHTML() {
//对用户输入“input”进行HTML编码,防止XSS
String input="<a href='sdfs'></a> < script > alert(); </ script >";
input = ESAPI.encoder().encodeForHTML(input);
System.out.println("编码:"+input);
//根据自己不同的需要可以选用以下方法
//input = ESAPI.encoder().encodeForHTMLAttribute(input);
//input = ESAPI.encoder().encodeForJavaScript(input);
//input = ESAPI.encoder().encodeForCSS(input);
//input = ESAPI.encoder().encodeForURL(input);
//针对富文本进行html编码
System.out.println("解码:"+ESAPI.encoder().decodeForHTML(input));
}
//针对sql注入漏洞
@Test
public void encodeForSQL(){
String input1="用户输入1";
String input2="用户输入2";
//解决注入问题
input1 = ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD),input1);
input2 = ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD),input2);
String sqlStr="select name from tableA where id="+input1 +"and date_created ='" + input2+"'";
System.out.println(sqlStr);
//执行SQL
}
/**
* 验证输入
* 检查每个输入的有效性,让每个输入合法。这里面的关键是一切都进行验证。ESAPI提供了很多常见的校验,可以方便针对不同的需要做校验。
*/
@Test
public void validInput() {
//type就是定义在validate.properties文件中的正则表达式
//ESAPI.validator().getValidInput(java.lang.String context,java.lang.String input, java.lang.String type,int maxLength,boolean allowNull);
//ESAPI.validator().isValidInput(java.lang.String context,java.lang.String input, java.lang.String type,int maxLength,boolean allowNull);
//实际使用参考如下:
String input="877507054@qq.com";
if(!ESAPI.validator().isValidInput("",input,"Email",100,false)){
System.out.println("出错了");
}
try{
input = ESAPI.validator().getValidInput("",input,"Email",11,false);
System.out.println(input);
}catch (Exception e){
System.out.println("输入错误");
e.printStackTrace();
}
}
@Test
public void validDirectoryPath() throws IOException {
String inputPath = "E:\\SHELL";
//inputPath = "E:\\人保科技官网";
File parent = new File(inputPath).getParentFile().getCanonicalFile();
//String parent = ESAPI.securityConfiguration().getResourceFile("ESAPI.properties").getParentFile().getCanonicalPath();
String path = ESAPI.validator().getValidDirectoryPath("用户输入路径", inputPath, parent, true, errors);
//path = ESAPI.validator().getValidDirectoryPath("用户输入路径", inputPath, new File("E:/"), false);
System.out.println(path);
}
//验证恶意文件
@Test
public void validFileName() {
//校验文件名
String inputfilename ="xxxx.txt";
List<String> allowedExtension = new ArrayList<String>();
allowedExtension.add("exe");
allowedExtension.add("jpg");
if(!ESAPI.validator().isValidFileName("upload",inputfilename, allowedExtension,false)){
//文件名不合法,throw exception
System.out.println("出错了");
}
//获取随机文件名
System.out.println(ESAPI.randomizer().getRandomFilename("exe"));
//得到结果rnQO8AK4ymmv.exe
}
}
4.点击劫持漏洞
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Filter过滤器实现对点击劫持漏洞
* 点击劫持漏洞是一种比较常见的安全漏洞,可以通过设置X-Frame-Options头信息来修复。X-Frame-Options是一个HTTP响应头,
* 允许网站控制浏览器是否允许将网页嵌入到<frame>、<iframe>或<object>标签中。通过设置X-Frame-Options头信息,可以避免网页被嵌入到其他网站中,从而防止点击劫持攻击。
* 以下是修复点击劫持漏洞的一些常见方法:
* 1.设置X-Frame-Options头信息:在HTTP响应中添加X-Frame-Options头信息,告知浏览器如何处理该页面的嵌入。常见的设置有:
* DENY:完全禁止页面被嵌入到<frame>、<iframe>或<object>中。
* SAMEORIGIN:只允许页面在相同域名下被嵌入。
* ALLOW-FROM uri:只允许页面在指定的URI中被嵌入。
* 在Java中,你可以通过在HTTP响应中设置X-Frame-Options头信息来修复该漏洞。例如,在Servlet中可以这样设置:
* response.setHeader("X-Frame-Options", "DENY");
* 2.升级到HTTPS:点击劫持攻击通常通过在受害者页面上覆盖一个透明的iframe来实现。使用HTTPS可以增加攻击者获取页面内容的难度。
* 综上所述,修复点击劫持漏洞的关键在于设置X-Frame-Options头信息,可以选择DENY或SAMEORIGIN根据你的具体需求来设置。同时,其他安全策略如CSP、Frame Busting Technique和使用HTTPS也可以增强应用程序的安全性
*
*/
public class IframeFilterInterceptor implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 必须
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 实际设置
response.setHeader("x-frame-options", "SAMEORIGIN");//只允许页面在相同域名下被嵌入。
// 调用下一个过滤器(这是过滤器工作原理,不用动)
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
5.检测到目标URL存在http host头攻击漏洞
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.springframework.core.io.support.PropertiesLoaderUtils.loadAllProperties;
/**
* 1、Filter过滤器实现对检测到目标URL存在http host头攻击漏洞
* 2、解决跨域问题
*/
public class HostCleanFilterInterceptor implements Filter {
private static final Logger logger = LoggerFactory.getLogger(WebContextInterceptor.class);
public static List<String> rightHost = new ArrayList<String>();
static{
try {
//循环添加IP白名单
Properties properties = loadAllProperties("config/IP-whitelist.properties");
Set<String> keyNames = properties.stringPropertyNames();
for (String key : keyNames) {
rightHost.add(properties.getProperty(key));
properties.getProperty(key);
}
} catch (IOException e) {
logger.error(e.getMessage());
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//解决跨域问题 begin
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");
//解决跨域问题 end
//解决存在http host头攻击漏洞 begin
String requestHost = request.getHeader("host");
if (requestHost != null && isRightHost(requestHost)){
response.setStatus(403);
return;
}
//解决存在http host头攻击漏洞 end
chain.doFilter(request,response);
}
public boolean isRightHost(String requestHost){
if (rightHost.indexOf(requestHost)<0){
//域名非法
return true;
}
return false;
}
public void destroy() {
}
}
6.XSS攻击-设置cookie的HttpOnly,防窃取cookie
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 使用拦截器设置cookie的HttpOnly.
* 是为了防止XSS攻击,窃取cookie的内容.
*/
public class HttpOnlyInterceptor implements HandlerInterceptor{
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object arg2, Exception Exception)
throws Exception {
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object arg2, ModelAndView arg3) throws Exception {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Cookie cookie = cookies[0];
if (cookie != null) {
// serlvet 2.5 不支持在 Cookie 上直接设置 HttpOnly 属性.
String value = cookie.getValue();
StringBuilder builder = new StringBuilder();
builder.append("JSESSIONID=" + value + "; ");
builder.append("Secure; ");
builder.append("HttpOnly; ");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, 1);
Date date = calendar.getTime();
Locale locale = Locale.CHINA;
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss",locale);
builder.append("Expires=" + sdf.format(date));
response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max- age=seconds; HttpOnly");
}
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object arg2) throws Exception {
return true;
}
}
7.过滤request中包含XSS代码
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Filter过滤器实现对Request的过滤
* @author MrsZhang
*
*/
public class XssFilterInterceptor implements Filter {
public void destroy() {
// TODO Auto-generated method stub
}
/**
* 过滤器用来过滤的方法
*/
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
//包装request
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* 建立HttpServletRequestWapper的包装类。
* 这个类的目的是对用户发送的请求进行包装,把request中包含XSS代码进行过滤
* @author MrsZhang
*
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] value = super.getParameterValues(name);
if(value != null){
for (int i = 0; i < value.length; i++) {
value[i] = xssEncode(value[i]);
}
}
return value;
}
@Override
public Map getParameterMap() {
// TODO Auto-generated method stub
return super.getParameterMap();
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取
* getHeaderNames 也可能需要覆盖
* 这一段代码在一开始没有注释掉导致出现406错误,原因是406错误是HTTP协议状态码的一种,
* 表示无法使用请求的内容特性来响应请求的网页。一般是指客户端浏览器不接受所请求页面的 MIME 类型。
* **/
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
/**
* 将容易引起xss漏洞的半角字符直接替换成全角字符 在保证不删除数据的情况下保存
* @param s
* @return 过滤后的值
*/
private static String xssEncode(String value) {
if (value == null || value.isEmpty()) {
return value;
}
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("(?i)<script.*?>.*?<script.*?>", "");
value = value.replaceAll("(?i)<script.*?>.*?</script.*?>", "");
value = value.replaceAll("(?i)<.*?javascript:.*?>.*?</.*?>", "");
value = value.replaceAll("(?i)<.*?\\s+on.*?>.*?</.*?>", "");
value = value.replaceAll(">", "");
value = value.replaceAll("<", "");
value = value.replaceAll("\'", "");
value = value.replaceAll("\"", "");
value = value.replaceAll("&", "");
value = value.replaceAll("\\", "");
value = value.replaceAll("#", "");
value = value.replaceAll("|", "");
value = value.replaceAll(";", "");
value = value.replaceAll("$", "");
value = value.replaceAll("%", "");
value = value.replaceAll("@", "");
value = value.replaceAll("<>'", "");
value = value.replaceAll("()", "");
value = value.replaceAll("+", "");
value = value.replaceAll(",", "");
return value;
}
//获取最原始的request
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
//获取最原始的request的静态方法
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if(req instanceof XssHttpServletRequestWrapper){
return ((XssHttpServletRequestWrapper)req).getOrgRequest();
}
return req;
}
}
WEB安全编程技术规范:https://wenku.baidu.com/view/c24d4e642af90242a895e588.html
参考:https://blog.csdn.net/frog4/article/details/81876462