UNPKG

expresser

Version:

A ready to use Node.js web app wrapper, built on top of Express.

210 lines (162 loc) 7.44 kB
# EXPRESSER FIREWALL # ----------------------------------------------------------------------------- # Firewall to protect the server against well known HTTP and socket attacks. # ATTENTION! The Firewall module is started automatically by the App module. If you wish to # disable it, set `Settings.firewall.enabled` to false. # <!-- # @see Settings.firewall # --> class Firewall async = require "async" lodash = require "lodash" logger = require "./logger.coffee" moment = require "moment" settings = require "./settings.coffee" sockets = "./sockets.coffee" util = require "util" utils = require "./utils.coffee" # Holds a collection of blacklisted IPs. blacklist: null # PROTECTION PATTERNS # ------------------------------------------------------------------------- patterns = {} patterns.lfi = [/\.\.\//] # root path match patterns.sql = [/((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(;))/i, # SQL meta chars /\w*((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/i, # simple SQL injection /((\%27)|(\'))union/i, # union SQL injection /exec(\s|\+)+(s|x)p\w+/i, # MSSQL specific injection /UNION(?:\s+ALL)?\s+SELECT/i] # union select SQL injection patterns.xss = [/((\%3C)|<)((\%2F)|\/)*[a-z0-9\%]+((\%3E)|>)/i, # simple XSS /((\%3C)|<)((\%69)|i|(\%49))((\%6D)|m|(\%4D))((\%67)|g|(\%47))[^\n]+((\%3E)|>)/i, # img src XSS /((\%3C)|<)[^\n]+((\%3E)|>)/i] # all other XSS # INIT # ------------------------------------------------------------------------- # Init the firewall. This must be called AFTER the web app has started. # @param [Object] Firewall init options. # @option options [Object] server The Express server object to bind to. init: (options, callback) => options = {server: options} if not options.server? env = process.env # Server is mandatory! if not options.server? logger.error "Firewall.init", "App server is invalid. Abort!" return # Bind HTTP protection. if settings.firewall.httpPatterns isnt "" and env.NODE_ENV isnt "test" options.server.use @checkHttpRequest logger.info "Firewall.init", "Protect HTTP requests." # Bind sockets protection. if settings.firewall.socketPatterns isnt "" and env.NODE_ENV isnt "test" logger.info "Firewall.init", "Protect Socket requests." @blacklist = {} # HTTP PROTECTION # ------------------------------------------------------------------------- # Check HTTP requests against common web attacks. checkHttpRequest: (req, res, next) => ip = utils.getClientIP req # If IP is blaclisted, end the request immediatelly. if @checkBlacklist ip @sendAccessDenied res, "Blacklisted" # Helper method to check HTTP patterns. check = (p) => @checkHttpPattern p, req, res # Set valid and checl all enabled patterns. valid = true enabledPatterns = settings.firewall.httpPatterns.split "," for pattern in enabledPatterns valid = false if check(pattern) # Only proceed if request is valid. next() if valid # Test the request against the enabled protection patterns. checkHttpPattern: (module, req, res) => p = patterns[module].length - 1 while p >= 0 if patterns[module][p].test req.url @handleHttpAttack "#{module}", p, req, res return true --p return false # Handle attacks. handleHttpAttack: (module, pattern, req, res) => ip = utils.getClientIP req @logAttack module, pattern, req.url, ip @sendAccessDenied res # SOCKETS PROTECTION # ------------------------------------------------------------------------- # Check Socket requests against common web attacks. checkSocketRequest: (socket, message, next) => ip = utils.getClientIP req # If IP is blaclisted, end the request immediatelly. if @checkBlacklist ip @sendAccessDenied socket, "Blacklisted" # Helper method to check socket patterns. check = (p) => @checkSocketPattern p, socket, util.inspect(message) # Set valid and checl all enabled patterns. valid = true enabledPatterns = settings.firewall.socketPatterns.split "," for pattern in enabledPatterns valid = false if check(pattern) # Only proceed if request is valid. next() if valid # Test the request against the enabled protection patterns. checkSocketPattern: (module, socket, message) => p = patterns[module].length - 1 while p >= 0 if patterns[module][p].test message @handleSocketAttack "#{module}", p, socket, message return --p # Handle attacks. handleSocketAttack: (module, pattern, socket, message) => ip = utils.getClientIP socket @logAttack module, pattern, message, ip @sendAccessDenied socket # BLACKLIST METHODS # ------------------------------------------------------------------------- # Reset the blacklist object. clearBlacklist: => @blacklist = {} # Check if the specified IP is blacklisted. checkBlacklist: (ip) => bl = @blacklist[ip] if not bl? return false # Check if record has expired. if bl.expires < moment() delete @blacklist[ip] return false # Increase the blacklist count, and increase the expiry date in case # it has reached the max retries. bl.count = bl.count + 1 if bl.count >= settings.firewall.blacklistMaxRetries bl.expires = moment().add settings.firewall.blacklistLongExpires, "s" return true # Add the specified IP to the blacklist. addToBlacklist: (ip) => bl = @blacklist[ip] bl = {} if not bl? # Set blacklist object. bl.expires = moment().add settings.firewall.blacklistExpires, "s" bl.count = 1 @blacklist[ip] = bl # HELPER METHODS # ------------------------------------------------------------------------- # Send an access denied and end the request in case it wasn't authorized. sendAccessDenied: (obj, message) => message = "Access denied" if not message? # Set status and message. obj.status(403) if obj.status? and not obj.headerSent obj.send(message) if obj.send? # Disconnect or end? if obj.disconnect? return obj.disconnect() else return obj.end() # Log attacks. logAttack: (module, pattern, resource, ip) => logger.warn "ATTACK DETECTED!", module, pattern, resource, "From #{ip}" # Singleton implementation # -------------------------------------------------------------------------- Firewall.getInstance = -> @instance = new Firewall() if not @instance? return @instance module.exports = exports = Firewall.getInstance()