Showing posts with label JAX-RS. Show all posts
Showing posts with label JAX-RS. Show all posts

Using Jackson JSON View to Protect Mass Assignment Vulnerabilities


Senario
We use JAX-RS to develop Restful Web Service and only consume and produce json data with Jackson.

In our model class(ModelA), there may be cases that:

  • Some fields are only viewable but not editable  - Client can view them but can't edit it, they are maintained by backend logic
  • Some fields are totally internal, shouldn't even return to client, and client is not allowed to edit.

We need use code to implement this logic, express what fields are viewable only, what fields are editable, and what fields are internal in whitelist mode; otherwise we may expose some security issue. - Check Mass-Assignment Vulnerabilities... Or How Github Got Hacked

Solution - Jackson @JsonView
We can create JSON view like below:
public class View {
    
    public static class Editable {}
    public static class Viewable extends Editable {}
    public static class Internal extends Viewable {}
}

Then annotate our mode class:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {

 @JsonView(View.Editable.class)
 protected String editableField;

 @JsonView(View.Viewable.class)
 protected String viewableField; 

 @JsonView(View.Internal.class)
 protected String internalField;
}

At last, we annotate out jax-rs resource with @JsonView annotation.  
 @GET
 @Produces(MediaType.APPLICATION_JSON )
 @JsonView(View.Viewable.class)
 public Iterable<Model> search() {}

 @GET
 @Path("{id}")
 @Produces(MediaType.APPLICATION_JSON )
 @JsonView(View.Viewable.class)
 public Model getModel(@PathParam("id") final String id) {}

 @POST
 @Consumes({MediaType.APPLICATION_JSON})
 public Response add(@JsonView(View.Editable.class) final Model model) {}

In JAX-RS, if one model(either request or response) is annotated with @JsonView(View.Editable.class), in our case add method, Jackson will only serialize or deserialize fields that are annotated with @JsonView(View.Editable.class).
In our case, client can only pass editableField, if client pass any other fields, server will just silently ignore them.

If one model (either request or response) is annotated @JsonView(View.Viewable.class),  then Jackson will serialize or deserialize fields that are annotated with both @JsonView(View.Editable.class) and @JsonView(View.Viewable.class). child(Viewable) inherits view membership from parents(Editable).

In both cases, Jackson will not serialize or deserialize fields that are annotated with  @JsonView(View.Internal.class). So they are protected.

In our service implementation: in add method, we need make sure we add these non-editable fields; in update method, we may have to read and merge these non-editable fields from old value from database to the new value.

-- One trick: Don't mix-use @JsonIgnore and @JsonView, seems this will confuse Jackson, the field will be serialized or deserialized in all cases.

Misc
Spring MVC provides data binder that we can specify what fields are not allowed.
@InitBinder public void initBinder(WebDataBinder binder) { binder.setDisallowedFields(DISALLOWED_FIELDS); }


Read More
Jackson Essentials - the JSON Libaray
Using Jackson JSON View to Protect Mass Assignment Vulnerabilities
Merge JSON Objects: Jackson + BeanUtils.copyProperties
Jackson Generic Type + Java Type Erasure

Jackson Date Serialize + Deserialize
http://wiki.fasterxml.com/JacksonJsonViews
Mass-Assignment Vulnerabilities... Or How Github Got Hacked
Mass Assignment, Rails, and You

Java Config: Integrating JAX-RS Jersey with Spring Security


The Scenario
When we were developing the admin backend, we split it into 2 projects: backend application using jax-rs jersey, front end with angularjs - following single app design principle.

Later we decide to merge these 2 into one project, and use Spring security to protect front side(web url, page section etc) and jersey jax-rs.

We prefer Spring Java config over xml because it's more flexible, and XML configuration is kind of black box to developers,  using Java config, we can know more about the implementation and help us debug it later.

Update:
Later we upgrade to jersey 2:

@Priority(value = 1)
public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {MyAppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        // this is to fix issues in jersey-srping3
        // org.glassfish.jersey.server.spring.SpringWebApplicationInitializer
        servletContext.setInitParameter("contextConfigLocation", "");
        servletContext.addListener(RequestContextListener.class);
        MyUtil.addDefaultUncaughtExceptionHandler();
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        final WebApplicationContext context = super.createRootApplicationContext();
        final ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment();
        final String profile = (String) env.getSystemProperties().get("env");
        env.setActiveProfiles(profile);
        return context;
    }
}

public class MySecurityInitializer extends AbstractSecurityWebApplicationInitializer {}

@WebServlet(loadOnStartup = 1)
@ApplicationPath("/v1/*")
public class MyJerseyApplication extends ResourceConfig {
    public JerseyRestProvisionApplication() {
        packages("the_package_xx");
        property(ServerProperties.WADL_FEATURE_DISABLE, true);
        register(JacksonFeature.class);
        register(GZipEncoder.class);
        register(MultiPartFeature.class);

        register(new LoggingFilter(Logger.getLogger(MyJerseyApplication.class.getName()), true));
        register(RequestContextFilter.class);
        register(MyHttpHeaderFilter.class);
    }
}
How To
Basically we configure two servlets:

  • web.servlet.DispatcherServlet: map it to / (notice not /*). This handles Spring Security and Spring MVC.
  • Jersey SpringServlet: map to v1(all rest API starts with v1/).

Spring security is used to do authentication and authorization.

Jersery 1

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(final ServletContext servletContext) {
        final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        final ConfigurableEnvironment env = rootContext.getEnvironment();

        final String profile = (String) env.getSystemProperties().get("env");
        env.setActiveProfiles(profile);

        servletContext.addListener(new ContextLoaderListener(rootContext));

        addSpringServlet(servletContext, profile);
        addSpringJersyServlet(servletContext, env);
    }

    protected void addSpringServlet(final ServletContext servletContext, final String profile) {

        final DispatcherServlet dispatcherServlet = new DispatcherServlet(new GenericWebApplicationContext());

        final Dynamic dispatcherDynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);;
        dispatcherDynamic.setLoadOnStartup(1);

        dispatcherDynamic.addMapping("/");

        servletContext
                .addFilter(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, DelegatingFilterProxy.class)
                .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
    }

    protected void addSpringJersyServlet(final ServletContext servletContext, final ConfigurableEnvironment env) {
        final ServletRegistration.Dynamic appServlet = servletContext.addServlet("jersey-servlet", new SpringServlet());

        appServlet.setInitParameter(JSONConfiguration.FEATURE_POJO_MAPPING, "true");
        appServlet.setInitParameter(ResourceConfig.FEATURE_TRACE, env.getProperty("jersey.enable.trace", "false"));
        appServlet.setInitParameter(ResourceConfig.FEATURE_DISABLE_WADL,
                env.getProperty("jersey.disable.wadl", "true"));

        appServlet.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,..);
        appServlet.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ..);

        appServlet.setLoadOnStartup(2);
        appServlet.addMapping("/v1/*");
    }
}
Resources
Difference between / and /* in servlet mapping url pattern
http://javapapers.com/servlet/what-is-servlet-mapping/

JAX-RS: capture all error, don't expose internal stack trace


The Goal: Don't expose internal stack trace to client

Capture all Error
When we are developing restful API that are exposed to the internet,  we better capture all exception/error, and don't expose internal stack trace to outside.

In Jersey, we can use ExceptionMapper to capture any kind of exception or error.
For known exception such as ProductNotFound, we can capture it, and return meaningful error message to client.

But bad things always happen, our application may throw NullPointerExceptionn or exception that we don't ever expect or may throw OutOfMemoryError.

When this happens, we need capture it, return some message like: "internal error", and then asynchronously notify engineer owners(send email, store to db or or other approaches).

public class GenericExceptionMapper implements ExceptionMapper {
    private static final Logger LOGGER = LoggerFactory.getLogger(GenericExceptionMapper.class);
    private static final String MSG_INTERNAL_ERROR = "internal error.";
    public static final String HEADER_ERROR_CODE = "X-ErrorCode";

    @Override
    public Response toResponse(Throwable exception) {
        // including exception's type and message in the log message to facilitate log navigation

        // here we capture all error, so no stack trace is exposed to client.
        // TODO alert developers
        LOGGER.error("Unhanded exception detected {}:{}",
                new Object[] {exception.getClass(), exception.getMessage(), exception});

        if (exception instanceof OutOfMemoryError) {
            // notify engineer owner
        } else {
            // may caused by program bug
            // store the error to separate log or db, so we can check them easily
        }

        ResponseBuilder builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
        builder.header(HEADER_ERROR_CODE, MSG_INTERNAL_ERROR);
        builder.entity(new ErrorMessage(MSG_INTERNAL_ERROR)).type(MediaType.APPLICATION_JSON);
        return builder.build();
    }
}

Code is the King
How Jersey find exception mapper for specific exception?
com.sun.jersey.spi.container.ContainerResponse.mapException(Throwable)
com.sun.jersey.server.impl.application.ExceptionMapperFactory.find(Class)

It checks all exception mappers, get the exception mapper whose exception type is isAssignableFrom and the distance from current exception is smallest.

If can't find. it will ResponseListener onError, which by default returns html page with error stack trace.

    public ExceptionMapper find(Class c) {
        int distance = Integer.MAX_VALUE;
        ExceptionMapper selectedEm = null;
        for (ExceptionMapperType emt : emts) {
            int d = distance(c, emt.c);
            if (d < distance) { 
                distance = d;
                selectedEm = emt.em;
                if (distance == 0) break;
            }
        }
        
        return selectedEm;
    }
    
    private int distance(Class c, Class emtc) {
        int distance = 0;
        if (!emtc.isAssignableFrom(c))
            return Integer.MAX_VALUE;
        
        while (c != emtc) {
            c = c.getSuperclass();
            distance++;
        }
        
        return distance;
    }

Labels

ANT (6) Algorithm (69) Algorithm Series (35) Android (7) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) JSON (7) Java (186) JavaScript (27) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) adsense (5) bat (8) regex (5) xml (5)