[Rt-commit] rt branch, 4.0-trunk, updated. rt-4.0.24-56-g27eda90
Shawn Moore
shawn at bestpractical.com
Wed Jul 12 10:26:54 EDT 2017
The branch, 4.0-trunk has been updated
via 27eda905a3df2379bb9b8ddbb0ccf2ead4749383 (commit)
via 94f391df9f714fd6466fac907643d7aa90902532 (commit)
via 358034bcbd6f2725915963e4d38acede402cb875 (commit)
via 5bea1fce0d936332ed913ad8ac7d2385644675cb (commit)
via f25c207ce9fb2e43429df59e79623f25dcde46a6 (commit)
via a394f9888e54577fa195724ea1b3d6985ac06e8e (commit)
from 12572858ebbdba79f7223186d379e02e1f1f6a71 (commit)
Summary of changes:
lib/RT/User.pm | 33 +++--
lib/RT/Util.pm | 66 +++++++++
t/api/constant_time_eq.t | 89 ++++++++++++
t/api/user.t | 41 +++++-
t/data/input/blns.txt | 362 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 582 insertions(+), 9 deletions(-)
create mode 100644 t/api/constant_time_eq.t
create mode 100644 t/data/input/blns.txt
- Log -----------------------------------------------------------------
commit a394f9888e54577fa195724ea1b3d6985ac06e8e
Author: Aaron Kondziela <aaron at bestpractical.com>
Date: Tue Jan 24 18:20:28 2017 -0500
Fix timing sidechannel vulnerability in password checking
"eq" operators for comparing against passwords are replaced by a new
RT::Util::constant_time_eq to resolve a timing sidechannel vulnerability.
This addresses CVE-2017-5361.
Fixes: T#161960
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index b089b7f..c5e0e54 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -82,6 +82,7 @@ use RT::Principals;
use RT::ACE;
use RT::Interface::Email;
use Text::Password::Pronounceable;
+use RT::Util;
sub _OverlayAccessible {
{
@@ -945,7 +946,10 @@ sub IsPassword {
# If it's a new-style (>= RT 4.0) password, it starts with a '!'
my (undef, $method, $salt, undef) = split /!/, $stored;
if ($method eq "sha512") {
- return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
+ return 0 unless RT::Util::constant_time_eq(
+ $self->_GeneratePassword_sha512($value, $salt),
+ $stored
+ );
} else {
$RT::Logger->warn("Unknown hash method $method");
return 0;
@@ -955,16 +959,28 @@ sub IsPassword {
my $hash = MIME::Base64::decode_base64($stored);
# Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
my $salt = substr($hash, 0, 4, "");
- return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26) eq $hash;
+ return 0 unless RT::Util::constant_time_eq(
+ substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26),
+ $hash
+ );
} elsif (length $stored == 32) {
# Hex nonsalted-md5
- return 0 unless Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)) eq $stored;
+ return 0 unless RT::Util::constant_time_eq(
+ Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)),
+ $stored
+ );
} elsif (length $stored == 22) {
# Base64 nonsalted-md5
- return 0 unless Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)) eq $stored;
+ return 0 unless RT::Util::constant_time_eq(
+ Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)),
+ $stored
+ );
} elsif (length $stored == 13) {
# crypt() output
- return 0 unless crypt(Encode::encode( "UTF-8", $value), $stored) eq $stored;
+ return 0 unless RT::Util::constant_time_eq(
+ crypt(Encode::encode( "UTF-8", $value), $stored),
+ $stored
+ );
} else {
$RT::Logger->warning("Unknown password form");
return 0;
@@ -1060,19 +1076,20 @@ sub GenerateAuthString {
=head3 ValidateAuthString
-Takes auth string and protected string. Returns true is protected string
+Takes auth string and protected string. Returns true if protected string
has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
=cut
sub ValidateAuthString {
my $self = shift;
- my $auth_string = shift;
+ my $auth_string_to_validate = shift;
my $protected = shift;
my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected );
+ my $valid_auth_string = substr(Digest::MD5::md5_hex($str),0,16);
- return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
+ return RT::Util::constant_time_eq( $auth_string_to_validate, $valid_auth_string );
}
=head2 SetDisabled
diff --git a/lib/RT/Util.pm b/lib/RT/Util.pm
index 8268b21..9fc24c5 100644
--- a/lib/RT/Util.pm
+++ b/lib/RT/Util.pm
@@ -54,6 +54,8 @@ use warnings;
use base 'Exporter';
our @EXPORT = qw/safe_run_child mime_recommended_filename/;
+use Encode qw/encode/;
+
sub safe_run_child (&) {
my $our_pid = $$;
@@ -150,6 +152,58 @@ sub assert_bytes {
}
+=head2 C<constant_time_eq($a, $b)>
+
+Compares two strings for equality in constant-time. Replacement for the C<eq>
+operator designed to avoid timing side-channel vulnerabilities. Returns zero
+or one.
+
+This is intended for use in cryptographic subsystems for comparing well-formed
+data such as hashes - not for direct use with user input or as a general
+replacement for the C<eq> operator.
+
+The two string arguments B<MUST> be of equal length. If the lengths differ,
+this function will call C<die()>, as proceeding with execution would create
+a timing vulnerability. Length is defined by characters, not bytes.
+
+This code has been tested to do what it claims. Do not change it without
+thorough statistical timing analysis to validate the changes.
+
+Added to resolve CVE-2017-5361
+
+For more on timing attacks, see this Wikipedia article:
+B<https://en.wikipedia.org/wiki/Timing_attack>
+
+=cut
+
+sub constant_time_eq {
+ my ($a, $b) = @_;
+
+ my $result = 0;
+
+ # generic error message avoids potential information leaks
+ my $generic_error = "Cannot compare values";
+ die $generic_error unless defined $a and defined $b;
+ die $generic_error unless length $a == length $b;
+ die $generic_error if ref($a) or ref($b);
+
+ for (my $i = 0; $i < length($a); $i++) {
+ my $a_char = substr($a, $i, 1);
+ my $b_char = substr($b, $i, 1);
+
+ # encode() is set to die on malformed
+ my @a_octets = unpack("C*", encode('UTF-8', $a_char, Encode::FB_CROAK));
+ my @b_octets = unpack("C*", encode('UTF-8', $b_char, Encode::FB_CROAK));
+ die $generic_error if (scalar @a_octets) != (scalar @b_octets);
+
+ for (my $j = 0; $j < scalar @a_octets; $j++) {
+ $result |= $a_octets[$j] ^ $b_octets[$j];
+ }
+ }
+ return 0 + not $result;
+}
+
+
RT::Base->_ImportOverlays();
1;
diff --git a/t/api/constant_time_eq.t b/t/api/constant_time_eq.t
new file mode 100644
index 0000000..4256dd2
--- /dev/null
+++ b/t/api/constant_time_eq.t
@@ -0,0 +1,81 @@
+use utf8;
+use warnings;
+use strict;
+use RT;
+use RT::Test tests => undef;
+use Test::Exception;
+
+use_ok "RT::Util";
+
+my ($a, $b) = (1, 2);
+
+# die-worthy error conditions
+_dies_ok (eval { RT::Util::constant_time_eq(undef, undef) }, 'Should die: both args undefined');
+_dies_ok (eval { RT::Util::constant_time_eq('', undef) }, 'Should die: first arg undefined');
+_dies_ok (eval { RT::Util::constant_time_eq(undef, '') }, 'Should die: second arg undefined');
+_dies_ok (eval { RT::Util::constant_time_eq('', 'a') }, 'Should die: empty string and non-empty string (different length)');
+_dies_ok (eval { RT::Util::constant_time_eq('a', '') }, 'Should die: empty string and non-empty string (different length)');
+_dies_ok (eval { RT::Util::constant_time_eq('abcde', 'abcdef') }, 'strings different lengths should die');
+_dies_ok (eval { RT::Util::constant_time_eq('ff', 'ff') }, 'string "ff" and ligature "ff" should die (different lengths)');
+_dies_ok (eval { RT::Util::constant_time_eq(\$a, '') }, 'First arg ref should die');
+_dies_ok (eval { RT::Util::constant_time_eq('', \$b) }, 'Second arg ref should die');
+_dies_ok (eval { RT::Util::constant_time_eq(\$a, \$a) }, 'Both args same ref should die');
+
+# Check unicode chars
+_lives_and (eval { RT::Util::constant_time_eq('', '') }, 1, 'both args empty strings is true');
+_lives_and (eval { RT::Util::constant_time_eq('a', 'a') }, 1, 'both args one-byte chars is true');
+_lives_and (eval { RT::Util::constant_time_eq('þ', 'þ') }, 1, 'both args two-byte utf8 chars is true');
+_dies_ok (eval { RT::Util::constant_time_eq('þ', 'þ') }, 'a two-byte utf8 char and its mojibake dies');
+_lives_and (eval { RT::Util::constant_time_eq('þ', 'ÿ') }, 0, 'two-byte utf8 chars which differ by one bit');
+_lives_and (eval { RT::Util::constant_time_eq('þ', '¾') }, 0, 'two-byte utf8 chars which differ by one bit');
+_lives_and (eval { RT::Util::constant_time_eq('þ', '¿') }, 0, 'two-byte utf8 chars which differ by two bits');
+_dies_ok (eval { RT::Util::constant_time_eq('þ', 'xx') }, 'two-byte utf8 and two one-byte chars is false');
+_lives_and (eval { RT::Util::constant_time_eq('試', '試') }, 1, 'both args three-byte utf8 chars is true');
+_dies_ok (eval { RT::Util::constant_time_eq('試', 'xxx') }, 'three-byte utf8 and three one-byte chars is false');
+_lives_and (eval { RT::Util::constant_time_eq('😎', '😎') }, 1, 'both args four-byte utf8 chars is true');
+_dies_ok (eval { RT::Util::constant_time_eq('😎', 'xxxx') }, 'four-byte utf8 and four one-byte chars is false');
+_lives_and (eval { RT::Util::constant_time_eq('😎✈️'x1024, '😎✈️'x1024) }, 1, 'both long strings of utf8 chars is true');
+
+# Longer strings
+_lives_and (eval { RT::Util::constant_time_eq('a'x4096, 'a'x4096) }, 1, 'both args equal long strings is true');
+_lives_and (eval { RT::Util::constant_time_eq('a'x4096 . 'c', 'a'x4096 . 'b') }, 0, 'both args unequal long strings is false');
+
+# Numeric values would be stringified before comparison. This should never
+# be used this way, but if so the behaviour should remain consistent.
+_lives_and (eval { RT::Util::constant_time_eq(0, 0) }, 1, 'both args equal zero ints is true');
+_lives_and (eval { RT::Util::constant_time_eq(123456789, 123456789) }, 1, 'both args equal long ints is true');
+_lives_and (eval { RT::Util::constant_time_eq(123.456, 123.456) }, 1, 'both args equal floats is true');
+_lives_and (eval { RT::Util::constant_time_eq(0, 1) }, 0, 'both args unequal ints is false');
+
+# Big List of Naughty Strings (https://github.com/minimaxir/big-list-of-naughty-strings)
+my $f;
+open ($f, '<', 't/data/input/blns.txt') or die "can't open blns.txt";
+my $line = 0;
+while(<$f>) {
+ $line++;
+ next if length($_) == 0;
+ next if $_ =~ /^#\w/;
+ my $string = $_;
+ _lives_and (eval { RT::Util::constant_time_eq($string, $string) }, 1, "Big List of Naughty String, blns.txt line $line");
+}
+close $f;
+
+# TODO: statistical analysis of the timing performance of RT::Util::constant_time_eq
+# This is probably very difficult to do and have work across myriad systems that could
+# end up running RT. This note serves to remind you to test manually if you change
+# the function.
+
+done_testing();
+
+# Helpers inspired by Test::Exception
+
+sub _lives_and {
+ my ($t, $r, $message) = @_;
+ is ($@, '', $message);
+ is ($t, $r, $message);
+}
+
+sub _dies_ok {
+ my ($t, $message) = @_;
+ isnt ($@, '', $message);
+}
diff --git a/t/api/user.t b/t/api/user.t
index e6b891f..e5f43fe 100644
--- a/t/api/user.t
+++ b/t/api/user.t
@@ -2,7 +2,7 @@
use strict;
use warnings;
use RT;
-use RT::Test tests => 111;
+use RT::Test tests => undef;
{
@@ -335,3 +335,42 @@ ok($rqv, "Revoked the right successfully - $rqm");
}
+{
+# Test the auth token mechanisms used for iCal feeds
+
+# Set up a test user
+my $user = RT::Test->load_or_create_user(Name => "AuthTokenTestUser");
+
+ok($user && $user->id, "Create AuthTokenTestUser");
+
+# Generate an auth token
+my $token = $user->GenerateAuthToken();
+
+ok($token, "GenerateAuthToken()");
+unlike($user->AuthToken, qr/[^0-9A-Fa-f]/, "GenerateAuthToken() returns hex digits");
+
+# Protect a string with an auth token
+my $protected_string = "The quick brown RT jumped over the lazy [redacted competitor's product]";
+my $auth_string = $user->GenerateAuthString($protected_string);
+
+ok($auth_string, "GenerateAuthString() returns a string");
+unlike($auth_string, qr/[^0-9A-Fa-f]/, "GenerateAuthString() returns hex digits");
+
+# Validate a string is protected by an auth token
+ok($user->ValidateAuthString($auth_string, $protected_string),
+ "ValidateAuthString() returns true when string is protected by token");
+
+my $bad_protected_string = "This string is not protected by the token";
+
+ok( ! $user->ValidateAuthString($auth_string, $bad_protected_string),
+ "ValidateAuthString() returns false with invalid string and valid token");
+
+my $bad_auth_string = "L04DD3ADB33FC0DE";
+ok( ! $user->ValidateAuthString($bad_auth_string, $protected_string),
+ "ValidateAuthString() returns false with valid string and invalid token");
+
+ok( ! $user->ValidateAuthString($bad_auth_string, $bad_protected_string),
+ "ValidateAuthString() returns false with invalid string and invalid token");
+}
+
+done_testing();
diff --git a/t/data/input/blns.txt b/t/data/input/blns.txt
new file mode 100644
index 0000000..90c0b29
--- /dev/null
+++ b/t/data/input/blns.txt
@@ -0,0 +1,362 @@
+# The MIT License (MIT)
+
+# Copyright (c) 2015 Max Woolf
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# See: https://github.com/minimaxir/big-list-of-naughty-strings
+
+# Trimmed down for use in RT
+
+# Reserved Strings
+#
+# Strings which may be used elsewhere in code
+
+undefined
+undef
+null
+NULL
+(null)
+nil
+NIL
+true
+false
+True
+False
+TRUE
+FALSE
+None
+hasOwnProperty
+\
+\\
+
+# Numeric Strings
+#
+# Strings which can be interpreted as numeric
+
+0
+1
+1.00
+$1.00
+1/2
+1E2
+1E02
+1E+02
+-1
+-1.00
+-$1.00
+-1/2
+-1E2
+-1E02
+-1E+02
+1/0
+0/0
+-2147483648/-1
+-9223372036854775808/-1
+-0
+-0.0
++0
++0.0
+0.00
+0..0
+.
+0.0.0
+0,00
+0,,0
+,
+0,0,0
+0.0/0
+1.0/0.0
+0.0/0.0
+1,0/0,0
+0,0/0,0
+--1
+-
+-.
+-,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+NaN
+Infinity
+-Infinity
+INF
+1#INF
+-1#IND
+1#QNAN
+1#SNAN
+1#IND
+0x0
+0xffffffff
+0xffffffffffffffff
+0xabad1dea
+123456789012345678901234567890123456789
+1,000.00
+1 000.00
+1'000.00
+1,000,000.00
+1 000 000.00
+1'000'000.00
+1.000,00
+1 000,00
+1'000,00
+1.000.000,00
+1 000 000,00
+1'000'000,00
+01000
+08
+09
+2.2250738585072011e-308
+
+# Special Characters
+#
+# ASCII punctuation. All of these characters may need to be escaped in some
+# contexts. Divided into three groups based on (US-layout) keyboard position.
+
+,./;'[]\-=
+<>?:"{}|_+
+!@#$%^&*()`~
+
+# Non-whitespace C0 controls: U+0001 through U+0008, U+000E through U+001F,
+# and U+007F (DEL)
+# Often forbidden to appear in various text-based file formats (e.g. XML),
+# or reused for internal delimiters on the theory that they should never
+# appear in input.
+# The next line may appear to be blank or mojibake in some viewers.
+
+
+# Non-whitespace C1 controls: U+0080 through U+0084 and U+0086 through U+009F.
+# Commonly misinterpreted as additional graphic characters.
+# The next line may appear to be blank, mojibake, or dingbats in some viewers.
+
+
+# Whitespace: all of the characters with category Zs, Zl, or Zp (in Unicode
+# version 8.0.0), plus U+0009 (HT), U+000B (VT), U+000C (FF), U+0085 (NEL),
+# and U+200B (ZERO WIDTH SPACE), which are in the C categories but are often
+# treated as whitespace in some contexts.
+# This file unfortunately cannot express strings containing
+# U+0000, U+000A, or U+000D (NUL, LF, CR).
+# The next line may appear to be blank or mojibake in some viewers.
+# The next line may be flagged for "trailing whitespace" in some viewers.
+
+
+# Unicode additional control characters: all of the characters with
+# general category Cf (in Unicode 8.0.0).
+# The next line may appear to be blank or mojibake in some viewers.
+
+
+# "Byte order marks", U+FEFF and U+FFFE, each on its own line.
+# The next two lines may appear to be blank or mojibake in some viewers.
+
+
+
+# Unicode Symbols
+#
+# Strings which contain common unicode symbols (e.g. smart quotes)
+
+Ω≈ç√∫˜µ≤≥÷
+åß∂ƒ©˙∆˚¬…æ
+œ∑´®†¥¨ˆøπ“‘
+¡™£¢∞§¶•ªº–≠
+¸˛Ç◊ı˜Â¯˘¿
+ÅÍÎÏ˝ÓÔÒÚÆ☃
+Œ„´‰ˇÁ¨ˆØ∏”’
+`⁄€‹›fifl‡°·‚—±
+⅛⅜⅝⅞
+ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя
+٠١٢٣٤٥٦٧٨٩
+
+# Unicode Subscript/Superscript/Accents
+#
+# Strings which contain unicode subscripts/superscripts; can cause rendering issues
+
+⁰⁴⁵
+₀₁₂
+⁰⁴⁵₀₁₂
+ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็
+
+# Quotation Marks
+#
+# Strings which contain misplaced quotation marks; can cause encoding errors
+
+'
+"
+''
+""
+'"'
+"''''"'"
+"'"'"''''"
+<foo val=“bar” />
+<foo val=“bar” />
+<foo val=”bar“ />
+<foo val=`bar' />
+
+# Two-Byte Characters
+#
+# Strings which contain two-byte characters: can cause rendering issues or character-length issues
+
+田中さんにあげて下さい
+パーティーへ行かないか
+和製漢語
+部落格
+사회과학원 어학연구소
+찦차를 타고 온 펲시맨과 쑛다리 똠방각하
+社會科學院語學研究所
+울란바토르
+𠜎𠜱𠝹𠱓𠱸𠲖𠳏
+
+# Changing length when lowercased
+#
+# Characters which increase in length (2 to 3 bytes) when lowercased
+# Credit: https://twitter.com/jifa/status/625776454479970304
+
+Ⱥ
+Ⱦ
+
+# Japanese Emoticons
+#
+# Strings which consists of Japanese-style emoticons which are popular on the web
+
+ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ
+(。◕ ∀ ◕。)
+`ィ(´∀`∩
+__ロ(,_,*)
+・( ̄∀ ̄)・:*:
+゚・✿ヾ╲(。◕‿◕。)╱✿・゚
+,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’
+(╯°□°)╯︵ ┻━┻)
+(ノಥ益ಥ)ノ ┻━┻
+┬─┬ノ( º _ ºノ)
+( ͡° ͜ʖ ͡°)
+
+# Emoji
+#
+# Strings which contain Emoji; should be the same behavior as two-byte characters, but not always
+
+😍
+👩🏽
+👾 🙇 💁 🙅 🙆 🙋 🙎 🙍
+🐵 🙈 🙉 🙊
+❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙
+✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿
+🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧
+0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟
+
+# Regional Indicator Symbols
+#
+# Regional Indicator Symbols can be displayed differently across
+# fonts, and have a number of special behaviors
+
+🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸
+🇺🇸🇷🇺🇸🇦🇫🇦🇲
+🇺🇸🇷🇺🇸🇦
+
+# Unicode Numbers
+#
+# Strings which contain unicode numbers; if the code is localized, it should see the input as numeric
+
+123
+١٢٣
+
+# Right-To-Left Strings
+#
+# Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew)
+
+ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.
+בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ
+הָיְתָהtestالصفحات التّحول
+﷽
+ﷺ
+مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،
+
+# Trick Unicode
+#
+# Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf)
+
+test
+test
+
test
+testtest
+test
+
+# Zalgo Text
+#
+# Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net)
+
+Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣
+̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰
+̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟
+̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕
+Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮
+
+# Unicode Upsidedown
+#
+# Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com)
+
+˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥
+00˙Ɩ$-
+
+# Unicode font
+#
+# Strings which contain bold/italic/etc. versions of normal characters
+
+The quick brown fox jumps over the lazy dog
+𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠
+𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌
+𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈
+𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰
+𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘
+𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐
+⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢
+
+# Unwanted Interpolation
+#
+# Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string.
+
+$HOME
+$ENV{'HOME'}
+%d
+%s
+{0}
+%*.*s
+File:///
+
+# File Inclusion
+#
+# Strings which can cause user to pull in files that should not be a part of a web server
+
+../../../../../../../../../../../etc/passwd%00
+../../../../../../../../../../../etc/hosts
+
+# MSDOS/Windows Special Filenames
+#
+# Strings which are reserved characters in MSDOS/Windows
+
+CON
+PRN
+AUX
+CLOCK$
+NUL
+A:
+ZZ:
+COM1
+LPT1
+LPT2
+LPT3
+COM2
+COM3
+COM4
commit f25c207ce9fb2e43429df59e79623f25dcde46a6
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Mon Jul 10 16:02:49 2017 +0000
Tests for constant_time_eq which mirror the SHA256 failure mode
The encode_utf8 tests match the SHA256 branch of IsPassword failure
mode: it provides two bytestrings of the same length (in the case of
this test, 2 (0xC2 0xBC) and 2 (0x61 0x61), but which end up as
characters strings of different length after UTF8 decoding (in this
test, 1 ("¼") and 2 ("aa") respectively).
diff --git a/t/api/constant_time_eq.t b/t/api/constant_time_eq.t
index 4256dd2..61d471b 100644
--- a/t/api/constant_time_eq.t
+++ b/t/api/constant_time_eq.t
@@ -4,6 +4,7 @@ use strict;
use RT;
use RT::Test tests => undef;
use Test::Exception;
+use Encode;
use_ok "RT::Util";
@@ -36,6 +37,14 @@ _lives_and (eval { RT::Util::constant_time_eq('😎', '😎') }, 1, 'both args
_dies_ok (eval { RT::Util::constant_time_eq('😎', 'xxxx') }, 'four-byte utf8 and four one-byte chars is false');
_lives_and (eval { RT::Util::constant_time_eq('😎✈️'x1024, '😎✈️'x1024) }, 1, 'both long strings of utf8 chars is true');
+diag 'Binary strings of same length but different length after UTF-8 decode';
+_dies_ok (eval { RT::Util::constant_time_eq('¼', 'a') }, 'Dies with no binary set');
+_dies_ok (eval { RT::Util::constant_time_eq(encode_utf8('¼'), 'aa') }, 'Dies with no binary set');
+_dies_ok (eval { RT::Util::constant_time_eq('¼', 'a', 0) }, 'Dies with binary set to false');
+_dies_ok (eval { RT::Util::constant_time_eq(encode_utf8('¼'), 'aa', 0) }, 'Dies with binary set to false');
+_lives_and (eval { RT::Util::constant_time_eq('¼', 'a', 1) }, 0, 'Lives with false result with binary set to true');
+_lives_and (eval { RT::Util::constant_time_eq(encode_utf8('¼'), 'aa', 1) }, 0, 'Lives with false result with binary set to true');
+
# Longer strings
_lives_and (eval { RT::Util::constant_time_eq('a'x4096, 'a'x4096) }, 1, 'both args equal long strings is true');
_lives_and (eval { RT::Util::constant_time_eq('a'x4096 . 'c', 'a'x4096 . 'b') }, 0, 'both args unequal long strings is false');
commit 5bea1fce0d936332ed913ad8ac7d2385644675cb
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Mon Jul 10 11:48:28 2017 -0400
Add a "binary" option to opt out of UTF8 encoding
The SHA256 branch of IsPassword generates binary values to compare,
which may lead to comparing two strings with a different number of
Unicode characters, even when both strings have 26 octets (since UTF8 is
a variable-length encoding). This triggers an error in constant_time_eq
which demands both strings are the same length.
When comparing binary values pass this flag to avoid treating the
inputs as UTF8.
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index c5e0e54..969c464 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -961,7 +961,7 @@ sub IsPassword {
my $salt = substr($hash, 0, 4, "");
return 0 unless RT::Util::constant_time_eq(
substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26),
- $hash
+ $hash, 1
);
} elsif (length $stored == 32) {
# Hex nonsalted-md5
diff --git a/lib/RT/Util.pm b/lib/RT/Util.pm
index 9fc24c5..0399208 100644
--- a/lib/RT/Util.pm
+++ b/lib/RT/Util.pm
@@ -166,6 +166,9 @@ The two string arguments B<MUST> be of equal length. If the lengths differ,
this function will call C<die()>, as proceeding with execution would create
a timing vulnerability. Length is defined by characters, not bytes.
+Strings that should be treated as binary octets rather than Unicode text
+should pass a true value for the binary flag.
+
This code has been tested to do what it claims. Do not change it without
thorough statistical timing analysis to validate the changes.
@@ -177,7 +180,7 @@ B<https://en.wikipedia.org/wiki/Timing_attack>
=cut
sub constant_time_eq {
- my ($a, $b) = @_;
+ my ($a, $b, $binary) = @_;
my $result = 0;
@@ -191,9 +194,18 @@ sub constant_time_eq {
my $a_char = substr($a, $i, 1);
my $b_char = substr($b, $i, 1);
- # encode() is set to die on malformed
- my @a_octets = unpack("C*", encode('UTF-8', $a_char, Encode::FB_CROAK));
- my @b_octets = unpack("C*", encode('UTF-8', $b_char, Encode::FB_CROAK));
+ my (@a_octets, @b_octets);
+
+ if ($binary) {
+ @a_octets = ord($a_char);
+ @b_octets = ord($b_char);
+ }
+ else {
+ # encode() is set to die on malformed
+ @a_octets = unpack("C*", encode('UTF-8', $a_char, Encode::FB_CROAK));
+ @b_octets = unpack("C*", encode('UTF-8', $b_char, Encode::FB_CROAK));
+ }
+
die $generic_error if (scalar @a_octets) != (scalar @b_octets);
for (my $j = 0; $j < scalar @a_octets; $j++) {
commit 358034bcbd6f2725915963e4d38acede402cb875
Author: Jim Brandt <jbrandt at bestpractical.com>
Date: Mon Jun 26 16:18:26 2017 -0400
Assign correct parameter to message for output
diff --git a/t/api/constant_time_eq.t b/t/api/constant_time_eq.t
index 61d471b..d82bc35 100644
--- a/t/api/constant_time_eq.t
+++ b/t/api/constant_time_eq.t
@@ -85,6 +85,6 @@ sub _lives_and {
}
sub _dies_ok {
- my ($t, $message) = @_;
+ my ($message) = @_;
isnt ($@, '', $message);
}
commit 94f391df9f714fd6466fac907643d7aa90902532
Author: Jim Brandt <jbrandt at bestpractical.com>
Date: Mon Jun 26 16:19:14 2017 -0400
Remove unused Test::Exception
diff --git a/t/api/constant_time_eq.t b/t/api/constant_time_eq.t
index d82bc35..facba5f 100644
--- a/t/api/constant_time_eq.t
+++ b/t/api/constant_time_eq.t
@@ -3,7 +3,6 @@ use warnings;
use strict;
use RT;
use RT::Test tests => undef;
-use Test::Exception;
use Encode;
use_ok "RT::Util";
commit 27eda905a3df2379bb9b8ddbb0ccf2ead4749383
Merge: 1257285 94f391d
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Wed Jul 12 14:26:34 2017 +0000
Merge branch '4.0/compare-old-passwords' into 4.0-trunk
-----------------------------------------------------------------------
More information about the rt-commit
mailing list