Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions system/web/context/RequestContext.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -1799,10 +1799,34 @@ component serializable="false" accessors="true" {
}

/**
* Get the HTTP Request Method Type
* Get the original (transport-level) HTTP Request Method Type, ignoring any _method override.
*/
string function getOriginalHTTPMethod(){
return uCase( CGI.REQUEST_METHOD );
}

/**
* Get the effective HTTP Request Method Type.
* Method spoofing via the _method parameter is only honored when the original
* transport-level request method is POST, and only for PUT, PATCH, and DELETE overrides.
* This prevents GET requests from spoofing destructive HTTP methods.
*/
string function getHTTPMethod(){
return getValue( "_method", CGI.REQUEST_METHOD );
var originalMethod = getOriginalHTTPMethod();

// Only honor _method override on POST requests
if ( originalMethod != "POST" ) {
return originalMethod;
}

var overriddenMethod = uCase( trim( getValue( "_method", "" ) ) );

// Only allow overriding to PUT, PATCH, or DELETE from POST
if ( listFindNoCase( "PUT,PATCH,DELETE", overriddenMethod ) ) {
return overriddenMethod;
}

return originalMethod;
}

/**
Expand Down
52 changes: 52 additions & 0 deletions tests/specs/web/context/RequestContextTest.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -856,4 +856,56 @@ component extends="coldbox.system.testing.BaseModelTest" {
expect( event.getSesBaseUrl() ).toInclude( event.getSESBasePath() );
}

/**
* Tests for method spoofing security fix (COLDBOX-1406).
* _method override should only be honored when the original request is POST.
*/
function testGetHTTPMethodNoOverrideOnGET(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "GET" );
event.setValue( "_method", "DELETE" );
expect( event.getHTTPMethod() ).toBe( "GET" );
}

function testGetHTTPMethodNoOverrideOnHEAD(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "HEAD" );
event.setValue( "_method", "DELETE" );
expect( event.getHTTPMethod() ).toBe( "HEAD" );
}

function testGetHTTPMethodNoOverrideOnPUT(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "PUT" );
event.setValue( "_method", "DELETE" );
expect( event.getHTTPMethod() ).toBe( "PUT" );
}

function testGetHTTPMethodPostDeleteOverride(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "POST" );
event.setValue( "_method", "DELETE" );
expect( event.getHTTPMethod() ).toBe( "DELETE" );
}

function testGetHTTPMethodPostPutOverride(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "POST" );
event.setValue( "_method", "PUT" );
expect( event.getHTTPMethod() ).toBe( "PUT" );
}

function testGetHTTPMethodPostPatchOverride(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "POST" );
event.setValue( "_method", "PATCH" );
expect( event.getHTTPMethod() ).toBe( "PATCH" );
}

function testGetHTTPMethodPostWithNoOverride(){
var event = getRequestContext().$( "getOriginalHTTPMethod", "POST" );
expect( event.getHTTPMethod() ).toBe( "POST" );
}

function testGetHTTPMethodPostGetOverrideIgnored(){
// POST _method=GET should not be honored; result stays POST
var event = getRequestContext().$( "getOriginalHTTPMethod", "POST" );
event.setValue( "_method", "GET" );
expect( event.getHTTPMethod() ).toBe( "POST" );
}

}
Loading