background

Share a recent pothole with this background:

In order to mitigate the security risks of open source components, third-party open source components are regularly reviewed and upgraded in the project. Spring Cloud is used in our project, and the version is Hoxton.sr1. There are known vulnerabilities, so it is officially recommended to upgrade to the latest hoxton.sr7 (the actual corresponding version has been upgraded from 2.2.1.RELEASE to 2.2.3.RELEASE). Since it is only a Semantic Versioning revision, The compatibility of Spring itself is very good, and the UT runs out to the test environment.

A strange phenomenon was found in the test, an external calling service always reported feign call exception, local debugging interrupt point to get the variable value, manual to simulate the request is normal, puzzled, because of the modification of many places, one by one after eliminating the location of the Spring Cloud OpenFeign version itself: Simply roll back the version and the exception goes away.

Is this a Bug in Spring Cloud?

By comparing the difference of request packets through the packet capture tool, it was found that Spring Cloud OpenFeign had modified the content of our request: ‘,’ in the header parameter was changed to ‘,’. Below is the POC for this vulnerability, which I submitted to The Spring Cloud OpenFeign official.

Describe the bug

  • spring-cloud-openfeign >= 2.2.3. RELEASE (Hoxton. SR5)
  • springframework 5.2.12. RELEASE
  • springboot 2.3.7. RELEASE
  • io.github.openfeign 10.10.1
  • jdk 1.8.0 comes with _212

Describe: Spring-cloud-openfeign version >= 2.2.3.RELEASE @requesTheader will repace ‘,’ to ‘,’ (One more white space), But it’s OK in 2.2.2.RELEASE.

Sample

"Wed, 23 Dec 2020 02:34:12 GMT"
Copy the code

was replaced by

"Wed,  23 Dec 2020 02:34:12 GMT"
Copy the code

One more white space!

This causes some APIs authentication to fail!

My POC:

package com.devyy.poc.openfeignmock.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/poc")
public class PocController {

    @PostMapping
    public String poc(@RequestHeader("requestDate") String requestDate) {
        log.info("PocController requestDate={}", requestDate);
        returnrequestDate; }}Copy the code
package com.devyy.poc.openfeignmock.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@feignClient (name = "FeignClient ", url = "http://127.0.0.1:8888/")
public interface IFeignClient {
    @RequestMapping(value = "/poc", method = RequestMethod.POST)
    String testWhiteSpace(@RequestHeader("requestDate") String requestDate);
}
Copy the code
package com.devyy.poc.openfeignmock;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients("com.devyy.poc.openfeignmock.feign")
@SpringBootApplication
public class OpenfeignMockApplication {
    public static void main(String[] args) { SpringApplication.run(OpenfeignMockApplication.class, args); }}Copy the code

pom.xml:


      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>com.devyy.poc</groupId>
    <artifactId>openfeign-mock</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>openfeign-mock</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

application.properties:

server.port=8888
Copy the code

My unit test:

package com.devyy.poc.openfeignmock;

import com.devyy.poc.openfeignmock.feign.IFeignClient;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
class OpenfeignMockApplicationTests {
    @Autowired
    private IFeignClient feignClient;

    @Test
    void contextLoads(a) {
        String req = "Wed, 23 Dec 2020 02:34:12 GMT";
        String res = feignClient.testWhiteSpace(req);
        log.info("testWhiteSpace req={}", req);
        log.info("testWhiteSpace res={}", res); }}Copy the code

My logs:

OpenfeignMockApplicationTests logs:

The 2020-12-23 11:53:16. 32812-294 the INFO [the main] C.D.P.O.O penfeignMockApplicationTests: testWhiteSpace req=Wed, 23 Dec 2020 02:34:12 GMT 11:53:16 2020-12-23. 32812-297 the INFO [main] C.D.P.O.O penfeignMockApplicationTests: testWhiteSpace res=Wed, 23 Dec 2020 02:34:12 GMTCopy the code

PocController logs:

The 2020-12-23 11:53:16. 39676-134 the INFO [nio - 8888 - exec - 1] C.D.P.O.C ontroller. PocController: PocController requestDate=Wed, 23 Dec 2020 02:34:12 GMTCopy the code

Then because it is Christmas, foreigners have a long holiday. After receiving no official reply from Spring Cloud, I started to run the framework and debug it. Finally, I found a line of code with the OpenFeign framework as the problem:

Github.com/OpenFeign/f…

  @Override
  public String expand(Map
       
         variables)
       ,> {
    String result = super.expand(variables);

    /* remove any trailing commas */
    while (result.endsWith(",")) {
      result = result.replaceAll("$"."");
    }

    /* space all the commas now */
    result = result.replaceAll(",".",");
    return result;
  }
Copy the code

The problem was quite obvious. I repaired the problem through patch, but I did not understand the author’s intention after all. So I submitted the issue and PR under the OpenFeign project and tried to contact the author of the code Kevin Davis for confirmation. Kevin Davis is a core Contributor for the OpenFeign program. Through a bit of depth (shi) into (wen) stream (UI), the author acknowledges that this is a bug, a new bug introduced to fix another bug:

I’m beginning to see that we’ll have to “bite the bullet” and upgrade the Template handling sooner than Feign 11. The root cause of all of these issues is that our Template Handling relies on String manipulation for templates that can support Collections of values. Single values work fine.

Since HTTP Headers are generally considered Collection based, this problem will continue to appear. I can see that in #1298 an attempt is make to clear out extra spaces, but that solution will work for the example provided only and not generally across all headers. I’m going to link this to the template update issue in #1019 and see if we can start by separating general URI templates with Headers first.

Finally, the author considered from the whole, did not adopt my patch pr (a small regret), but rewrote the relevant implementation, separated the common URI template and header, optimized the architecture while solving the problem, and completed the corresponding UT use case.

Refer to the link

  1. Github.com/spring-clou…
  2. Github.com/OpenFeign/f…
  3. Github.com/OpenFeign/f…
  4. Github.com/OpenFeign/f…
  5. Github.com/OpenFeign/f…