【问题标题】:UTF-8 | issue with special character on form submit | java.io.CharConversionExceptionUTF-8 |提交表单时出现特殊字符问题 | java.io.CharConversionException
【发布时间】:2012-12-11 06:16:36
【问题描述】:

我有一个表单,其中有文本框和提交按钮。我正在使用 burp 工具更改表单中提交的值以测试我的服务器端验证。

服务器端验证工作正常,除非从 burp 工具输入了 % 字符,否则在输入 % 字符时服务器会显示以下异常。

2012-12-11 11:37:07,860 WARN  [org.apache.tomcat.util.http.Parameters] (ajp-0.0.0.0-8109-19) Parameters: Character decoding failed. Parameter skipped.
java.io.CharConversionException: EOF
                at org.apache.tomcat.util.buf.UDecoder.convert(UDecoder.java:83)
                at org.apache.tomcat.util.buf.UDecoder.convert(UDecoder.java:49)
                at org.apache.tomcat.util.http.Parameters.urlDecode(Parameters.java:429)
                at org.apache.tomcat.util.http.Parameters.processParameters(Parameters.java:412)
                at org.apache.tomcat.util.http.Parameters.processParameters(Parameters.java:363)
                at org.apache.catalina.connector.Request.parseParameters(Request.java:2562)
                at org.apache.catalina.connector.Request.getParameter(Request.java:1060)
                at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:355)
                at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:118)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
                at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190)
                at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92)
                at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
                at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
                at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
                at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:436)
                at org.apache.coyote.ajp.AjpProtocol$AjpConnectionHandler.process(AjpProtocol.java:384)
                at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
                at java.lang.Thread.run(Thread.java:662)

当我提交带有 % 字符的表单时,它会正确地将其更改为 %25,但是我该如何处理这个服务器端呢?

【问题讨论】:

  • 使用winshark获取客户端提交的读取数据。

标签: java jakarta-ee special-characters server-side web.xml


【解决方案1】:

您遇到的问题是,当解码器以 % 符号开头时,它需要一个有效的、可解码的参数。提交表单时,输入参数%被正确编码为%25,当你用Burp编辑这个请求时,据我所知,这种编码不会发生。 Burp 愿意向您的服务器发送% 符号,并且您的服务器端验证假定这是编码值的开始,但任何解码都失败,因为它基本上是一个损坏的参数。

我最好的猜测是不要单独发送 %-values(即不附带数字值)。就像在空引用上调用方法一样。有些事情是行不通的。我想说您尝试检查百分比符号是否被您自己的输入验证批准或拒绝,但只要它首先通过解码,就必须对其进行编码。

【讨论】:

  • 我能理解你在说什么,但我想要一种方法,通过它我可以捕获这个异常并向用户显示一个自定义错误页面。可以看出,这个异常是在某个tomcat库中产生的,我做不到。
  • 遗憾的是,我对此一无所知..也许某种拦截器能够在 url 解码开始之前获取请求?不过我对此表示怀疑..
【解决方案2】:

考虑到要求您对输入进行编码是安全的,其他答案是正确的,因为所有浏览器也会这样做,并且理想情况下您永远不会收到未编码的纯 % 字符。

但是,话虽如此,如果您仍然坚持看到错误页面;可以编写自定义过滤器来捕获此CharConversionException 并向客户端发送错误页面或将请求转发到相同的URI,但设置errorMessage 属性以便以不同方式处理请求。

这是通过名为 useErrorPage 的过滤器参数在此处配置的。

编辑:鉴于@Santosh 共享的源代码显示CharConversionException 实际上被Tomcat 抑制,我修改了过滤器以注入参数验证并检查所有请求参数可以通过名为forbiddenChars 的过滤器参数配置的禁用字符列表。

将此过滤器放在 web.xml 中 DisplayTag 的 ResponseOverrideFilter 上方,以确保它拦截所有内容。当涉及到过滤器时,排序可能会产生副作用。

我想借此机会强调 Servlet 过滤器 是一个非常强大的概念,它可以让您以任何您喜欢的方式对任何请求进行预处理或后处理。大多数 Web 框架(如 MVC 的 Struts 2)都使用过滤器作为它们的入口点。

因此,您可以使用过滤器做的事情很少。只需确保将它们用于正确的目的,例如业务逻辑显然不应该去那里,尽管应用程序范围的身份验证可以。

IOException 过滤器

public class CharConversionExpFilter implements Filter {

    private char[] forbiddenChars;  // ADDED
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        forbiddenChars = filterConfig.getInitParameter("forbiddenChars")
                               .replace(",", "").toCharArray(); // ADDED
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        String requestURI = ((HttpServletRequest) request).getRequestURI();
        try {
            validateParameters((HttpServletRequest) request);  // ADDED
            chain.doFilter(request, response);
        } catch (IOException e) {
            if (e instanceof CharConversionException) {
                if ("true".equalsIgnoreCase(filterConfig.getInitParameter("useErrorPage"))) {
                    if (response instanceof HttpServletResponse) {
                        ((HttpServletResponse) response).sendError(400,
                        "The request cannot be fulfilled due to bad input.\nError:" + e.getMessage());
                    }
                } else {
                    request.setAttribute("errorMessage", e.getMessage());
                    filterConfig.getServletContext().getRequestDispatcher(requestURI).forward(request, response);
                }
            }
        }
    }

    // ADDED
    private void validateParameters(HttpServletRequest request) throws CharConversionException {
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String parameter = request.getParameter(parameterNames.nextElement());
            if (parameter != null && parameter.length() > 0) {
                for (char forbidChar : forbiddenChars) {
                    if (parameter.indexOf(forbidChar) != -1) {
                        throw new CharConversionException(
                                String.format(
                                        "Parameter: [%s] contains the forbidden character [%c]",
                                        parameter, forbidChar));
                    }
                }
            }
        }
    }

    @Override
    public void destroy() {}
}


web.xml

<filter>
  <filter-name>CharConversionExpFilter</filter-name>
  <filter-class>servlet.filter.CharConversionExpFilter</filter-class>
  <init-param>
    <param-name>useErrorPage</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param> <!-- ADDED -->
    <param-name>forbiddenChars</param-name>
    <param-value>%,*,?,#,$</param-value> <!-- filtering wildcards and EL chars -->
  </init-param>
</filter>

<filter-mapping>
  <filter-name>CharConversionExpFilter</filter-name>
  <url-pattern>/*</url-pattern> <!-- * = ALL; "/servlet-name" if required -->
</filter-mapping>

<error-page>
  <error-code>400</error-code>
  <location>/errorPage.jsp</location> <!-- isErrorPage = true; use "exception" obj -->
</error-page>


希望这可以为您提供足够的指导,以根据您的需要制定解决方案。 (注意:语法突出显示错误地将&lt;url-pattern&gt;/* 解释为多行Java 注释的开头。请忽略它。)

【讨论】:

  • 你试过了吗?我尝试了同样的方法,但过滤器 do.filter() 永远不会捕获异常并且参数(其值作为 % 传递)为空。它是一个被抛出和吞噬的异常,从不传播。
  • @Santosh 好找伙伴!我假设共享堆栈跟踪是由于抛出异常而出现的。我已经修改了过滤器以现在注入验证并将这个问题扼杀在萌芽状态。
  • 很好的解决方法,但 IMO 从未打算处理该异常,更重要的是指出了发送请求的工具中 http 协议违规 的问题。潜在地,可能存在更多协议违规问题(工具中的错误),并且很难处理每个问题。
  • @Santosh 是的,我在回答的第一行就谈到了这一点。
  • @Santosh,发送请求的工具是一个测试工具,它最好发送违反协议的请求,因为黑客(以及被破坏的客户端)肯定会这样做。
【解决方案3】:

通过此测试(在应该是 URL 编码的数据中包含原始“%”),您正在测试您的应用是否适当地拒绝格式错误的输入。没有错。

如果你想使用 burp 推送数据,你需要对其进行编码,这意味着你需要在你想要一个 '%' 的地方放入 '%25'。

【讨论】:

    【解决方案4】:

    如果您仔细检查堆栈跟踪,所有涉及的类(将 HTTP 文本解析为 Java 请求对象)都来自框架,并且用户无法控制它

    现在,这背后的基本前提是:服务器将始终假定它接收到的数据始终是 URL 编码,因此它将始终尝试解码。

    如果解码失败,则意味着未遵守 HTTP 协议的基本原则,因此直接拒绝请求,而没有给用户处理的机会。

    所以恕我直言,您无法处理这种情况,因为服务器正在正确地自行处理它。

    编辑:

    上面@Ravi 提出的一个解决方案看起来解决了这个问题。我尝试了相同的代码,但我永远无法捕捉到异常!

    深入挖掘,很明显为什么无法在任何用户定义的代码中处理该异常 (java.io.CharConversionException)。

    虽然java.io.CharConversionException被抛出,但它从未在调用层次结构中传播。这就是为什么,

    在堆栈跟踪中

            at org.apache.tomcat.util.buf.UDecoder.convert(UDecoder.java:83)
            at org.apache.tomcat.util.buf.UDecoder.convert(UDecoder.java:49)
            at org.apache.tomcat.util.http.Parameters.urlDecode(Parameters.java:429)
            at org.apache.tomcat.util.http.Parameters.processParameters(Parameters.java:412)
    

    查看org.apache.tomcat.util.http.Parameters类中方法processParameters()来源(代码取自grepcode

    public void processParameters( byte bytes[], int start, int len, 
                                       String enc ) {
            int end=start+len;
            int pos=start;
    
            if( debug>0 ) 
                log( "Bytes: " + new String( bytes, start, len ));
    
            do {
                boolean noEq=false;
                int valStart=-1;
                int valEnd=-1;
    
                int nameStart=pos;
                int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' );
                // Workaround for a&b&c encoding
                int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' );
                if( (nameEnd2!=-1 ) &&
                    ( nameEnd==-1 || nameEnd > nameEnd2) ) {
                    nameEnd=nameEnd2;
                    noEq=true;
                    valStart=nameEnd;
                    valEnd=nameEnd;
                    if( debug>0) log("no equal " + nameStart + " " + nameEnd + " " + new String(bytes, nameStart, nameEnd-nameStart) );
                }
                if( nameEnd== -1 ) 
                    nameEnd=end;
    
                if( ! noEq ) {
                    valStart= (nameEnd < end) ? nameEnd+1 : end;
                    valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
                    if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
                }
    
                pos=valEnd+1;
    
                if( nameEnd<=nameStart ) {
                    log.warn("Parameters: Invalid chunk ignored.");
                    continue;
                    // invalid chunk - it's better to ignore
                }
                tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
                tmpValue.setBytes( bytes, valStart, valEnd-valStart );
    
                try {
                    addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
                } catch (IOException e) {
                    // Exception during character decoding: skip parameter
                    log.warn("Parameters: Character decoding failed. " + 
                             "Parameter skipped.", e);
                }
    
                tmpName.recycle();
                tmpValue.recycle();
    
            } while( pos<end );
        }
    

    该方法中有一个do-while循环,用于解析所有请求参数。查看方法中循环末尾的 try-catch 块。有一条评论说

    // Exception during character decoding: skip parameter
    

    所以即使抛出异常但它没有传播。其记录为警告消息,参数值设置为空。 所以很明显你将永远无法捕捉到该异常 (java.io.CharConversionException)

    【讨论】:

    • 如 Ravi 所示,用户可以通过 Servlet 过滤器对此进行控制。
    • 那行不通。请参阅我的编辑。我希望你能重新考虑你的否决票。
    • @OldPro,如果您投了反对票,请重新考虑。我的回答已经给出了足够的证据。
    • 我也面临同样的问题,但目前还没有解决方案?
    猜你喜欢
    • 1970-01-01
    • 2011-10-27
    • 1970-01-01
    • 2015-01-15
    • 1970-01-01
    • 2014-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多