hoyt has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I have a webservice that's taking in parameters via CGI. If one of those parameters has a special character (& for example), it gets sent in as %26. If I immediately print out the contents of param('data'), it prints to the screen with &, not %26. This is causing me a problem because I am calculating an hmac_sha512_hex value to verify that the input is getting received as sent. On the JavaScript side I set each parameter with encodeURIComponent(), which is how it's getting %26, then I read all of those to create the hash. On the Perl side, it's calculating it with the &, instead of %26. Is there a way to access the true raw input? I only need it to validate this one part - that the hash_hmac from PHP matches the hmac_sha512_hex from Perl. Here's the basic of the Perl script for this portion:
my $raw_data = uri_unescape $query->param('data'); my $api_key = uri_unescape $query->param('api_key'); $sth = $user_dbh->prepare($get_secret_key); $sth->execute($user_id); my ($secret_key) = $sth->fetchrow_array() and $sth->finish(); ... if($api_key eq hmac_sha512_hex($raw_data.$xyz.$abc,$secret_key)) { $auth_services{$service}->(); }else{ $status_ref->{'status'} = "FAIL"; $status_ref->{'status_cd'} = "F"; $status_ref->{'message'} = "Login details unable t +o be confirmed"; $status_ref->{'response_cd'} = "401"; }
Thanks!

Replies are listed 'Best First'.
Re: CGI Input with Escaped Characters
by dsheroh (Monsignor) on Apr 02, 2017 at 06:26 UTC
    Do you have any control over the sending javascript code? If so, then the obvious solution would be to calculate the hash before URI-encoding the data instead of after. If you're really going to be paranoid about making sure the data is transferred correctly, doing it that way would be better anyhow, since it would serve to validate that the data was encoded and decoded correctly, not only that it made it across the wire intact.
      Good point about making sure it gets decoded properly. I do have control over the JS in this instance and started to do that (changing the hash to calculate pre-encoding), but I stopped for three reasons. 1) I encode the string in a JS function piece by piece, then calculate the hash once it's all pieced together. So I'd need to piece together a non-encoded string as well, then hash that. It's a lot of work, and is in a handful of files (pure laziness). JS code snippet below. 2) I'm not sure I'll always have control over the input like that. 3) I was hoping to handle it with Perl for ease.
      var jData = {request:{'service':'ins_task_queue'},data:[]}; $('[name=remote_download_id]:checked').each(function(){ remote_download_id = $(this).val(); var here = $(this).siblings(); var tmpObj = {}; tmpObj['remote_download_id'] = remote_download_id; $.each($(this).siblings(),function(k,v){ --> tmpObj[here[k].name] = encodeURIComponent(here[k].value +); }); here = $(this).parent().siblings().children(':input'); $.each($(this).parent().siblings().children(':input'),function +(k,v){ --> tmpObj[here[k].name] = encodeURIComponent(here[k].value +); }); jData['data'].push(tmpObj); }); ...
      PHP parts:
      public function getRequest($request,$data,$response_type){ $reqData['request']['service'] = $request; $reqData['data'] = $data; $fields = array( 'data' => json_encode($reqData) , 'xyz' => $xyz , 'abc' => $abc ); $fields['api_key'] = getApiKey($fields); global $debug; $debug = $fields; return getSvc($fields); } function getApiKey($fields){ return hash_hmac('sha512',$fields['data'].$fields['abc'].$fields[' +xyz'],$GLOBALS['ses_secret_key']); }
      I'm still really struggling with this. I've tried to back the calculation up a few steps in the JS code, but that's leading to a ton of other problems for me, so I'm not sure that's going to work too well. I keep coming back to attempting to do this in Perl instead. I've boiled this down to a simple example:
      #!/usr/bin/perl -w use strict; use CGI; use URI::Escape; use JSON; use Digest::SHA qw(hmac_sha512_hex); my $query = CGI->new; my $raw_data = $query->param('data'); my $data = decode_json($raw_data); my $actual_data = '{"request":{"service":"test"},"data":{"test_input": +"%2B2"}}'; print "raw_data: ".$raw_data."\n"; print "no escaping: ".$data->{data}->{test_input}." vs escaping: "; print uri_escape($data->{data}->{test_input})."\n"; print hmac_sha512_hex($raw_data,"ABCD1234")."\n"; print hmac_sha512_hex($actual_data,"ABCD1234")."\n";
      If you run that on the command line like this: ./script.pl 'data={"request":{"service":"test"},"data":{"test_input":"%2B2"}}' You'll get this:
      raw_data: {"request":{"service":"test"},"data":{"test_input":"+2"}} no escaping: +2 vs escaping: %2B2 3c6de296682e7f3896073fe41af9732a294ef723bb1e5c75aa1eba1af981f04f0a0963 +d03604119ea92b719a2912ef0c957c03a7268b51e2170f8fed7c875465 32595bf215b309a73c8dd4d09600430378f455c7cb44d31573b08566ddff0a7bd3c536 +8d70696b57a2c1c95e862ed7b062501e39820bf973c9309812250df460
      I need the 32595... calculation to compare to the input (which I removed from the Perl example to make it shorter). I can't just unescape the whole string, because then it'll attempt to escape the part that makes it a JSON input ({, :, etc). It's as if the CGI input unescapes automatically and I can't figure out how to make it not unescape (or "re-escape"). Any ideas on how to do that before I write something myself to deal with escaped characters in a hmac sha? Thanks!

        Hi,

        You forgot to escape  {"request":{"service":"test"},"data":{"test_input":"%2B2"}}

        This is an error  CGI->new( 'data={"request":{"service":"test"},"data":{"test_input":"%2B2"}}' );

        You want this  CGI->new( 'data=%7B%22request%22%3A%7B%22service%22%3A%22test%22%7D%2C%22data%22%3A%7B%22test_input%22%3A%22%252B2%22%7D%7D')

        You want this  CGI->new( { data => $actual_data, } )

        Its like writing

        my $query = CGI->new({}); $query->param( 'data', $actual_data ); print $query->self_url;

        This is how query-string/form-data works

        Now if you were to use HTTP POST/PUT/PATCH you could use raw json in the request ; they call that AJAX

        { use LWP; my $req = HTTP::Request->new( POST => 'http://127.0.0.1:80/' ); $req->content_type( 'application/json' ); #~ $req->header('X-File-Name' => 'tiny.gif' ); $req->content_length( length $actual_data ); $req->content( $actual_data ); print "\n", $req->as_string, "\n"; } __END__ POST http://127.0.0.1:80/ Content-Length: 59 Content-Type: application/json {"request":{"service":"test"},"data":{"test_input":"%2B2"}}

        And to get at this ajax request from a .cgi you'd use

        use CGI 4.35; my $raw_data = CGI->new->param('POSTDATA'); # or PUTDATA or PATCHDATA

        An "fundvanced" example Mojolicious::Lite +and jQuery +AJAX + Mojo::Template

Re: CGI Input with Escaped Characters
by shmem (Chancellor) on Apr 02, 2017 at 09:17 UTC

    If it is a GET query, then the original data should be in $ENV{QUERY_STRING} iirc.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      That looks correct, and may work, but I try to keep the Perl code so that it'll work from command line too for troubleshooting. When I output the $ENV{} from command line, it doesn't show to that as an available key. Would that only work from a web posting action? Thanks!

        You can pass CGI parameters on the command, using data=foo api_key=1234. See the DEBUGGING section of CGI. But I doubt that CGI will shoehorn that back into %ENV.

        You could set the environment variable on the command line also

        $ env QUERY_STRING=data=foo&api_key=laurel%26hardy script.cgi

        or you could check inside your script if it is run from the commandline:

        if (-t STDIN) { # command line. for params, substitute special chars (e.g. & => %2 +6) # (there must be a module out there which does that for you) } else { # via webserver }
        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: CGI Input with Escaped Characters
by poj (Abbot) on Apr 02, 2017 at 07:19 UTC
    ..(& for example), it gets sent in as %26 ... it prints to the screen with &

    How are you seeing that it is being sent as %26 ?

    poj
      I echo out the string in a JS console in my browser when troubleshooting. If I take that whole string to an hmac generator site, I can tell that the hash matches with %26, and if I print it from the Perl command line, it matches the hash from an & sign.

        It seems like you need to convert & back to %26

        my $raw_data =  uri_escape $query->param('data'); # not uri_unescape
        
        poj