CRLF概述CRLF意味着回车和换行符。 这些元素嵌入在HTTP头和其他软件代码中,表示行尾(EOL )标记。 如果攻击者能够将CRLF序列注入HTTP流,则会出现漏洞。 通过引入这种意外的CRLF注入,攻击者可以利用CRLF漏洞操纵web APP功能。
CRLF攻击示例攻击者通过界面访问网站时,可以将rn添加到参数中,并输入可能会引起误解的信息,如“connect failed”。 如果攻击成功,日志将出现换行符,新行中将出现“connect failed”,这可能需要大量精力来解决管理员不存在的错误
建议CRLF注入修复便于CRLF注射预防:
始终遵循不信任用户输入的规则。 清理所有用户提供的数据,并正确编码HTTP标头的输出。 否则,这些输出将显示给用户,以避免注入CRLF序列及其结果。 CRLF防御的一个示例是post请求localhost :8080/app pay/new order。 请求参数如下。
{
“订单:”89077“,
' skuId':'1',
' order ' : '1ista injectionrnordercreatedsuccess!'
}
考虑一下以下事项。
1 .用对应的业务代码提前检查参数,去掉特殊字符串
2 .添加过滤器
首先,方案1的情况下,头痛、脚痛、被攻击的业务很多的情况下,要修好相当麻烦
方案2要求实现继承HttpServletRequestWrapper的类以获取从HttpServletRequest传递的参数值,修改该参数值,然后通过筛选器传递。
定义过滤器参数过滤器
package com.webStudy.notes; import javax.servlet.*; import javax.servlet.annotation.web filter; import javax.servlet.http.http servlet request; import java.io.IOException; @ webfilterpublicclassparameterfilterimplementsfilter { @ overridepublicvoiddofilter (servletrequestservletrequest,servlet ) filterchainfilterchain (throwsioexception,servlet exception { myrequestwrapperrequest=new } filter chain.do filter (requet ) }实现自己的MyRequestWrapper (常用手段)
package com.webStudy.notes; import javax.servlet.read listener; import javax.servlet.servlet inputstream; import javax.servlet.http.http servlet request; import javax.servlet.http.httpservletrequestwrapper; import java.io.*; import Java.nio.charset.standard charsets; import java.util.regex.Matcher; import java.util.regex.Pattern; publicclassmyrequestwrapperextendshttpservletrequestwrapper { privatestaticstringregex=' [\ r,n] '; privatestaticpatternpattern=pattern.com pile (regex; 私有文件字符串体; publicmyrequestwrapper (httpservletrequestrequest ) throwsioexception { super } request; stringbuilder sb=new stringbuilder (; inputstream ins=request.get inputstream (; 缓冲读取ISR=null; try { if (ins!=null(ISR=newbufferedreader ) newinputstreamreader(ins ); char[] charBuf
fer = new char[128]; int readCount = 0; while((readCount = isr.read(charBuffer)) != -1){ sb.append(charBuffer,0,readCount); } }else{ sb.append(""); } }catch (IOException e){ throw e; }finally { if(isr != null) { isr.close(); } } body = changeBodyStr(sb.toString()); } //某些情况下,可能出现rn被转义,也就是参数中表示为\r,\n,这种情况需要直接替换字符串"\r","\n",可以自行实现 private String changeBodyStr(String bodyString){ Matcher matcher = pattern.matcher(bodyString); return matcher.replaceAll("").trim(); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } //从流中读取一次Body信息后,流就被关闭了,所以需要重写一个方法来返回新的ServletInputStream @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayIns.read(); } }; }}除了以上两种,还可以考虑通过AOP,拦截所有controller,再修改body,也许通过@initBinder也可以做一些操作(未验证)
还有个思路是直接不让日志中打印"rn",比如logback中的Layout组件,其作用为:
对LoggingEvent进行格式化,返回一个String,然后通过OutputStream.write()方法,把格式化之后的日志信息写到目的地。
logback配置,参考方案2 的思路,我们也可以通过extends PatternLayout的方式写个自己的PatternLayout,来对日志信息做处理,然后输出到目的地。
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="自定义layout类" /></encoder>以上是我个人对CRLF日志注入的一个总结,希望能对大家有所帮助。
参考文章:
https://www.jianshu.com/p/47de9e15b2b7
https://blog.csdn.net/zxygww/article/details/47045055
https://www.jianshu.com/p/a0eb78b8c775?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation