Forum Discussion
hooleylist
Jun 16, 2009Cirrostratus
That's an interesting concept.
You have some of the pieces in your example, but there are a few issues. Some or all of the payload may be available in HTTP_REQUEST using the HTTP::payload command, but it will only be what the client sent in the first packet. To ensure the full payload is collected, you should trigger HTTP payload collection using HTTP::collect. You can then replace the payload in the request event, HTTP_REQUEST_DATA, in order to rewrite the payload before the request is sent to the pool. The HTTP::payload replace command expects the original content length, so you can use 'HTTP::payload replace 0 [HTTP::payload length] $new_payload'.
Be careful with global variables. Any variable declared in RULE_INIT, with the global command or prefixed with :: will be global. Global variables are accessible (and changeable) on any TCP connection from any client. Your ::postVars array should be local to ensure the values aren't trampled by other clients. However, instead of looping through the parameter names and values, you can use a workaround with URI::query (Click here) to get the parameter values.
when RULE_INIT {
Hardcode an encryption key
set ::aes_key 7ef82a9b038c19d4a6b2c014d812
Credit Card parameter name
set ::cc_param_name "CCNumber"
Card Code Verification parameter name
set ::ccv_param_name "CCV"
Maximum collection size (in bytes)
set ::max_collect_size 1000000
Log debug messages? 1=yes, 0=no.
set ::cc_encrypt_debug 1
}
when HTTP_REQUEST {
Check if request is a POST
if {[HTTP::method] eq "POST"}{
If we're logging debug messages, save the client IP:port to make log lines shorter.
if {$::cc_encrypt_debug}{
set log_prefix "[IP::client_addr]:[TCP::client_port]"
log local0. "$log_prefix: New POST request to [HTTP::host][HTTP::uri]"
}
Trigger collection for up to 1MB of data
if {([HTTP::header exists "Content-Length"]) && ([HTTP::header "Content-Length"] <= $::max_collect_size)}{
set content_length [HTTP::header "Content-Length"]
} else {
set content_length $::max_collect_size
}
if { [info exists content_length] && $content_length > 0} {
HTTP::collect $content_length
if {$::cc_encrypt_debug}{log local0. "$log_prefix: Collecting $content_length bytes"}
}
}
}
when HTTP_REQUEST_DATA {
if {$::cc_encrypt_debug}{log local0. "$log_prefix: Original payload ([HTTP::payload length] bytes): [HTTP::payload]"}
Parse the Credit Card number and CCV parameter values from the payload using a URI::query
set cc_unencrypted [URI::query "?[HTTP::payload]" $::cc_param_name]
set ccv_unencrypted [URI::query "?[HTTP::payload]" $::ccv_param_name]
if {$::cc_encrypt_debug}{log local0. "$log_prefix: Parsed \$cc_unencrypted=$cc_unencrypted, \$ccv_unencrypted=$ccv_unencrypted"}
Replace the CC with the encrypted version if a CC parameter value was parsed
if {[string length $cc_unencrypted]}{
Replace the Credit Card number parameter name=value pair with the encrypted and encoded version.
We use the full param_nam=value for searching/replacing to reduce the chance of a false match
HTTP::payload replace 0 [HTTP::payload length] [regsub -all "${::cc_param_name}=${cc_unencrypted}" [HTTP::payload] "${::cc_param_name}=[URI::encode [b64encode [AES::encrypt $::aes_key $cc_unencrypted]]]"]
if {$::cc_encrypt_debug}{log local0. "$log_prefix: CC updated payload ([HTTP::payload length] bytes): [HTTP::payload]"}
}
Replace the CCV with the encrypted version if a CCV parameter value was parsed
if {[string length $ccv_unencrypted]}{
Replace the Card Code Verification parameter name=value pair with the encrypted and encoded version.
We use the full param_nam=value for searching/replacing to reduce the chance of a false match
HTTP::payload replace 0 [HTTP::payload length] [regsub -all "${::ccv_param_name}=${ccv_unencrypted}" [HTTP::payload] "${::ccv_param_name}=[URI::encode [b64encode [AES::encrypt $::aes_key $ccv_unencrypted]]]"]
if {$::cc_encrypt_debug}{log local0. "$log_prefix: CCV updated payload ([HTTP::payload length] bytes): [HTTP::payload]"}
}
}
I haven't test this fully but I think it shows the concepts for collecting the request payload and modifying the content with encrypted values.
To decrypt the response content you would want to add in some of the request modifications in your original rule (like removing the Accept-Encoding header, setting the HTTP version to 1.0, and other steps listed in the HTTP::collect example Click here). You would then need to write a regex which matches all the potential forms for the CC and CCV numbers and decrypt them.
With all that said, I think it would be more efficient to use a stream profile and stream iRule to encrypt/decrypt the CC and CCV values. The advantage of a stream profile and iRule versus collecting the payload is that there is less buffering of content. You can check the last example on the STREAM::expression wiki page (Click here) for an example which sets a stream expression and then modifies the matching string in the STREAM_MATCHED event. If you give this a shot and get stuck, reply with what you've tried and we can try to help.
Aaron