PerlDancer / PerlDancer/Dancer2
Query-string parser truncates values at the second '=' (base64/JWT corruption)
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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