Skip to content

Commit

Permalink
Update RateLimiter.cfc, fix for CONTENTBOX-1512 (#608)
Browse files Browse the repository at this point in the history
* Update RateLimiter.cfc, fix for CONTENTBOX-1512 #resolve
  • Loading branch information
GunnarLieb authored Jun 26, 2024
1 parent 0c7df70 commit a8314cc
Showing 1 changed file with 52 additions and 76 deletions.
128 changes: 52 additions & 76 deletions modules/contentbox/models/security/RateLimiter.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@
component extends="coldbox.system.Interceptor" {

// DI
property name="settingService" inject="id:settingService@contentbox";
property name="securityService" inject="id:securityService@contentbox";
property name = "settingService" inject = "id:settingService@contentbox";
property name = "securityService" inject = "id:securityService@contentbox";
property name = "cachebox" inject = "Cachebox";

/**
* Configure
*/
function configure(){
// the limiter data
variables.limitData = {};
}

/**
* Limiter
* onRequestCapture
* fires before any event caching or processing
*/
function onRequestCapture( event, data, buffer ){
var allSettings = variables.settingService.getAllSettings();
Expand All @@ -42,99 +36,81 @@ component extends="coldbox.system.Interceptor" {
}
}

/**
* Written by Charlie Arehart, charlie@carehart.org, in 2009, updated 2012, modified by Luis Majano 2016
* - Throttles requests made more than "count" times within "duration" seconds from single IP.
* - Duck typed for performance
*
* @count The throttle counter
* @duration The time in seconds to limit
* @event The request context object
* @settings The settings structure
*/
private function limiter( count, duration, event, settings ){
/**
* Written by Charlie Arehart, charlie@carehart.org, in 2009, updated 2012, modified by Luis Majano 2016
* - Throttles requests made more than "count" times within "duration" seconds from single IP.
* - Duck typed for performance
* @count The throttle counter
* @duration The time in seconds to limit
* @event The request context object
* @settings The settings structure
*/
private function limiter( count, duration, event, settings ) {
// Get real IP address of requester
var realIP = variables.securityService.getRealIP();

// If first time visit, create record.
if ( !structKeyExists( variables.limitData, realIP ) ) {
lock name="cb-ratelimiter-#hash( realIP )#" type="exclusive" throwontimeout="true" timeout="5" {
if ( !structKeyExists( variables.limitData, realIP ) ) {
variables.limitData[ realIP ] = { attempts : 1, lastAttempt : now() };
return this;
}
}
}

lock name="cb-ratelimiter-#hash( realIP )#" type="readonly" throwontimeout="true" timeout="5" {
var targetData = variables.limitData[ realIP ];
var realIP = variables.securityService.getRealIP();
var cache = cachebox.getDefaultCache();
var cacheKey = 'limiter'&realIP;

var targetData = cache.getOrSet( cacheKey, function(){
return { attempts = 0, lastAttempt = now() }
} );

// on first visit no further processing
if( targetData.attempts == 0 ){
targetData.attempts++;
cache.set( cacheKey, targetData );
return this;
}

log.debug( "Limit data", targetData );
// log.info( "DateDiff " & dateDiff( "s", targetData.lastAttempt, Now() ) );
// log.info( "Within Duration " & dateDiff( "s", targetData.lastAttempt, Now() ) LT arguments.duration );

//log.info( "DateDiff " & dateDiff( "s", targetData.lastAttempt, Now() ) );
//log.info( "Within Duration " & dateDiff( "s", targetData.lastAttempt, Now() ) LT arguments.duration );
// Are we executing another request withing our duration in seconds? Ex: Has X seconds passed before last attempt
if ( dateDiff( "s", targetData.lastAttempt, now() ) LT arguments.duration ) {
if( dateDiff( "s", targetData.lastAttempt, Now() ) LT arguments.duration ){
// Limit by count?
if ( targetData.attempts GT arguments.count ) {
if ( settings.cb_security_rate_limiter_logging && log.canInfo() ) {
if( targetData.attempts GT arguments.count ){

if( settings.cb_security_rate_limiter_logging && log.canInfo() ){
// Log it to app logs
log.info(
"'limiter invoked for:','#realIP#',#targetData.attempts#,#cgi.request_method#,'#cgi.SCRIPT_NAME#', '#cgi.QUERY_STRING#','#cgi.http_user_agent#','#targetData.lastAttempt#',#listLen( cgi.http_cookie, ";" )#"
);
log.info( "'limiter invoked for:','#realIP#',#targetData.attempts#,#cgi.request_method#,'#cgi.SCRIPT_NAME#', '#cgi.QUERY_STRING#','#cgi.http_user_agent#','#targetData.lastAttempt#',#listlen(cgi.http_cookie,";" )#" );
}

// Log attempt
lock name="cb-ratelimiter-#hash( realIP )#" type="exclusive" throwontimeout="true" timeout="5" {
targetData.attempts++;
targetData.lastAttempt = now();
}
targetData.attempts++;
targetData.lastAttempt = now();
cache.set( cacheKey, targetData );

// Do we have a redirect URL setup?
if ( len( settings.cb_security_rate_limiter_redirectURL ) ) {
location(
settings.cb_security_rate_limiter_redirectURL,
"false",
"301"
);
if( len( settings.cb_security_rate_limiter_redirectURL ) ){
location( settings.cb_security_rate_limiter_redirectURL, "false", "301" );
return;
}

// Output Message
writeOutput(
replaceNoCase(
settings[ "cb_security_rate_limiter_message" ],
"{duration}",
arguments.duration,
"all"
)
writeOutput(
replaceNoCase( settings[ "cb_security_rate_limiter_message" ], "{duration}", arguments.duration, "all" )
);
arguments.event
.setHTTPHeader( statusCode = "503", statusText = "Service Unavailable" )
.setHTTPHeader( name = "Retry-After", value = arguments.duration );
arguments.event.setHTTPHeader( statusCode="503", statusText="Service Unavailable" )
.setHTTPHeader( name="Retry-After", value=arguments.duration );

// No execution anymore.
event.noExecution();

// Hard abort;
abort;
abort;
} else {
// Log attempt
lock name="cb-ratelimiter-#hash( realIP )#" type="exclusive" throwontimeout="true" timeout="5" {
targetData.attempts++;
targetData.lastAttempt = now();
}
targetData.attempts++;
targetData.lastAttempt = now();
}
} else {
// Reset attempts counter, since we are in the safe zone, just log the last attempt timestamp
lock name="cb-ratelimiter-#hash( realIP )#" type="exclusive" throwontimeout="true" timeout="5" {
targetData.attempts = 0;
targetData.lastAttempt = now();
}
targetData.attempts = 0;
targetData.lastAttempt = now();
}

cache.set( cacheKey, targetData );
return this;
}

}

0 comments on commit a8314cc

Please sign in to comment.