Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple & experimental Web Application Firewall using Cloudflare's edge workers
/*
*
* Web Application Firewall built with Cloudflare workers
*
* Author: < https://twitter.com/dustyfresh >
*
* License: GPLv3 < https://www.gnu.org/licenses/gpl-3.0.en.html >
*
* Cloudflare worker documentation:
* < https://developers.cloudflare.com/workers/about/ >
*
* Event logging is with Loggly
* < https://www.loggly.com/docs/http-endpoint/ >
*
*/
/*
Start of variable config
- Each request starts with a risk score of 0
- Any request with a risk score greater than safe_score will be dropped
*/
var score = 0;
var safe_score = 50;
// Set this to 1 if you are using static hosting like S3 that can't process POST requests.
// Set to 0 if your backend will handle POST requests
var no_post = 0;
// loggly HTTP/S Event Endpoint to send logs to
// https://www.loggly.com/docs/http-endpoint/
var LOGGLY_ENDPOINT = 'changeme'
// error handling
function handle_error(err){
console.log(err);
}
// event logging
function log_violation(msg){
console.log(msg);
}
function high_risk_event(input){
// things that go here should always have higher weight because it's definitely
// considered bad.
var bad_input = [
'%00',
'eval(',
'alert(',
'<?',
'javascript:',
'<script>',
'\00',
'system(',
'file://',
'php://',
'gopher://',
'ftp://',
'sftp://',
'zlib://',
'data://',
'glob://',
'$(',
'`',
'cmd.exe',
]
bad_input.forEach(function(sig){
if(input.includes(sig)){
score += 100;
log_violation('detected '+sig+' in the user-agent header');
}
});
}
// Process user-agent for malicious things
function process_user_agent(ua){
high_risk_event(ua);
// process user-agent with our list of regular expression signatures
var bad_agent_regexp = [
'python',
'curl',
'java',
'wget',
'lynx',
'eval',
'fake',
'w00t',
'perl',
'spider', // arachnophobia was the best movie of all time
'burp',
'acunetix',
'desu',
'wpscan',
'dirbuster',
'sqlmap',
'evil',
'masscan',
'requests',
'shodan',
'scan.lol',
'nikto',
'nmap',
'`',
"'{1}", // start of some sqli sigs
'union',
'update',
'delete',
'insert',
'table',
'from',
'ascii',
'hex',
'drop',
'eval',
]
bad_agent_regexp.forEach(function(sig){
var regexp = new RegExp(sig);
if(regexp.test(ua)){
score += 100;
log_violation('detected '+sig+' in the user-agent header');
}
});
}
// Process URL
function process_url(url){
high_risk_event(url);
var bad_url_sigs = [
'..\/{1,}etc',
"'{1}"
]
bad_url_sigs.forEach(function(sig){
var regexp = new RegExp(sig);
if(regexp.test(url)){
score += 100;
log_violation('detected '+sig+' in the url');
}
});
}
// Process POST input before sending to the backend
function process_post(postData){
high_risk_event(postData);
// start of regexp sigs
var bad_post_sigs = [
'..\/{1,}etc',
"'{1}"
]
bad_post_sigs.forEach(function(sig) {
var regexp = new RegExp(sig);
if(regexp.test(postData)){
score += 100;
log_violation('detected '+sig+' in POST data');
}
});
}
// start the CF worker event listener
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request))
});
async function fetchAndApply(request) {
// We catch the exception and set ua to 0 if there
// is not user-agent header in the request
try {
// start user-agent analysis
var ua = request.headers.get('user-agent').toLowerCase();
process_user_agent(ua, score);
} catch(err) {
var ua = 0;
}
// start URL analysis
var url = request.url.toLowerCase();
process_url(decodeURIComponent(url), score);
// inspect POST requests for bad things
if(request.method == 'POST'){
if(no_post == 1){
return new Response('Method not allowed', {status: 405, statusText: 'denied'});
} else {
let body = await request.text()
let formData = new URLSearchParams(body)
process_post(decodeURIComponent(formData));
// we log all POST data to loggly (todo: change this to be json data that is sent to loggly)
let headers = {'Content-Type': 'content-type:text/plain' }
const init = { method: 'POST', headers: headers, body: '{ "event": "post_request", "score": ' + score + ', "payload": "' + decodeURIComponent(body) + '", "url": "' + decodeURIComponent(request.url) + '" }' }
const response = await fetch(LOGGLY_ENDPOINT, init);
// check request threat score
if(score > safe_score){
// return 403 page if POST check does not pass the process_post function
let headers = {'Content-Type': 'content-type:text/plain' }
const init = { method: 'POST', headers: headers, body: '{ "event": "firewall", "score": ' + score + ', "payload": "' + decodeURIComponent(body) + '", "url": "' + decodeURIComponent(request.url) + '" }' }
const response = await fetch(LOGGLY_ENDPOINT, init);
return new Response('(╯°□°)╯︵ ┻━┻', {status: 403, statusText: 'Forbidden'});
} else {
// return request to backend with POST params since they are not bad
let newRequest = new Request(request, { body })
return fetch(newRequest);
}
}
} else {
// proceed with GET request scoring
if(score > safe_score){
let headers = {'Content-Type': 'content-type:text/plain' }
const init = { method: 'POST', headers: headers, body: '{ "event": "firewall", "score": ' + score + ', "url": "' + decodeURIComponent(request.url) + '" }' }
const response = await fetch(LOGGLY_ENDPOINT, init);
return new Response('(╯°□°)╯︵ ┻━┻', {status: 403, statusText: 'Forbidden'});
} else {
return fetch(request);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment