# This fixes OSA-2017-08, also known as CVE-2017-16854: An attacker who is
# logged into OTRS as a customer can use the ticket search form to disclose
# internal article information of their customer tickets.
# URL: https://www.otrs.com/security-advisory-2017-08-security-update-otrs-framework/

diff -Naur otrs2-5.0.16.orig/Kernel/Modules/CustomerTicketSearch.pm otrs2-5.0.16/Kernel/Modules/CustomerTicketSearch.pm
--- otrs2-5.0.16.orig/Kernel/Modules/CustomerTicketSearch.pm	2017-01-17 03:39:35.000000000 +0100
+++ otrs2-5.0.16/Kernel/Modules/CustomerTicketSearch.pm	2017-12-07 13:35:37.915211744 +0100
@@ -585,7 +585,12 @@
                     }
                     else
                     {
+                        ARTICLE:
                         for my $Articles (@Article) {
+
+                            # Skip internal articles.
+                            next ARTICLE if $Articles->{ArticleType} =~ /-int/;
+
                             if ( $Articles->{Body} ) {
                                 $Data{ArticleTree}
                                     .= "\n-->||$Articles->{ArticleType}||$Articles->{From}||"
@@ -724,10 +729,9 @@
             my @PDFData;
             for my $TicketID (@ViewableTicketIDs) {
 
-                # get first article data
-                my %Data = $TicketObject->ArticleLastCustomerArticle(
+                # Get last customer or any other article if it doesn't exist.
+                my %Data = $Self->_LastCustomerArticle(
                     TicketID      => $TicketID,
-                    Extended      => 1,
                     DynamicFields => 0,
                 );
 
@@ -1058,10 +1062,9 @@
                     )
                 {
 
-                    # get first article data
-                    my %Article = $TicketObject->ArticleLastCustomerArticle(
+                    # Get last customer or any other article if it doesn't exist.
+                    my %Article = $Self->_LastCustomerArticle(
                         TicketID      => $TicketID,
-                        Extended      => 1,
                         DynamicFields => 1,
                     );
 
@@ -1902,4 +1905,47 @@
     return %StopWordsServerErrors;
 }
 
+sub _LastCustomerArticle {
+    my ( $Self, %Param ) = @_;
+
+    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
+
+    # Get all customer articles.
+    my @Index = $TicketObject->ArticleIndex(
+        TicketID   => $Param{TicketID},
+        SenderType => 'customer',
+    );
+
+    # Go over articles in reverse order and return the last external one.
+    if (@Index) {
+        for my $CustomerArticleID ( reverse @Index ) {
+            my %LastCustomerArticle = $TicketObject->ArticleGet(
+                ArticleID     => $CustomerArticleID,
+                Extended      => 1,
+                DynamicFields => $Param{DynamicFields},
+            );
+            if ( $LastCustomerArticle{ArticleType} !~ /-int/ ) {
+                return %LastCustomerArticle;
+            }
+        }
+    }
+
+    # If no customer articles were found, return the last external one.
+    @Index = $TicketObject->ArticleIndex(
+        TicketID => $Param{TicketID},
+    );
+    for my $ArticleID ( reverse @Index ) {
+        my %LastArticle = $TicketObject->ArticleGet(
+            ArticleID     => $ArticleID,
+            Extended      => 1,
+            DynamicFields => $Param{DynamicFields},
+        );
+        if ( $LastArticle{StateType} eq 'merged' || $LastArticle{ArticleType} !~ /-int/ ) {
+            return %LastArticle;
+        }
+    }
+
+    return;
+}
+
 1;
diff -Naur otrs2-5.0.16.orig/scripts/test/Selenium/Customer/CustomerTicketSearch.t otrs2-5.0.16/scripts/test/Selenium/Customer/CustomerTicketSearch.t
--- otrs2-5.0.16.orig/scripts/test/Selenium/Customer/CustomerTicketSearch.t	2017-01-17 03:39:35.000000000 +0100
+++ otrs2-5.0.16/scripts/test/Selenium/Customer/CustomerTicketSearch.t	2017-12-07 13:35:37.915211744 +0100
@@ -85,6 +85,25 @@
             "Ticket ID $TicketID - created",
         );
 
+        # Add test article to the ticket.
+        #   Make it email-internal, with sender type customer, in order to check if it's filtered out correctly.
+        my $InternalArticleMessage = 'not for the customer';
+        my $ArticleID              = $TicketObject->ArticleCreate(
+            TicketID       => $TicketID,
+            ArticleType    => 'email-internal',
+            SenderType     => 'customer',
+            Subject        => $TitleRandom,
+            Body           => $InternalArticleMessage,
+            ContentType    => 'text/plain; charset=ISO-8859-15',
+            HistoryType    => 'EmailCustomer',
+            HistoryComment => 'Some free text!',
+            UserID         => 1,
+        );
+        $Self->True(
+            $ArticleID,
+            "Article is created - ID $ArticleID"
+        );
+
         # get test ticket number
         my %Ticket = $TicketObject->TicketGet(
             TicketID => $TicketID,
@@ -100,6 +119,12 @@
             "Ticket $TitleRandom found on page",
         );
 
+        # Check if internal article was not shown.
+        $Self->True(
+            index( $Selenium->get_page_source(), $InternalArticleMessage ) == -1,
+            'Internal article not found on page'
+        );
+
         # click on '← Change search options'
         $Selenium->find_element( "← Change search options", 'link_text' )->VerifiedClick();
 
