Writing secure Vert.x Web apps

This is a start­ing guide for se­cur­ing vert.x web ap­pli­ca­tions. It is by no means a com­pre­hen­sive guide on web ap­pli­ca­tion se­cu­rity such as OWASP. Stan­dard rules and prac­tices apply to vert.x apps as if they would to any other web frame­work.

The post will cover the items that al­ways seem to come up on fo­rums.

Don’t run as root

It is a com­mon prac­tise that your de­vops team mem­ber will con­stantly say, one shall run a ser­vice with the least amount of priv­i­leges nec­es­sary and no more. Al­though this might sound like folk­lore to less ex­pe­ri­enced de­vel­op­ers that hit an issue when try­ing to run on priv­i­leged ports 80, 443, run­ning as root solves it quickly but open a door to big­ger prob­lems. Lets look at this code:

public class App extends AbstractVerticle {
  @Override
  public void start() {

    Router router = Router.router(vertx);

    router.route().handler(StaticHandler.create(""));

    vertx.createHttpServer().requestHandler(router::accept).listen(80);
  }
}

When started with the CWD set to / (java -Dvertx.cwd=/ ...) you just cre­ated a sim­ple file server for all your server stor­age. Now imag­ine that you want to start this ap­pli­ca­tion you will hit the error:

Aug 26, 2015 2:02:18 PM io.vertx.core.http.impl.HttpServerImpl
SEVERE: java.net.SocketException: Permission denied

So if you do now run as root it will start, how­ever in your browser now try to nav­i­gate to: http://localhost/etc/shadow con­grat­u­la­tions you just ex­posed your server logins and passwords!

There are sev­eral ways to run as a under priv­i­leged user, you can use iptables to for­ward re­quests to higher ports, use authbind, run be­hind a proxy like ngnix, etc…

Sessions

Many ap­pli­ca­tions are going to deal with user ses­sions at some point.

Ses­sion cook­ies should have the SECURE and HTTPOnly flags set. This en­sures that they can only be sent over HTTPS (you are using HTTPS right?) and there is no script ac­cess to the cookie client side:

Router router = Router.router(vertx);

router.route().handler(CookieHandler.create());
router.route().handler(SessionHandler
    .create(LocalSessionStore.create(vertx))
    .setCookieHttpOnlyFlag(true)
    .setCookieSecureFlag(true)
);

router.route().handler(routingContext -> {

  Session session = routingContext.session();

  Integer cnt = session.get("hitcount");
  cnt = (cnt == null ? 0 : cnt) + 1;

  session.put("hitcount", cnt);

  routingContext.response().end("Hitcount: " + cnt);
});

vertx.createHttpServer().requestHandler(router::accept).listen(8080);

And in this case when in­spect­ing your browser you should see:

nocookie

Of course if you do not do that any script on your browser has the ca­pa­bil­ity of read­ing, sniff­ing hi­jack­ing or tam­per­ing your ses­sions.

Security Headers

There are plenty of se­cu­rity head­ers that help im­prove se­cu­rity with just a cou­ple of lines of code. There is no need to ex­plain them here since there are good ar­ti­cles on­line that will prob­a­bly do it bet­ter than me.

Here is how one could im­ple­ment a cou­ple of them:

public class App extends AbstractVerticle {

  @Override
  public void start() {

    Router router = Router.router(vertx);
    router.route().handler(ctx -> {
      ctx.response()
          // do not allow proxies to cache the data
          .putHeader("Cache-Control", "no-store, no-cache")
          // prevents Internet Explorer from MIME - sniffing a
          // response away from the declared content-type
          .putHeader("X-Content-Type-Options", "nosniff")
          // Strict HTTPS (for about ~6Months)
          .putHeader("Strict-Transport-Security", "max-age=" + 15768000)
          // IE8+ do not allow opening of attachments in the context of this resource
          .putHeader("X-Download-Options", "noopen")
          // enable XSS for IE
          .putHeader("X-XSS-Protection", "1; mode=block")
          // deny frames
          .putHeader("X-FRAME-OPTIONS", "DENY");
    });

    vertx.createHttpServer().requestHandler(router::accept).listen(8080);
  }
}

Cross-Site Request Forgery (CSRF) Protection

Vert.x web pro­vides CSRF pro­tec­tion using an in­cluded han­dler. To en­able CSRF pro­tec­tions you need to add it to your router as you would add any other han­dler:

public class App extends AbstractVerticle {

  @Override
  public void start() {

    Router router = Router.router(vertx);

    router.route().handler(CookieHandler.create());
    router.route().handler(SessionHandler
        .create(LocalSessionStore.create(vertx))
        .setCookieSecureFlag(true)
    );
    router.route().handler(CSRFHandler.create("not a good secret"));

    router.route().handler(ctx -> {
      ...
    });

The han­dler adds a CSRF token to re­quests which mu­tate state. In order change the state a (XSRF-TOKEN) cookie is set with a unique token, that is ex­pected to be sent back in a (X-XSRF-TOKEN) header.

Limit uploads

When deal­ing with up­loads al­ways de­fine a upper bound, oth­er­wise you will be vul­ner­a­ble to DDoS at­tacks. For ex­am­ple lets say that you have the fol­low­ing code:

public class App extends AbstractVerticle {

  @Override
  public void start() {

    Router router = Router.router(vertx);

    router.route().handler(BodyHandler.create());

    router.route().handler(ctx -> {
      ...

Now a bad in­ten­tioned per­son could gen­er­ate a ran­dom file with 1GB of trash:

dd if=/dev/urandom of=ddos bs=1G count=1

And then up­load it to your server:

curl --data-binary "@ddos" -H "Content-Type: application/octet-stream" -X POST http://localhost:8080/

Your ap­pli­ca­tion will hap­pily try to han­dle this until one of 2 things hap­pens, it will run out of disk space or mem­ory. In order to mit­i­gate these kind of at­tacks al­ways spec­ify the max­i­mum al­lowed up­load size:

public class App extends AbstractVerticle {

  private static final int KB = 1024;
  private static final int MB = 1024 * KB;

  @Override
  public void start() {

    Router router = Router.router(vertx);
    router.route().handler(BodyHandler.create().setBodyLimit(50 * MB));

Final Words

Al­though this is just a small list of things you should re­mem­ber when im­ple­ment­ing your ap­pli­ca­tion there are more com­pre­hen­sive check­lists to check:

Next post

Contract Driven REST Services with Vert.x3

We see a new trend in development where we are shifting from developing applications to APIs. More and more we see services being offered as REST APIs that we are allowed to consume.

Read more
Previous post

Vert.x 3 real time web apps

One of the interesting features of Vert.x is the SockJS event bus bridge. It allows external applications to communicate with Vert.x event bus using Websockets.

Read more
Related posts

Some Rest with Vert.x

This post is part of the Introduction to Vert.x series. Let’s go a bit further this time and develop a CRUD-ish application

Read more

Real-time bidding with Websockets and Vert.x

The expectations of users for interactivity with web applications have changed over the past few years. Users during bidding in auction no longer want to press the refresh button.

Read more

An Introduction to the Vert.x Context Object

Under the hood, the vert.x Context class plays a critical part in maintaining the thread-safety guarantees of verticles. Most of the time, vert.x coders don't need to make use of Context objects directly.

Read more