mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-11-03 19:08:50 +03:00
DoSMimeDecryptMessage and DoSMimeVerifyMessage built BODY.PEEK[<partId>] (and [<partId>.MIME]) from the partId / sigPartId parameters without checking them, so a value could close the bracket or the line and add IMAP commands of its own. They run in the user's own IMAP session, so this is not cross-account, but nothing in that session should be steerable by a request parameter. Read both through smimePartIdParam(), which accepts only an IMAP section number (1, 1.2.10) or TEXT and refuses anything else. test/smime-partid.php checks valid ids pass and injection attempts, zero or leading-zero sections, lowercase and other section keywords are refused.
37 lines
1.3 KiB
PHP
37 lines
1.3 KiB
PHP
<?php
|
|
|
|
// Run with: php test/smime-partid.php
|
|
|
|
// Enable SnappyMail Api and include index file
|
|
$_ENV['SNAPPYMAIL_INCLUDE_AS_API'] = true;
|
|
require __DIR__ . '/../index.php';
|
|
|
|
function check(bool $condition, string $message): void
|
|
{
|
|
if (!$condition) {
|
|
throw new RuntimeException($message);
|
|
}
|
|
}
|
|
|
|
$actions = (new ReflectionClass(\RainLoop\Actions::class))->newInstanceWithoutConstructor();
|
|
$params = new ReflectionProperty(\RainLoop\Actions::class, 'aCurrentActionParams');
|
|
$method = new ReflectionMethod(\RainLoop\Actions::class, 'smimePartIdParam');
|
|
$read = function (string $value) use ($actions, $params, $method) : ?string {
|
|
$params->setValue($actions, ['partId' => $value]);
|
|
try {
|
|
return $method->invoke($actions, 'partId');
|
|
} catch (\RainLoop\Exceptions\ClientException $e) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
foreach (['', '1', '2', '1.2', '1.2.10', 'TEXT'] as $ok) {
|
|
check($ok === $read($ok), "valid part id '{$ok}' refused");
|
|
}
|
|
foreach (["1]\r\nA1 LOGOUT\r\n", '1] BODY[HEADER', "1\r\nA1 LOGOUT", '1 2', '0', '1.0', '01', '1.', '.1', 'text', 'HEADER'] as $bad) {
|
|
check(null === $read($bad), 'part id '.json_encode($bad).' accepted');
|
|
}
|
|
// Surrounding whitespace is trimmed away, and the trimmed value is what is used.
|
|
check('1' === $read("1\n"), 'trailing newline not trimmed');
|
|
|
|
echo "smime-partid: ok\n";
|