我们进一步跟进HttpParser中的方法:
public static boolean isNotRequestTarget(int c) { // Fast for valid request target characters, slower for some incorrect // ones try { // 关键在于这个数组 return IS_NOT_REQUEST_TARGET[c]; } catch (ArrayIndexOutOfBoundsException ex) { return true; } } // Combination of multiple rules from RFC7230 and RFC 3986. Must be // ASCII, no controls plus a few additional characters excluded if (IS_CONTROL[i] || i > 127 || i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { // 可以看到只有在REQUEST_TARGET_ALLOW数组中的值才不会设置成true,所以我们需要追踪REQUEST_TARGET_ALLOW数组的赋值 if (!REQUEST_TARGET_ALLOW[i]) { IS_NOT_REQUEST_TARGET[i] = true; } } String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow"); if (prop != null) { for (int i = 0; i < prop.length(); i++) { char c = prop.charAt(i); // 可以看到在配置文件中配置了tomcat.util.http.parser.HttpParser.requestTargetAllow并且包含{、}、|的时候,REQUEST_TARGET_ALLOW数组中的值才会为true if (c == '{' || c == '}' || c == '|') { REQUEST_TARGET_ALLOW[c] = true; } else { log.warn(sm.getString("httpparser.invalidRequestTargetCharacter", Character.valueOf(c))); } } }解决办法: 其实通过源码分析不难得到解决办法
在Tomcat的catalina.properties文件中添加以下语句:
tomcat.util.http.parser.HttpParser.requestTargetAllow={}|
当然需要注意的是,这个后门在Tomcat8.5以后就无法使用的,Tomcat9之后的解决办法暂时未找到,可能只有对URL进行编码了。
问题二:Cookie设置报错这个问题就是在升级到Tomcat8.5以上的时候会出现的,具体原因是Tomcat8.5采用的Cookie处理类是:
Rfc6265CookieProcessor,而在之前使用的处理类是LegacyCookieProcessor。该处理类对domai进行了校验:
private void validateDomain(String domain) { int i = 0; int prev = -1; int cur = -1; char[] chars = domain.toCharArray(); while (i < chars.length) { prev = cur; cur = chars[i]; if (!domainValid.get(cur)) { throw new IllegalArgumentException(sm.getString( "rfc6265CookieProcessor.invalidDomain", domain)); } // labels must start with a letter or number if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) { throw new IllegalArgumentException(sm.getString( "rfc6265CookieProcessor.invalidDomain", domain)); } // labels must end with a letter or number if (prev == '-' && cur == '.') { throw new IllegalArgumentException(sm.getString( "rfc6265CookieProcessor.invalidDomain", domain)); } i++; } // domain must end with a label if (cur == '.' || cur == '-') { throw new IllegalArgumentException(sm.getString( "rfc6265CookieProcessor.invalidDomain", domain)); } }新的Cookie规范对domain有以下要求
1、必须是1-9、a-z、A-Z、. 、- (注意是-不是_)这几个字符组成
2、必须是数字或字母开头 (所以以前的cookie的设置为.XX.com 的机制要改为 XX.com 即可)
3、必须是数字或字母结尾
原来的代码设置domain时如下:
cookie.setDomain(".aaa.com");这就导致设置domain的时候不符合新的规范,直接报错如下:
java.lang.IllegalArgumentException: An invalid domain [.aaa.com] was specified for this cookie at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateDomain(Rfc6265CookieProcessor.java:181) at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:123) at org.apache.catalina.connector.Response.generateCookieString(Response.java:989) at org.apache.catalina.connector.Response.addCookie(Response.java:937) at org.apache.catalina.connector.ResponseFacade.addCookie(ResponseFacade.java:386)解决办法(以下3中任意一种皆可)
修改原来代码为:
cookie.setDomain("aaa.com");
如果是Spring-boot环境,直接替换默认的Cookie处理类:
@Configuration @ConditionalOnExpression("${tomcat.useLegacyCookieProcessor:false}") public class LegacyCookieProcessorConfiguration { @Bean EmbeddedServletContainerCustomizer embeddedServletContainerCustomizerLegacyCookieProcessor() { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer factory) { if (factory instanceof TomcatEmbeddedServletContainerFactory) { TomcatEmbeddedServletContainerFactory tomcatFactory = (TomcatEmbeddedServletContainerFactory) factory; tomcatFactory.addContextCustomizers(new TomcatContextCustomizer() { @Override public void customize(Context context) { context.setCookieProcessor(new LegacyCookieProcessor()); } }); } } }; } }
在Tomcat的context.xml中增加如下配置,指定Cookie的处理类:
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />更多Tomcat相关教程见以下内容: