This project is an example fw/1 application with secure single and two-factor (2FA) authentication and session management functions. This code was originally put together for the ColdFusion: Code Security Best Practices
presentation by Denard Springle at NCDevCon 2015 and has since been transformed into a concise starting point for developers who need to create a secure application using the fw/1 CFML MVC framework.
This code has been expanded multiple times to include additional functionality not shown during the initial presentation. More details on how (and why) these security functions work and are important can be gleaned from reading the ColdFusion Code Security guides on the bottom half of CFDocs and from reviewing the SecurityService.cfc in /model/services/ which has been expanded with comments to help aid in understanding how and why security features have been implemented and should be easy to pick up and run with for anyone with a passing familiarity of CFML and fw/1.
admin
subsystemadmin
subsystemApplication.cfc
addDate
true/false parameter to uberHash function to append the current date to the input value on hashflushCache
to flushprod
(production) environment before running IP watching or blocking routinesipBlocked.html
file.Application.cfc
FW/1 initialization modelApplication.cfc
(off by default to maintain backwards compatibility). Code prior to this release has been moved to the legacy
branch.rekeyKeyRing()
has been added to the SecurityService to aid in rekeying your keyring for this change (and rekeying it in general) if upgrading from a previous release. You may alternatively uncomment a line in Application.cfc
to force legacy master key usage. Please see additional notes in the Application.cfc
for further details. Lucee 4.5 will continue to use the legacy hashing of the master key.Application.cfc
. Please see additional notes in the Application.cfc
for further details.rc.product
and rc.version
variables definitions and the dashboard view now uses the engine and engine version information derived from the application scopefalse
) by default in Application.cfc
for backwards compatibility. To use this new function you should set application.rejectHackedPasswords
to true
.SecurityService.cfc
has been enhanced with additional functionality to randomly generate (when creating a new keyring) and use initialization vectors with all encryption and decryption. This will break existing code that is using a keyring without an initialization vector (will return an error about the length of the initialization vector).SecurityService.cfc
has been modified for compatibility with JDK17+ as it relates to the master key encryption and decryption block mode being utilized. Prior to these changes the master key encryption and decryption relied on the CTR block mode of encryption (BLOWFISH/CTR/PKCS5Padding). This has been modified to instead utilize CBC block mode (BLOWFISH/CBC/PKCS5Padding) for greater compatibility with JDK17+. This will break existing code that is using a keyring encrypted with the old CTR block mode. It is recommended to decrypt your existing keyring with the CTR block mode and then re-encrypt using the CBC block mode if you are upgrading from a previous version of this repository. The following code will help you accomplish this safely:<cfscript>
if( !structKeyExists( variables, 'rc' ) ) {
variables.rc = {};
structAppend( rc, url );
structAppend( rc, form, true );
}
// set a keyring path
rc.keyRingPath = expandPath( './keyrings/[ABCDEF0123456789].bin' );
// set a keyring backup path
rc.backupPath = rc.keyRingPath & '_BACKUP_' & dateTimeFormat( now(), 'yyyymmddhhnnss' );
// validate the keyring file exists
if( !fileExists( rc.keyRingPath ) ) {
throw( rc.keyRingPath & ': keyring path does not exist!' );
}
// backup the existing keyring file
fileCopy( rc.keyRingPath, rc.backupPath );
// validate the backup file exists
if( !fileExists( rc.backupPath ) ) {
throw( rc.backupPath & ': backup path does not exist!' );
}
// load the CTR encrypted keyring from the file
rc.keyring = charsetEncode( fileReadBinary( rc.keyRingPath ), 'utf-8' );
// decrypt the keyring with the master key and BLOWFISH/CTR block mode
rc.roundOne = decrypt( rc.keyring, rc.masterKey, 'BLOWFISH/CTR/PKCS5Padding', 'HEX' );
rc.roundTwo = decrypt( roundOne, rc.masterKey, 'AES/CBC/PKCS5Padding', 'HEX' );
// re-encrypt the keyring with the master key and BLOWFISH/CBC block mode
rc.roundOne = encrypt( rc.roundTwo, rc.masterKey, 'AES/CBC/PKCS5Padding', 'HEX' );
rc.roundTwo = encrypt( roundOne, variables.masterKey, 'BLOWFISH/CBC/PKCS5Padding', 'HEX' );
// write the keyring back to disk
fileWrite( rc.keyRingPath, charsetDecode( rc.roundTwo, 'utf-8' ) );
</cfscript>
NEW! The scrypt JAR has been added to the repository and initialized for use (with 32MB/64MB RAM used for hashing). It has been added to the uberHash()
method of SecurityService.cfc
and can be utilized by passing the flag useScrypt
as true
(default is false
). e.g. application.securityService.uberHash( input = 'mY$7R0nGP@$$w0R6', useScrypt = true )
NEW! The scrypt JAR has been added to the repository and initialized for use (with 32MB/64MB RAM used for hashing). It has been added to the uberHash()
method of SecurityService.cfc
and can be utilized by passing the flag useScrypt
as true
(default is false
). e.g. application.securityService.uberHash( input = 'mY$7R0nGP@$$w0R6', useScrypt = true )
NEW! The missing link... a new function, checkScrypt()
has been added to the SecurityService.cfc
to check for values hased using useScript=true
with uberHash()
Lucee 4.5+
Adobe ColdFusion 2021+
box install fw1-sa
twofactorauth
for your database in your CFML engine's admin (or change in Application.cfc
)Application.cfc
if running Lucee 5.x+)keyrings
folder to a location outside your webrootdevelopmentHmacKey
value in Application.cfc
(use generateSecretKey( 'HMACSHA512' )
)keyRingPath
location to where you moved the keyrings
folder to in Application.cfc
173
to some other integer number of iterations in Application.cfc
Application.cfc
(instead of c2VjdXJlX2F1dGhfbWFzdGVyX2tleQ==
)Application.cfc
(instead of UnRUcFBBS1hOQmgwem9XYg==
)Application.cfc
(instead of c2VjdXJlX2F1dGhfa2V5cmluZw==
)512
to some other integer number of iterations in Application.cfc
mid()
function of the hashed master key to start at a position other than 38
in a range from 1
to 106
cookieName
and dummyCookieOne
, dummyCookieTwo
and dummyCookieThree
values in Application.cfc
Application.cfc
as needed (see notes in Application.cfc
)check if the keyring is a valid array of keys
statement in Application.cfc
to prevent regeneration of a new keyring file after initial launch. See notes in Application.cfc
.NOTE If you are currently running a version of fw1-sa without the 2FA integration, then you'll need to complete the following steps before updating to the latest master branch:
If not using 2FA:
Application.cfc
(or MyApplication.cfc
if included in your distribution) so you can copy values for keyring and other application variables as needed.providerId
and phone
as additional fields before updatingIf using 2FA:
Application.cfc
(or MyApplication.cfc
if included in your distribution) so you can copy values for keyring and other application variables as needed.If you find any bugs or have a feature you'd like to see implemented in this code, please use the issues area here on GitHub to log them.
This project is actively being maintained and monitored by Denard Springle. If you would like to contribute to this example please feel free to fork, modify and send a pull request!
This project utilizes the free open source MVC CFML (ColdFusion) framework Framework One (fw/1) by Sean Corfield.
The use and distribution terms for this software are covered by the Apache Software License 2.0 (http://www.apache.org/licenses/LICENSE-2.0).