skip to content
Back to
Home Bounties Research Advisories CodeQL Wall of Fame Get Involved Events
December 3, 2020

GHSL-2020-204: Server-Side Template Injection in Corona Warn App Server

Alvaro Munoz


A Server-Side Template Injection was identified in Corona Warn App Server enabling attackers to inject arbitrary Java EL expressions, leading to un-auth Remote Code Execution (RCE) vulnerability.


Corona Warn App Server

Tested Version

latest commit to the date of testing: 3fd6baf


Remote Code Execution - JavaEL Injection

It is possible to run arbitrary code on the server (with Submission service account privileges) by injecting arbitrary Java Expression Language (EL) expressions.

Submission server uses Java Bean Validation (JSR 380) custom constraint validators such as ValidSubmissionPayload: When building custom constraint violation error messages, it is important to understand that they support different types of interpolation, including Java EL expressions. Therefore if an attacker can inject arbitrary data in the error message template being passed to ConstraintValidatorContext.buildConstraintViolationWithTemplate() argument, they will be able to run arbitrary Java code. Unfortunately, it is common that validated (and therefore, normally untrusted) bean properties flow into the custom error message. In this case there are, at least, two paths where attacker controlled strings are included in custom constraint error validation messages:

    private boolean checkVisitedCountriesAreValid(SubmissionPayload submissionPayload,
        ConstraintValidatorContext validatorContext) {
      if (submissionPayload.getVisitedCountriesList().isEmpty()) {
        return true;
      Collection<String> invalidVisitedCountries = submissionPayload.getVisitedCountriesList().stream()

      if (!invalidVisitedCountries.isEmpty()) {
        invalidVisitedCountries.forEach(country -> addViolation(validatorContext,
            "[" + country + "]: Visited country is not part of the supported countries list"));
      return invalidVisitedCountries.isEmpty();


    private boolean checkOriginCountryIsValid(SubmissionPayload submissionPayload,
        ConstraintValidatorContext validatorContext) {
      String originCountry = submissionPayload.getOrigin();
      if (submissionPayload.hasOrigin() && !originCountry.isEmpty()
          && !supportedCountries.contains(originCountry)) {
        addViolation(validatorContext, String.format(
            "Origin country %s is not part of the supported countries list", originCountry));
        return false;
      return true;

The ValidSubmissionPayload annotation is used to validate data received by the Submission service controller exposed in /version/v1/diagnosis-keys:

   * Handles diagnosis key submission requests.
   * @param exposureKeys The unmarshalled protocol buffers submission payload.
   * @param tan          A tan for diagnosis verification.
   * @return An empty response body.
  @PostMapping(value = SUBMISSION_ROUTE, headers = {"cwa-fake=0"})
  @Timed(description = "Time spent handling submission.")
  public DeferredResult<ResponseEntity<Void>> submitDiagnosisKey(
      @ValidSubmissionPayload @RequestBody SubmissionPayload exposureKeys,
      @RequestHeader("cwa-authorization") String tan) {
    return buildRealDeferredResult(exposureKeys, tan);

Spring security policies are defined to protect the endpoints but, in this case, all requests to the Submission endpoint are allowed with no authentication/authorization:

  protected void configure(HttpSecurity http) throws Exception {
        .mvcMatchers(HttpMethod.POST, SUBMISSION_ROUTE).permitAll()
    http.headers().contentSecurityPolicy("default-src 'self'");


This issue leads to Remote Code execution

Coordinated Disclosure Timeline


SAP ID SR-20-00362


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


You can contact the GHSL team at, please include a reference to GHSL-2020-204 in any communication regarding this issue.