怎样写一个安全的Vert.x Web apps

此文只是对vert.x Web应用程序的入门指导(大神请绕道)。 这绝不是对Web应用程序安全性的全面指导,如OWASP。 标准规则和惯例适用于vert.x应用程序。

不要用root身份运行

DEVOPS团队成员会经常说,只给必要的运行权限,不要多给。有些新手试图以root用户在80或443端口下运行,虽然这样简单,但也为坏蛋打开一扇门。 让我们看看下面的代码:


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);
  }
}

当与开始CWD设置为/ (java -Dvertx.cwd=/ ...)你就创建了一个简单的文件服务器,给所有服务器存储。 现在,假设你要启动这个程序,会打印如下错误:

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

如果你现在使用root运行的,浏览器输入http://localhost/etc/shadow,恭喜你,你的服务器loginspasswords 暴露了 !

有几种方法,可以root用户运行,使用iptables请求转发到更高的端口,使用authbind,配置代理服务器ngnix等…

Sessions

许多应用程序要处理在某些时候用户会话。

会话Cookie应该有SECUREHTTPOnly设置的标志,只发送HTTPS(在使用HTTPS?),也没有脚本访问cookie的客户端:

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);

在这种情况下,检查浏览器,应该看到:

image

你的浏览器的脚本有读取的能力,可以嗅探劫持或篡改您的会话。

Security Headers

有很多的安全headers有助于提高安全性,只需几行代码。 没有必要在这里解释,因为网上有很好的文章说的做得可能会比我更好。

怎么实现:

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);
  }
}

保护跨站请求伪造(CSRF)

Vert.x webhandler里提供了CSRF保护。下面代码增加CSRF保护:

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))
        .setCookieHttpOnlyFlag(true)
        .setCookieSecureFlag(true)
    );
    router.route().handler(CSRFHandler.create("not a good secret"));

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

该处理器(handler)增加了一个CSRF令牌(token)。 为了改变cookie(XSRF-TOKEN),设置了一个独一无二的token,即预计返回一个(X-XSRF-TOKEN)header。

限制上传

上传处理一定要定义一个上限,否则你会很容易受到DDoS攻击。 例如,看看下面的代码:

public class App extends AbstractVerticle {

  @Override
  public void start() {

    Router router = Router.router(vertx);

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

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

现在,“好心人”可以随机生成一个1GB的垃圾文件:

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

然后把它上传到你的服务器:

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

您的应用程序将愉快地处理,上面两步不断重复,它会耗尽磁盘空间或内存。 为了减轻这些类型的攻击,始终指定的最大允许上传的大小:

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));

最后

虽然只有几点,也应该记住。

当程序要用于生产,你还要注意更多: