A number of days in the past, I did not know what a CSV Injection Assault was. I really like producing CSV (Comma Separated Worth) recordsdata in ColdFusion. And, heretofore, I had at all times considered CSV recordsdata as containing nothing greater than inert textual content knowledge. On Wednesday, nevertheless, David Epler – one among our senior safety engineers – acquired the outcomes of our ongoing Penetration Take a look at (PenTest); and, lo-and-behold, one of many recognized vulnerabilities was “CSV Injection”. Since this was an unknown assault vector for me, I assume it’s also an unknown difficulty for a few of you. As such, I needed to take a look at remediating CSV Injection assaults in ColdFusion.
As with the entire OWASP (Open Internet Utility Safety Venture) Injection assaults, CSV Injection permits uncooked, user-provided knowledge to set off unintended actions within the goal system. With CSV Injection, the unintended motion is the analysis of a Method within the context of spreadsheet program (reminiscent of MS Excel, Google Sheets, Numbers) on the sufferer’s pc.
For instance, if a CSV knowledge file incorporates the encoded subject:
"=3+2"
… the presence of the equal signal (=
) as the primary character might trigger the given spreadsheet program to judge the cell content material as a math equation, yielding the sum, 5
.
Maths – whereas difficult for my Caveman mind – is not in-and-of-itself a menace. However, as George Mauer factors out in his article on CSV Injection, some spreadsheet packages have formulation that may execute low-level instructions and even make HTTP requests to distant servers. Yikes!
The remediation of CSV Injection assaults beneficial by OWASP is to:
- Quote fields.
- Escape embedded quotes.
- Prepend the sphere worth with a single-quote (
'
) if the sphere begins with one of many malicious characters that may set off a formulation analysis:- Equals (
=
) - Plus (
+
) - Minus (
-
) - At (
@
) - Tab (
0x09
) - Carriage return (
0x0D
)
- Equals (
Since I really like writing ColdFusion elements, my remediation for the CSV Injection outlined within the PenTest was to create a part that centralizes the logic for serializing Array knowledge into CSV knowledge. It solely has two public strategies:
serializeArray( rows )
serializeArrayAsBinary( rows )
The second methodology is only a comfort methodology since I am nearly at all times taking the generated CSV knowledge and piping it into the variable
attribute of the CFContent
tag. This part builds on my earlier put up, celebrating the fun of CSV in ColdFusion:
part
output = false
trace = "I present helper strategies for SAFELY serializing Array knowledge as CSV content material."
{
// These are used internally, however may also be used externally as properly so as to make
// the calling code extra apparent (seeing names is simpler than seeing ASCII numbers).
this.chars = {
COMMA: ",",
TAB: chr( 9 ),
NEWLINE: chr( 10 ),
CARRIAGE_RETURN: chr( 13 ),
QUOTES: """",
ESCAPED_QUOTES: """"""
};
/**
* I initialize the CSV serializer with the given defaults.
*/
public void operate init(
string fieldDelimiter = this.chars.COMMA,
string rowDelimiter = this.chars.NEWLINE,
string encoding = "utf-8"
) {
variables.defaultFieldDelimiter = fieldDelimiter;
variables.defaultRowDelimiter = rowDelimiter;
variables.defaultEncoding = encoding;
}
// ---
// PUBLIC METHODS.
// ---
/**
* I serialize the given array knowledge as a CSV string payload.
*/
public string operate serializeArray(
required array rows,
string fieldDelimiter = defaultFieldDelimiter,
string rowDelimiter = defaultRowDelimiter
) {
var csvData = rows
.map(
( row ) => {
return( this.serializeArrayRow( row, fieldDelimiter ) );
}
)
.toList( rowDelimiter )
;
return( csvData );
}
/**
* I serialize the given array knowledge as a CSV binary payload.
*
* NOTE: This methodology is offered for comfort - once I generate CSV content material, I'm
* ALMOST ALWAYS then streaming the content material again to the consumer utilizing CFContent, which
* accepts a binary `variable` attribute.
*/
public string operate serializeArrayAsBinary(
required array rows,
string fieldDelimiter = defaultFieldDelimiter,
string rowDelimiter = defaultRowDelimiter,
string encoding = defaultEncoding
) {
return(
charsetDecode(
serializeArray( rows, fieldDelimiter, rowDelimiter ),
encoding
)
);
}
// ---
// PRIVATE METHODS.
// ---
/**
* I return a price through which any KNOWN potential CSV injection vector has been escaped.
*
* CAUTION MODIFIES DATA OUTPUT: This works by checking the primary character within the
* subject; and, if it's a doubtlessly harmful character, it prepends the sphere worth
* with a single-quote. Some spreadsheet packages will disguise this single-quote; others
* will render it. As such, it might seem to the top consumer that we have altered their knowledge
* (which now we have). Sadly, there is no means round this.
*/
non-public string operate escapeCsvInjection( required string subject ) {
change ( subject.left( 1 ) ) ":
return( "'" & subject );
break;
default:
return( subject );
break;
}
/**
* I return a subject worth that's quoted and through which embedded particular characters have
* been escaped.
*/
non-public string operate escapeField( required string subject ) {
return(
this.chars.QUOTES &
subject.exchange( this.chars.QUOTES, this.chars.ESCAPED_QUOTES, "all" ) &
this.chars.QUOTES
);
}
/**
* I serialize the given row, each QUOTING and ESCAPING the content material of every subject.
*/
non-public string operate serializeArrayRow(
required array row,
required string fieldDelimiter
) {
var csvData = row
.map(
( subject ) => {
// NOTE: Calling the toString() methodology to solid every subject to a string
// in case we're coping with non-string easy values. This enables
// us to name member-methods in the entire subsequent invocations.
var escapedValue = toString( subject );
escapedValue = escapeCsvInjection( escapedValue );
escapedValue = escapeField( escapedValue );
return( escapedValue );
}
)
.toList( fieldDelimiter )
;
return( csvData );
}
}
The basis of the answer right here is that each subject worth is handed via these three strategies:
toString()
escapeCsvInjection()
escapeField()
What I find yourself with is fields that present up trying like this:
=cmd | Sarah "Stubs" Smith
… and find yourself getting serialized like this:
"'=cmd | Sarah ""Stubs"" Smith"
Now, let’s attempt to generate a CSV file in ColdFusion that incorporates malicious content material. Discover that I am utilizing the comfort methodology, serializeArrayAsBinary()
, to pipe the info on to the CFContent
tag. This resets the output buffer and halts all processing on the server:
NOTE: This code is working in Lucee CFML. As such, it doesn’t want the
cf
prefixes on the CFScript-based tags.
<cfscript>
serializer = new CsvSerializer();
rows = [
[ "ID", "NAME", "EMAIL" ],
[ 1, "Sarah ""Stubs"" Smith", "sarah.smith@example.com" ],
[ 2, "John Johnson", "jon.johnson@example.com" ],
[ 3, "=(3+5)", "#chr( 9 )#dr.evil@example.com" ], // <== CAUTION: Malicious knowledge!
[ 4, "Jo Jamila", "jo.jamila@example.com" ]
];
header
title = "content-disposition"
worth = getContentDisposition( "user-data.csv" )
;
content material
sort = "textual content/csv; charset=utf-8"
variable = serializer.serializeArrayAsBinary( rows )
;
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I get the content-disposition header worth for the given filename.
*/
non-public string operate getContentDisposition(
required string filename,
string disposition = "attachment"
) {
var encodedFilename = encodeForUrl( filename );
return( "#disposition#; filename=""#encodedFilename#""; filename*=UTF-8''#encodedFilename#" );
}
</cfscript>
If we then run this ColdFusion code, generate the CSV file, and import it into Google Sheets, we get the next output:

As you may see, the the malicious subject values have been neutralized with the single-quote prefix. Moreover, Google Sheets does not render the quote within the sheet output; however, when you look within the Method enter, you may see it.
I’m regularly impressed with how folks discover new and fascinating methods to misuse and abuse applied sciences. Fortunately, SQL Injection has lengthy since been all however eradicated from ColdFusion due to the CFQueryParam
tag. And, many types of persevered XSS (Cross-Aspect Scripting) assaults have been neutralized by the varied encodeForXYZ()
strategies. Maybe we have to introduce an encodeForCsv()
methodology?
Wish to use code from this put up?
Take a look at the license.