Skip to content

Commit

Permalink
v0.3.0: Hooks and many improvements
Browse files Browse the repository at this point in the history
See CHANGELOG.md
  • Loading branch information
timokoessler committed Nov 6, 2022
1 parent 1d31f7c commit 9a951cc
Show file tree
Hide file tree
Showing 16 changed files with 731 additions and 549 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.3.0] - 2022-11-06

Hooks, log request method, improvements and bug fixes.

### Added

- Added Pre- and Post-Block-Hooks, which makes it possible, for example, to have your own whitelist rules or notifications.
- Log request method
- Validate ip addresses in cidr notation before adding to search crawler whitelist
- Example of how to send notifications when a request is blocked
- Bug fix: Remove unicode character "Zero Width Space" (200B) from bing ip adresses

### Changed

- Bug fix: replace quotation marks in logs (user agent and url)
- Remove `googleusercontent.com` from trusted urls for fake search crawler detection
- Remove `Not` and `Petalbot` from bad bot list

## [0.2.0] - 2022-10-23

The second beta release.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ app.use(easyWaf({
| ipWhitelist | array | [] | All requests by ips on the whitelist are never blocked. CIDR notation is supported. |
| modules[name].enabled | boolean | true, except "Block Tor Exit Nodes" | This option allows you to completely disable a specific module. |
| modules[name].excludePaths | boolean | undefined | Exclude paths from being checked by this module with a regex. |
| postBlockHook | callback | undefined | Run your own code after a request is blocked. For example, you can send a notification. |
| preBlockHook | callback | undefined | Run your own code before a request is blocked. Return false if the request should not be blocked. |
| trustProxy | string / array | [] | If a reverse proxy is used, this setting must be configured. See [npm/proxy-addr](https://www.npmjs.com/package/proxy-addr) for possible values. |

## What is checked?
Expand Down
22 changes: 22 additions & 0 deletions examples/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const express = require('express');
const easyWaf = require('easy-waf');
const app = express();

app.use(easyWaf({
preBlockHook: (req, moduleInfo, ip) => { // Executed before a request is blocked. If false is returned, the request will not be blocked.
if (moduleInfo.name === 'xss'
&& ['::1', '127.0.0.1', '::ffff:127.0.0.1'].includes(ip)
&& req.url.match('^[^?]*')[0] === '/test') { // Do not block xss from localhost at path /test
return false;
}
},
postBlockHook: (req, moduleInfo, ip) => { // Executed after a request is blocked. See send-notification.js for an example.
// ...
}
}));

app.get('/get', function(req, res){
res.status(200).send();
});

app.listen(3000);
29 changes: 29 additions & 0 deletions examples/send-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const express = require('express');
const easyWaf = require('easy-waf');
const nodemailer = require('nodemailer');
const app = express();

var mailTransporter = nodemailer.createTransport({
host: 'mail.example.com',
port: 587, secure: false,
auth: { user: 'user@example.com', pass: 'pass' }
});

app.use(easyWaf({
postBlockHook: (req, moduleInfo, ip) => { // Executed after a request is blocked.
mailTransporter.sendMail({
from: 'from@example.com',
to: 'to@example.com',
subject: `EasyWAF Blocked Request from ${ip}`,
html: `Module: ${moduleInfo.name}<br>Url: ${req.url}<br>...`
}).catch(function (e) {
console.log('Error on sendMail: ' + e.message);
});
}
}));

app.get('/get', function (req, res) {
res.status(200).send();
});

app.listen(3000);
13 changes: 12 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ declare module "block" {
* @param {EasyWAFModuleInfo} moduleInfo
* @param {EasyWafConfig} config
* @param {String} ip
* @returns {Boolean}
*/
function blocked(req: import('http').IncomingMessage, res: import('http').ServerResponse, moduleInfo: EasyWAFModuleInfo, config: EasyWafConfig, ip: string): void;
function blocked(req: import('http').IncomingMessage, res: import('http').ServerResponse, moduleInfo: EasyWAFModuleInfo, config: EasyWafConfig, ip: string): boolean;
}
declare module "modules/badBots" {
/**
Expand Down Expand Up @@ -343,6 +344,14 @@ type EasyWafConfig = {
* This option allows you to enable / disable modules or exclude paths with a regex
*/
modules?: EasyWafConfigModules;
/**
* Run your own code after a request is blocked. For example, you can send a notification.
*/
postBlockHook?: EasyWAFPostBlockHook;
/**
* Run your own code before a request is blocked. Return false if the request should not be blocked.
*/
preBlockHook?: EasyWAFPreBlockHook;
/**
* If a reverse proxy is used, this setting must be configured. See https://www.npmjs.com/package/proxy-addr for possible values.
*/
Expand Down Expand Up @@ -412,3 +421,5 @@ type EasyWAFRequestInfo = {
*/
url: string;
};
type EasyWAFPreBlockHook = (req: import('http').IncomingMessage, moduleInfo: EasyWAFModuleInfo, ip: string) => boolean;
type EasyWAFPostBlockHook = (req: import('http').IncomingMessage, moduleInfo: EasyWAFModuleInfo, ip: string) => any;
14 changes: 14 additions & 0 deletions lib/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const crypto = require('crypto');
* @param {EasyWAFModuleInfo} moduleInfo
* @param {EasyWafConfig} config
* @param {String} ip
* @returns {Boolean}
*/
function blocked(req, res, moduleInfo, config, ip){

Expand All @@ -16,6 +17,10 @@ function blocked(req, res, moduleInfo, config, ip){
/** @type {String} */
var referenceID = crypto.createHash('sha256').update(ip + date.getTime()).digest('hex');

if(typeof config.preBlockHook === 'function' && config.preBlockHook(req, moduleInfo, ip) === false){
return false;
}

if(!config.dryMode){
res.writeHead(403, {'Content-Type': 'text/html'});
if(!config.customBlockedPage){
Expand Down Expand Up @@ -57,6 +62,15 @@ function blocked(req, res, moduleInfo, config, ip){
}

logger.requestBlocked(moduleInfo, req, referenceID, config, ip);

if(typeof config.postBlockHook === 'function'){
config.postBlockHook(req, moduleInfo, ip);
}

if(config.dryMode){
return false;
}
return true;
}

module.exports = blocked;
31 changes: 17 additions & 14 deletions lib/easy-waf.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ var config = {
enabled: true
}
},
postBlockHook: undefined,
preBlockHook: undefined,
trustProxy: []
};

Expand Down Expand Up @@ -123,22 +125,23 @@ function easyWaf(conf){
}

if(typeof ipBlacklist !== 'undefined' && ipBlacklist.contains(ip)){
block(req, res, {name: 'IPBlacklist'}, config, ip);
if(config.dryMode) next();
return;
if(block(req, res, {name: 'IPBlacklist'}, config, ip)){
return;
}
}

if(Array.isArray(config.allowedHTTPMethods) && !config.allowedHTTPMethods.includes(req.method)){
block(req, res, {name: 'HTTPMethod'}, config, ip);
if(config.dryMode) next();
return;
if(block(req, res, {name: 'HTTPMethod'}, config, ip)){
return;
}
}

try {
var url = decodeURIComponent(req.url);
} catch(e) {
block(req, res, {name: 'uriMalformed'}, config, ip);
if(config.dryMode) next();
if(!block(req, res, {name: 'uriMalformed'}, config, ip)){
next();
}
return;
}

Expand Down Expand Up @@ -189,16 +192,16 @@ function modulesLoop(modules, moduleKeys, i, req, res, reqInfo, next){
let key = moduleKeys[i];
if(typeof modules[key].check === 'function'){
if(!modules[key].check(reqInfo)){
block(req, res, modules[key].info(), config, reqInfo.ip);
if(config.dryMode) next();
return;
if(block(req, res, modules[key].info(), config, reqInfo.ip)){
return;
}
}
} else if(typeof modules[key].checkCB === 'function'){
modules[key].checkCB(reqInfo, (ok) => {
if(!ok){
block(req, res, modules[key].info(), config, reqInfo.ip);
if(config.dryMode) next();
return;
if(block(req, res, modules[key].info(), config, reqInfo.ip)){
return;
}
}
modulesLoop(modules, moduleKeys, ++i, req, res, reqInfo, next);
});
Expand Down
2 changes: 1 addition & 1 deletion lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function requestBlocked(moduleInfo, req, referenceID, config, ip){
var url = req.url.replace(/(\n|\r|\v)/ig, '').replace(/"/g, '&quot;');
var ua = (req.headers['user-agent'] || '').replace(/(\n|\r|\v)/ig, '').replace(/"/g, '&quot;');

console.warn((!config.dryMode ? 'EasyWAF - Blocked:' : 'EasyWAF DryMode - Blocked:') + ' ip=' + ip + ' module=' + moduleInfo.name + ' time=' + new Date().getTime() + ' url="' + url + '" ua="' + ua + '" rid=' + referenceID);
console.warn((!config.dryMode ? 'EasyWAF - Blocked:' : 'EasyWAF DryMode - Blocked:') + ' ip=' + ip + ' module=' + moduleInfo.name + ' time=' + new Date().getTime() + ' url="' + url + '" ua="' + ua + '" method=' + req.method + ' rid=' + referenceID);
}

module.exports = {
Expand Down
Loading

0 comments on commit 9a951cc

Please sign in to comment.