PerlDancer / PerlDancer/Dancer2

Query-string parser truncates values at the second '=' (base64/JWT corruption)

Open Beginner friendly
#1,806 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug
Dominant language
Perl
Stars
604
Forks
288
Avg merge
1d 5h
Merged PRs (30d)
5

Description

Severity: medium as a security issue, high as a correctness bug · Status: confirmed against main @ 21bc21d9

lib/Dancer2/Core/Request.pm:553-554:

foreach my $token ( split /[&;]/, $source ) {
    my ( $key, $val ) = split( /=/, $token );      # <-- no limit

split without a limit discards everything after the second =. Every base64-padded value silently loses its padding — JWTs, OAuth state and code, HMAC signatures, signed redirect targets.

This path backs the classic params DSL keyword, and only runs when the optional CGI::Deurl::XS is absent — so behaviour differs between machines depending on what happens to be installed.

Reproduction

perl -Ilib -e 'use Dancer2::Core::Request;
  $Dancer2::Core::Request::XS_PARSE_QUERY_STRING = 0;
  my $r = Dancer2::Core::Request->new(env => { REQUEST_METHOD=>"GET", PATH_INFO=>"/",
    QUERY_STRING=>"state=YWJjZA==", "psgi.url_scheme"=>"http", SERVER_NAME=>"l", SERVER_PORT=>80 });
  print "params: [", $r->params->{state}, "]  query_parameters: [",
        $r->query_parameters->{state}, "]\n"'
params: [YWJjZA]  query_parameters: [YWJjZA==]

Gotcha when reproducing: the flag must be set after the module loads. Dancer2::Core::Request initialises it at load time (line 55), so assigning it before a require gets silently overwritten back to 1 and the bug appears not to reproduce. Use use followed by a runtime assignment, as above.

Why it is also a security issue

params and query_parameters are backed by different parsers and disagree about the same request. That is a parser differential: a validation check written against one API can be bypassed by code that reads the other.

Suggested fix

One character:

my ( $key, $val ) = split( /=/, $token, 2 );

Do NOT change the separator set

An earlier pass suspected [&;] was also divergent. It is not — verified directly:

QUERY_STRING = "state=YWJjZA==&x=1;y=2"

CGI::Deurl::XS: state=[YWJjZA==], x=[1], y=[2]
Plack         : state=[YWJjZA==], x=[1], y=[2]

All three parsers split on ; and all three preserve ==. The only divergence is the missing split limit. Changing the separator set would be a gratuitous behaviour change and would break apps relying on ;.

Suggested test

?state=YWJjZA== yields identical values via params('query'), query_parameters, and with $XS_PARSE_QUERY_STRING forced both on and off.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lib/Dancer2/Core/Request.pm at lines 553-554 and review the params parsing path, then run the provided Perl reproduction with XS parsing disabled. Add regression coverage for the suggested state value through params('query') and query_parameters, with XS parsing both enabled and disabled. Done means all paths preserve YWJjZA== and existing semicolon separators remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
perl
Domain
backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
92/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.