aws / serverless-java-container

A Java wrapper to run Spring, Spring Boot, Jersey, and other apps inside AWS Lambda.
https://aws.amazon.com/serverless/
Apache License 2.0
1.49k stars 551 forks source link

ArrayIndexOutOfBoundsException when header value contains a equal sign #263

Closed ryber closed 5 years ago

ryber commented 5 years ago

Scenario

ArrayIndexOutOfBoundsException when a header value contains a "=" as part of it's value

Expected behavior

Would Parse the value to include the "="

Actual behavior

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1

Steps to reproduce

    @Test
    public void extraEqualsInHeader() {
      AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null);

        String value = Base64.getUrlEncoder().encodeToString("a".getBytes());

        List<AwsHttpServletRequest.HeaderValue> result = context.parseHeaderValue(value);

        assertEquals("YQ==", result.get(0).getValue());
    }

Full log output

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1

    at com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.parseHeaderValue(AwsHttpServletRequest.java:373)
    at com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.parseHeaderValue(AwsHttpServletRequest.java:338)
    at com.amazonaws.serverless.proxy.internal.servlet.LambdaTest.extraEqualsInHeader(LambdaTest.java:20)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
sapessi commented 5 years ago

Thanks for reporting this @ryber - we'll address it in the next minor release.

sapessi commented 5 years ago

I just tested with the Apache header value parser and it also struggles with the base64 encoded value. What I get out of it is:

[{"name":"YQ","value":"=","parameters":[],"parameterCount":0}]

I can fix the parser not to throw the exception. However, I assume you want to get the full base64 encoded value out. Are you using the String getHeader(String) method of the request?

ryber commented 5 years ago

We have a filter that deals with JWT's stored in secured/http-only cookies. It works fine in Tomcat, Jetty, and Spark (jetty). In this case we were using it with Java spark in a lambda. That's when we hit this exception. I created the issue boiled down without all the extra. So round-about it comes from getting cookie values. But I suspect it would have a problem even if it was just in an "regular" header

sapessi commented 5 years ago

Should be ok with regular headers because the getHeader method returns the string value without trying to parse it. I've added a condition to check whether the entire value is base64-encoded in the fix. I will add a test for a cookie value to be base64-encoded. Can you confirm that the header looks something like this:

Cookie: jwt=YQ==; secondCookie=customvalue
ryber commented 5 years ago

Yes, looks exactly like that

ryber commented 5 years ago

I'd keep in mind that the = is the buffer and I believe could be just one, maybe 3? In any case not always just two of them

sapessi commented 5 years ago

Yup, thanks. Working on a fix now. It's hard to distinguish whether it's a key name with a null value or a base64 encoded value when the buffer has a single =

sapessi commented 5 years ago

Just pushed some more changes. I've added a unit test for your use-case and it seems to work. you can test with the 1.4-SNAPSHOT version from the core branch of this repo

ryber commented 5 years ago

Thanks, is it in a repo somewhere or do I need to build it myself?

sapessi commented 5 years ago

You'll have to clone the core branch and mvn install on your local system, then if you add the 1.4-SNAPSHOT as a dependency to your project maven should pick it up by itself from the local repo

ryber commented 5 years ago

Hey it works @sapessi! Thanks for the quick turnaround.

Once note, I had to comment out the owasp filter locally to get maven to do a local install because it couldn't resolve itself (I think), but after that all was well.

sapessi commented 5 years ago

awesome. I'll try to push 1.3.2 out this weekend!

sapessi commented 5 years ago

Release 1.3.2 - which includes this fix - is on its way to maven central! Resolving this issue.