[Rt-commit] rt branch, 4.0-trunk, updated. rt-4.0.13-37-ge3511b8

Thomas Sibley trs at bestpractical.com
Wed May 22 16:47:53 EDT 2013


The branch, 4.0-trunk has been updated
       via  e3511b8e05675c1f5fb097ad7a867fd1183c278c (commit)
       via  063e1f308ed0a2a2a099a1cc8687fc4a81f3a358 (commit)
      from  b8105f1750064fd5d52be2a4e4810bd471c7e443 (commit)

Summary of changes:
 t/security/CVE-2012-4730-email-header-injection.t  | 103 +++++++++++++++++++++
 t/security/CVE-2012-4731-create-article.t          |  53 +++++++++++
 t/security/CVE-2012-4732-csrf-blacklist.t          |  42 +++++++++
 t/security/CVE-2012-4734-login-warning.t           |  30 ++++++
 .../CVE-2012-4735-incoming-encryption-header.t     |  85 +++++++++++++++++
 t/security/CVE-2012-4735-sign-any-key.t            |  94 +++++++++++++++++++
 t/security/CVE-2012-4735-sign-encrypt-header.t     |  55 +++++++++++
 7 files changed, 462 insertions(+)
 create mode 100644 t/security/CVE-2012-4730-email-header-injection.t
 create mode 100644 t/security/CVE-2012-4731-create-article.t
 create mode 100644 t/security/CVE-2012-4732-csrf-blacklist.t
 create mode 100644 t/security/CVE-2012-4734-login-warning.t
 create mode 100644 t/security/CVE-2012-4735-incoming-encryption-header.t
 create mode 100644 t/security/CVE-2012-4735-sign-any-key.t
 create mode 100644 t/security/CVE-2012-4735-sign-encrypt-header.t

- Log -----------------------------------------------------------------
commit 063e1f308ed0a2a2a099a1cc8687fc4a81f3a358
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu May 2 15:38:11 2013 -0400

    Add security tests for vulnerabilities released 2012-10-25
    
    This includes tests for:
    
      CVE-2012-4730  Email header injection
      CVE-2012-4731  Create Article without proper rights
      CVE-2012-4732  CSRF blacklist evasion
      CVE-2012-4734  Confused deputy during login
      CVE-2012-4735  Improper signing or encryption of GnuPG messages

diff --git a/t/security/CVE-2012-4730-email-header-injection.t b/t/security/CVE-2012-4730-email-header-injection.t
new file mode 100644
index 0000000..893d111
--- /dev/null
+++ b/t/security/CVE-2012-4730-email-header-injection.t
@@ -0,0 +1,103 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+use Email::Abstract;
+
+# Multiple email addresses for a single user are supported in 3.8 and 4.0 via
+# commas, so we really only care if you can inject other headers.  It doesn't
+# much matter if you can inject other recipients via your user email address or
+# other values since you can do that in many visible and not-so-visible supported (!)
+# ways.
+# 
+#                        HEADER INJECTION VIA USER EMAIL ADDRESS
+# 
+#                                                   4.0-trunk   security/4.0/email-header-injection
+#                                                  -------------------------------------------------
+# Via recipient headers w/ RecordOutgoingEmail ON  | No       | No                                 |
+# Via recipient headers w/ RecordOutgoingEmail OFF | Yes      | No                                 |
+# Via RT-Originator                                | Yes      | No                                 |
+# Via other default headers                        | No       | No                                 |
+#                                                  -------------------------------------------------
+# 
+# With RecordOutgoingEmail ON, your recipient headers are filtered through
+# Email::Address, potentially mangling addresses if you've inserted newlines or
+# other funky chars that run together.
+
+my ($ok, $msg);
+
+# Create evil user
+my $user    = RT::User->new( RT->SystemUser );
+($ok, $msg) = $user->Create(
+    Name            => "eviluser",
+    EmailAddress    => "foo\@example.com\nEvil: yes\n\nMalicious",
+);
+ok $user->id, "Created user with evil email address: $msg";
+like $user->EmailAddress, qr/\nEvil: yes/, "Email address still evil";
+like $user->EmailAddress, qr/\n\nMalicious/, "Email address still malicious";
+
+($ok, $msg) = $user->PrincipalObj->GrantRight(
+    Right   => 'CreateTicket',
+    Object  => RT->System,
+);
+ok $ok, "Granted CreateTicket to evil user: $msg";
+
+note "To: header (any recipient header)";
+{
+    for my $record_outgoing (1, 0) {
+        RT->Config->Set( RecordOutgoingEmail => $record_outgoing );
+        note "RecordOutgoingEmail is " . ($record_outgoing ? "ON" : "OFF");
+
+        # Create ticket which will...
+        my $ticket  = RT::Ticket->new( RT->SystemUser );
+        ($ok, $msg) = $ticket->Create(
+            Queue       => 1,
+            Subject     => "test recipient ticket",
+            Requestor   => $user->PrincipalId,
+        );
+        ok $ticket->id, "Created ticket: $msg";
+
+        # ... send an autoreply to said user, putting their email address in the To: header
+        my @email = RT::Test->fetch_caught_mails;
+        is @email, 1, "Caught one email";
+
+        my $entity = Email::Abstract->new($email[0])->cast('MIME::Entity');
+        my $head = $entity->head;
+        like $head->get("Subject"), qr/autoreply/i, "Looks like autoreply";
+        like $head->get("To"), qr/foo\@example\.com/, "To: contains foo\@example.com";
+        ok !$head->get("Evil"), "No Evil header";
+        unlike $entity->stringify_body, qr/Malicious/, "No Malicious body";
+    }
+}
+
+note "RT-Originator header";
+{
+    for my $originator (1, 0) {
+        RT->Config->Set( UseOriginatorHeader => $originator );
+        note "UseOriginatorHeader is " . ($originator ? "ON" : "OFF");
+
+        # Create ticket as evil user
+        my $ticket  = RT::Ticket->new( RT::CurrentUser->new($user) );
+        ($ok, $msg) = $ticket->Create(
+            Queue       => 1,
+            Subject     => "test originator ticket",
+            Requestor   => 'unsuspecting at example.com',  # provide any recipient
+        );
+        ok $ticket->id, "Created ticket: $msg";
+
+        # ... sends an email
+        my @email = RT::Test->fetch_caught_mails;
+        is @email, 1, "Caught one email";
+
+        my $entity = Email::Abstract->new($email[0])->cast('MIME::Entity');
+        my $head = $entity->head;
+        if ($originator) {
+            like $head->get("RT-Originator"), qr/foo\@example\.com/, "RT-Originator contains email";
+            like $head->get("RT-Originator"), qr/Evil: yes/, "Evil didn't leak out of RT-Originator";
+        }
+        ok !$head->get("Evil"), "No Evil header";
+        unlike $entity->stringify_body, qr/Malicious/, "No Malicious body";
+    }
+}
+
+done_testing;
diff --git a/t/security/CVE-2012-4731-create-article.t b/t/security/CVE-2012-4731-create-article.t
new file mode 100644
index 0000000..2797fbc
--- /dev/null
+++ b/t/security/CVE-2012-4731-create-article.t
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+my $user = RT::Test->load_or_create_user( Name => 'testuser' );
+ok $user->id, "Created user: ".$user->id;
+
+my $class = RT::Class->new( RT->SystemUser );
+my ($ok, $msg) = $class->Create( Name => 'recipes' );
+ok $ok, "Created class: $msg";
+
+ok(
+    RT::Test->set_rights(
+        Principal   => 'Privileged',
+        Right       => [qw/SeeClass/],
+        Object      => $class,
+    ),
+    "Granted SeeClass on the Class"
+);
+
+my $article = RT::Article->new( RT::CurrentUser->new($user) );
+($ok, $msg) = $article->Create( Class => 'recipes', Name => 'ricotta raspberry buttermilk scones' );
+ok !$ok, "Can't create article: $msg";
+
+ok(
+    RT::Test->add_rights(
+        Principal   => 'Privileged',
+        Right       => [qw/CreateArticle/],
+        Object      => $class,
+    ),
+    "Granted CreateArticle on the Class"
+);
+
+($ok, $msg) = $article->Create( Class => 'recipes', Name => 'ricotta raspberry buttermilk scones' );
+ok $ok, "Created article: $msg";
+
+($ok, $msg) = $article->SetSummary("a tasty morning treat");
+ok !$ok, "Can't update Summary: $msg";
+
+ok(
+    RT::Test->add_rights(
+        Principal   => 'Privileged',
+        Right       => [qw/ModifyArticle/],
+        Object      => $class,
+    ),
+    "Granted ModifyArticle on the Class"
+);
+
+($ok, $msg) = $article->SetSummary("a tasty morning treat");
+ok $ok, "Updated Summary: $msg";
+
+done_testing;
diff --git a/t/security/CVE-2012-4732-csrf-blacklist.t b/t/security/CVE-2012-4732-csrf-blacklist.t
new file mode 100644
index 0000000..b20da3b
--- /dev/null
+++ b/t/security/CVE-2012-4732-csrf-blacklist.t
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+my $ticket = RT::Ticket->new(RT::CurrentUser->new('root'));
+my ($ok, $msg) = $ticket->Create(Queue => 1, Owner => 'nobody', Subject => 'bad music');
+ok($ok);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $test_page = "/Helpers/Toggle/TicketBookmark?id=1";
+my $test_path = "/Helpers/Toggle/TicketBookmark";
+
+ok $m->login, 'logged in';
+
+# valid referer
+$m->add_header(Referer => $baseurl);
+$m->get_ok($test_page);
+$m->content_lacks("Possible cross-site request forgery");
+$m->content_contains("star.gif");
+
+# come from an external source
+$m->add_header(Referer => 'http://example.com');
+$m->get_ok($test_page);
+$m->content_contains("Possible cross-site request forgery");
+$m->title_is('Possible cross-site request forgery');
+
+# come with no referer
+$m->add_header(Referer => undef);
+$m->get_ok($test_page);
+$m->content_contains("Possible cross-site request forgery");
+$m->title_is('Possible cross-site request forgery');
+
+# clicking the resume request button gets us to the test page
+$m->follow_link(text_regex => qr{resume your request});
+$m->content_lacks("Possible cross-site request forgery");
+like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$});
+$m->content_contains("star.gif");
+
+undef $m;
+done_testing;
diff --git a/t/security/CVE-2012-4734-login-warning.t b/t/security/CVE-2012-4734-login-warning.t
new file mode 100644
index 0000000..3dd21cd
--- /dev/null
+++ b/t/security/CVE-2012-4734-login-warning.t
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+my $ticket = RT::Ticket->new(RT::CurrentUser->new('root'));
+my ($ok, $msg) = $ticket->Create(Queue => 1, Owner => 'nobody', Subject => 'bad music');
+ok($ok);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $test_page = "/Ticket/Display.html?id=$ok;Action=Take";
+
+$m->get_ok($test_page);
+$m->content_contains("update a ticket");
+ok($m->form_name('login'),"Found the login form");
+$m->submit_form(
+    form_name => 'login',
+    fields    => {
+        user => 'root',
+        pass => 'password'
+    }
+);
+$m->content_contains('Owner changed from Nobody to root');
+
+$ticket->Load($ticket->Id);
+is($ticket->OwnerObj->Name,'root',"Ticket was assigned to root");
+
+undef $m;
+done_testing;
diff --git a/t/security/CVE-2012-4735-incoming-encryption-header.t b/t/security/CVE-2012-4735-incoming-encryption-header.t
new file mode 100644
index 0000000..d58c1c5
--- /dev/null
+++ b/t/security/CVE-2012-4735-incoming-encryption-header.t
@@ -0,0 +1,85 @@
+use strict;
+use warnings;
+
+use RT::Test::GnuPG tests => undef;
+use Test::Warn;
+
+{
+    my $mail = <<EOF;
+From: root\@localhost
+Subject: a ticket
+X-RT-Incoming-Encryption: Success
+X-RT-Incoming-Signature: Success
+
+This was _totally_ encrypted.
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($mail);
+    ok $id, "created a ticket";
+
+    my $ticket = RT::Ticket->new( RT->SystemUser );
+    $ticket->Load( $id );
+    ok $ticket->id, "loaded ticket";
+
+    my $txn = $ticket->Transactions->First;
+    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
+
+    like( $msg->Content, qr/This was _totally_ encrypted/, "Found the right attachment" );
+
+    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+        'Not encrypted',
+        'Incoming encryption header is removed'
+    );
+
+    is( $msg->GetHeader('X-RT-Incoming-Signature'),
+        undef,
+        'Incoming signature header is removed'
+    );
+}
+
+
+
+{
+    my $mail = <<EOF;
+From: root\@localhost
+Subject: a ticket
+X-RT-Incoming-Encryption: Success
+X-RT-Incoming-Signature: Success
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+This was _totally_ encrypted.
+-----END PGP MESSAGE-----
+EOF
+
+    my ($status, $id);
+    warnings_like {
+        ($status, $id) = RT::Test->send_via_mailgate($mail);
+        ok $id, "created a ticket";
+    } [qr/keyring .* created/,
+       qr/Had a problem during decrypting and verifying/,
+       qr/Couldn't process a message/,
+   ];
+
+    my $ticket = RT::Ticket->new( RT->SystemUser );
+    $ticket->Load( $id );
+    ok $ticket->id, "loaded ticket";
+
+    my $txn = $ticket->Transactions->First;
+    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
+
+    like( $msg->Content, qr/This was _totally_ encrypted/, "Found the right attachment" );
+
+    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+        undef,
+        'Incoming encryption header is removed'
+    );
+
+    is( $msg->GetHeader('X-RT-Incoming-Signature'),
+        undef,
+        'Incoming signature header is removed'
+    );
+}
+
+done_testing;
diff --git a/t/security/CVE-2012-4735-sign-any-key.t b/t/security/CVE-2012-4735-sign-any-key.t
new file mode 100644
index 0000000..290c7a8
--- /dev/null
+++ b/t/security/CVE-2012-4735-sign-any-key.t
@@ -0,0 +1,94 @@
+use strict;
+use warnings;
+
+use RT::Test::GnuPG
+  tests         => undef,
+  gnupg_options => {
+    passphrase    => 'rt-test',
+    'trust-model' => 'always',
+  };
+
+RT::Test->import_gnupg_key('rt-recipient at example.com');
+RT::Test->import_gnupg_key('general at example.com');
+
+# Determine the key IDs of the newly-loaded keys
+my %secret_keys;
+{
+    my %info = RT::Crypt::GnuPG::GetKeysInfo(undef, 'secret', 1);
+    for my $key (@{$info{info}}) {
+        my $user = $key->{User}[0]{String};
+        $user = (Email::Address->parse( $user ))[0]->address;
+        $secret_keys{$user} = $key->{Key};
+    }
+}
+
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'Signing',
+    CorrespondAddress => 'rt-recipient at example.com',
+    CommentAddress    => 'rt-recipient at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+sub create_signed {
+    my $key = shift;
+    diag( "Attempting to sign using $key" );
+
+    $m->goto_create_ticket( $queue->id );
+    $m->form_name('TicketCreate');
+    unless ($m->current_form->find_input("SignUsing")) {
+        my $content = $m->content;
+        $content =~ s!using Queue(?:'|')s key!using <input type="text" name="SignUsing" />!;
+        $m->update_html( $content );
+    }
+
+    $m->form_name('TicketCreate');
+    $m->field( Subject    => 'test' );
+    $m->field( Requestors => 'rt-test at example.com' );
+    $m->field( Content    => 'Some content' );
+    $m->tick( Sign => 1 );
+    $m->field( SignUsing  => $key );
+    $m->submit;
+}
+
+create_signed( '' );
+$m->content_lacks("unable to sign outgoing email messages");
+my @mail = RT::Test->fetch_caught_mails;
+is( scalar @mail, 1, "Got a mail" );
+like( $mail[0], qr/BEGIN PGP SIGNATURE/, "Is signed" );
+
+
+create_signed( 'rt-recipient at example.com' );
+$m->content_lacks("unable to sign outgoing email messages");
+ at mail = RT::Test->fetch_caught_mails;
+is( scalar @mail, 1, "Got a mail" );
+like( $mail[0], qr/BEGIN PGP SIGNATURE/, "Is signed" );
+
+
+create_signed( 'general at example.com' );
+$m->content_contains("unable to sign outgoing email messages");
+ at mail = RT::Test->fetch_caught_mails;
+is( scalar @mail, 0, "Sent no mail" );
+
+create_signed( $secret_keys{'general at example.com'} );
+$m->content_contains("unable to sign outgoing email messages");
+ at mail = RT::Test->fetch_caught_mails;
+is( scalar @mail, 0, "Sent no mail" );
+
+
+my $user = RT::User->new( RT->SystemUser );
+$user->Load( 'root' );
+my ($ok, $msg) = $user->SetPrivateKey( $secret_keys{'general at example.com'} );
+ok($ok, "Set private key to $secret_keys{'general at example.com'}: $msg" );
+
+
+create_signed( $secret_keys{'general at example.com'} );
+$m->content_lacks("unable to sign outgoing email messages");
+ at mail = RT::Test->fetch_caught_mails;
+is( scalar @mail, 1, "Sent a mail" );
+like( $mail[0], qr/BEGIN PGP SIGNATURE/, "Is signed" );
+
+undef $m;
+done_testing;
diff --git a/t/security/CVE-2012-4735-sign-encrypt-header.t b/t/security/CVE-2012-4735-sign-encrypt-header.t
new file mode 100644
index 0000000..4cf8e00
--- /dev/null
+++ b/t/security/CVE-2012-4735-sign-encrypt-header.t
@@ -0,0 +1,55 @@
+use strict;
+use warnings;
+
+use RT::Test::GnuPG
+  tests => undef,
+  gnupg_options => {
+    passphrase    => 'rt-test',
+    'trust-model' => 'always',
+  };
+
+RT::Test->import_gnupg_key('rt-recipient at example.com');
+RT::Test->import_gnupg_key( 'rt-test at example.com', 'public' );
+
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'GPG',
+    CorrespondAddress => 'rt-recipient at example.com',
+    CommentAddress    => 'rt-recipient at example.com',
+    Sign              => 0,
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+{
+    my $mail = <<EOF;
+From: root\@localhost
+Subject: a ticket
+
+That sounds like a great idea!
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($mail, queue => "GPG");
+    ok $id, "created a ticket";
+
+    my ($reply) = RT::Test->fetch_caught_mails;
+    ok( defined $reply, "Got a message" );
+    unlike( $reply, qr/BEGIN PGP SIGNATURE/, "It is not signed");
+}
+
+{
+    my $mail = <<EOF;
+From: root\@localhost
+Subject: a ticket
+X-RT-Sign: 1
+
+That sounds like a great idea!
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($mail, queue => "GPG");
+    ok $id, "created a ticket";
+
+    my ($reply) = RT::Test->fetch_caught_mails;
+    ok( defined $reply, "Got a message" );
+    unlike( $reply, qr/BEGIN PGP SIGNATURE/, "It is not signed");
+}
+
+done_testing;

commit e3511b8e05675c1f5fb097ad7a867fd1183c278c
Merge: b8105f1 063e1f3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed May 22 13:37:03 2013 -0700

    Merge branch 'security/4.0/tests' into 4.0-trunk


-----------------------------------------------------------------------


More information about the Rt-commit mailing list