May 11, 2020

GHSL-2020-020: EL expression input sanitation bypass in Hibernate Validator - CVE-2020-10693

Alvaro Muñoz

Summary

A bug in Hibernate Validator's interpolation of constraint error messages enables invalid EL expressions to be evaluated as if they were valid.

This bug enables attackers to bypass input sanitation (escaping, stripping) controls that developers may have put in place when handling user-controlled data in error messages.

Product

Hibernate Validator

Tested Version

6.1.2.Final

Details

Incorrect EL expression tokenization (GHSL-2020-020, CVE-2020-10693)

The ConstraintValidatorContext documentation specifies that developers should be very careful when integrating user input into a custom message template as it will be interpreted by the Expression Language engine, which may allow attackers to run arbitrary Java code.

Several applications attempt to prevent such EL injections by replacing the EL opening delimiter ${ with just { e.g.:

    public String replaceElDelimiter(final String value) {
        if (value != null) {
            return value.replaceAll("\$+\\{", "{");
        }
        return null;
    }

This is seemingly a secure way to prevent injection attacks since all occurrences of ${ will be replaced with {, and since the regex matches repeating $ it will also fix more intricate injection attempts that send e.g. $${ in an attempt to arrive at the ${ delimiter to achieve EL execution.

However, a bug in the Hibernate message interpolation parser (org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector), allows attackers to bypass this protection with a payload such as FOO $\A{payload}. The reason is the following:

  • When the parser encounters $ we move to handleELDesignator and due to tokenCollector.getInterpolationType().equals(InterpolationTermType.EL) we skip the character and move to ELState state.
  • When the parser encounters \ in the ELState, we skip the character and move to EscapedState
  • When the parser encounters A in the EscapedState, we append it to the current token (FOO A) and move back to ELState
  • When the parser encounters { in the ELState, we terminate the current token, which will remain as FOO A, and then we create a new empty token and append ${ to it, so it is basically replacing { with ${ and then it marks the new token as an EL token and moves to the InterpolationTermState

Impact

This issue may lead to mitigation bypasses that allow for remote code execution in affected applications.

CVE

  • CVE-2020-10693

Coordinated Disclosure Timeline

  • 02/05/2020: Report sent to Vendor
  • 02/05/2020: Assigned INC1163499 internal ID
  • 03/06/2020: RedHat asks for reproducer PoC
  • 03/10/2020: Sent PoC to RedHat
  • 03/13/2020: RedHat asks for EL payload to run arbitrary commands
  • 03/13/2020: Sent arbitrary command execution payload to RedHat
  • 04/14/2020: Issue is assigned CVE-2020-10693 and moved to EMBARGOED status
  • 05/05/2020: Embargo is lifted
  • 05/11/2020: Public Advisory

Supporting Resources

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include the GHSL-2020-020 in any communication regarding this issue.