mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-11-04 03:18:49 +03:00
@ZaphodB fix(@security): S/MIME actions put the client's part id into IMAP FETCH as is
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.
This commit is contained in:
parent
a67c85885e
commit
099ee3b97e
2 changed files with 56 additions and 4 deletions
37
test/smime-partid.php
Normal file
37
test/smime-partid.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?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";
|
||||
Loading…
Add table
Add a link
Reference in a new issue