Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RESTWS-742: Filter based on content-type #369

Merged
merged 1 commit into from
Dec 8, 2018

Conversation

isears
Copy link
Member

@isears isears commented Dec 1, 2018

No description provided.

@isears
Copy link
Member Author

isears commented Dec 1, 2018

Discussion on security mailing list: https://talk.openmrs.org/c/software/security

import org.apache.commons.logging.LogFactory;

/**
* Filter intended for all /ws/rest calls that prevents non-json Content-Types due to security
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of saying non-json content types, shouldn't we say xml content types? Because that is what we are disabling.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I had initially started with the idea of blocking everything but json, but eventually settled on just blocking xml. I'll change this.

@dkayiwa
Copy link
Member

dkayiwa commented Dec 2, 2018

@isears i have tried to run these changes in the reference application. When i search and then select a patient, i get an error and this stack trace: https://pastebin.com/t5CFWW6b

@isears
Copy link
Member Author

isears commented Dec 2, 2018

This is concerning. It's possible some parts of the refapp are using the api's XML functionality. I'll look into it.

@isears
Copy link
Member Author

isears commented Dec 2, 2018

Ok, I think I found the issue. It looks like the filter was dropping requests that had a null content-type (i.e. GET requests). I've added a fix and a test case. Thanks for checking that @dkayiwa

@dkayiwa
Copy link
Member

dkayiwa commented Dec 2, 2018

@isears did you try reproducing the reported security problem before and after these changes?

@isears
Copy link
Member Author

isears commented Dec 2, 2018

@dkayiwa yes: I'm verifying that XML payloads don't get deserialized by POST-ing <map></map> to /openmrs/ws/rest/v1/obs and checking the logs for the following error:

org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.util.HashMap' to required type 'org.openmrs.module.webservices.rest.SimpleObject'.

The presence of a TypeMismatchException indicates that the system is attempting to deserialize XML payloads.

@@ -70,6 +70,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
HttpServletResponse httpresponse = (HttpServletResponse) response;
httpresponse.sendError(HttpServletResponse.SC_FORBIDDEN, "IP address '" + request.getRemoteAddr()
+ "' is not authorized");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you intentionally make changes to this class?

Copy link
Member Author

@isears isears Dec 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I added the return statement because I found that deserialization was occurring even after an IP failed the "allowed IPs" check. I figured it makes sense to stop processing a request's payload after it fails the "allowed IPs" authorization check, but this isn't strictly necessary to patch the vulnerability. If you think it's going to cause instability somewhere else in the system I can take it out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not deeply evaluated the effect of this. Just saw this comment on line 104 "continue with the filter chain in all circumstances". Does it matter if you just did chain.doFilter(request, response); before returning?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, unfortunately: a lot of processing happens in the chain.doFilter(request, response) call tree, to include deserialization. The purpose of the return statement is to prevent all that and just go straight to sending an HTTP 403 (Forbidden) response when appropriate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. 😊

@isears
Copy link
Member Author

isears commented Dec 3, 2018

@dkayiwa pushed the updates we talked about

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {

// check content-type (deny xml requests)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the above comment, wouldn't it be better to either remove it or say something like allow only json requests?

}

@Test
public void doFilter_shouldFilterXMLContent() throws IOException, ServletException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have used "Allow" for the other tests, can we consistently use the same even for this test?
When i first looks at "Filter" in the name, it was not obvious to me whether it is to filter to allow or not to allow.

@Test
public void doFilter_shouldFilterXMLContent() throws IOException, ServletException {

List<String> xmlContentTypes = Arrays.asList("application/xml", "text/xml");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add at least one non xml content type to make it more explicit that we intentionally decided to disable not only xml, but other content types too, that are not json?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the above, even the test method name would change to reflect that we are disabling everything that is not json

@isears
Copy link
Member Author

isears commented Dec 4, 2018

@dkayiwa I agree with all your suggestions. Just pushed changes that I think should address everything.

}

@Test
public void doFilter_shouldAllowGetRequest() throws IOException, ServletException {
Copy link
Member

@dkayiwa dkayiwa Dec 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the method name, should allow Get request for all content types? Or for when content type is not specified? Can we a bit more explicit about what this is testing for in regards to the various content types (json, xml, and others) and GET requests? Can we reflect this in the method name and method body?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the RFCs, a GET request shouldn't have a Content-Type, but I think an attacker could theoretically craft a GET request with a Content-Type header that may trigger this vulnerability. So I think two things are important here:

  1. We make sure we're allowing RFC-compliant GET requests because they are crucial to the normal operation of the Refapp.
  2. We make no assumptions about RFC-compliance when security checking and only allow requests with json or null Content-Types, regardless of the type of request (GET, POST, PUT, etc.)

With that in mind, would it be more appropriate to split that into two tests? E.g. doFilter_shouldAllowGetRequest and doFilter_shouldAllowNullContentType?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

@isears
Copy link
Member Author

isears commented Dec 8, 2018

@dkayiwa Did you get a chance to look at the latest changes? I pushed a few days ago but forgot to ping you about it

@dkayiwa dkayiwa merged commit 4605674 into openmrs:master Dec 8, 2018
@dkayiwa
Copy link
Member

dkayiwa commented Dec 8, 2018

Thanks @isears for the ping and hard work! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants