Description: CVE-2014-9057: SQL injection vulnerability in the XML-RPC interface
Origin: vendor
Bug-Debian: https://bugs.debian.org/774192
Forwarded: not-needed
Author: Salvatore Bonaccorso <carnil@debian.org>
Last-Update: 2015-02-08
Applied-Upstream: 5.18

--- a/lib/MT/XMLRPCServer.pm
+++ b/lib/MT/XMLRPCServer.pm
@@ -76,6 +76,18 @@ BEGIN {
     $HAVE_XML_PARSER = $@ ? 0 : 1;
 }
 
+sub _validate_params {
+    my ($params) = @_;
+
+    foreach my $p (@$params) {
+        die _fault( MT->translate("Invalid parameter") )
+            if ( 'ARRAY' eq ref $p )
+            or ( 'HASH' eq ref $p );
+    }
+
+    return 1;
+}
+
 sub _fault {
     my $mt  = MT::XMLRPCServer::Util::mt_new();
     my $enc = $mt->config('PublishCharset');
@@ -124,6 +136,7 @@ sub _make_token {
 sub _login {
     my $class = shift;
     my ( $user, $pass, $blog_id ) = @_;
+
     my $mt  = MT::XMLRPCServer::Util::mt_new();
     my $enc = $mt->config('PublishCharset');
     require MT::Author;
@@ -265,11 +278,10 @@ sub _save_placements {
             my $cat_class = MT->model('category');
 
             # The spec says to ignore invalid category names.
-            @categories = grep {defined} $cat_class->search(
-                {   blog_id => $entry->blog_id,
-                    label   => $cats,
-                }
-            );
+            @categories
+                = grep {defined}
+                $cat_class->search(
+                { blog_id => $entry->blog_id, label => $cats, } );
         }
     }
 
@@ -279,10 +291,7 @@ CATEGORY: for my $category (@categories)
         my $place;
         if ($is_primary_placement) {
             $place = MT::Placement->load(
-                {   entry_id   => $entry->id,
-                    is_primary => 1,
-                }
-            );
+                { entry_id => $entry->id, is_primary => 1, } );
         }
         if ( !$place ) {
             $place = MT::Placement->new;
@@ -301,10 +310,7 @@ CATEGORY: for my $category (@categories)
             # Delete all the secondary placements, so each of the remaining
             # iterations of the loop make a brand new placement.
             my @old_places = MT::Placement->load(
-                {   entry_id   => $entry->id,
-                    is_primary => 0,
-                }
-            );
+                { entry_id => $entry->id, is_primary => 0, } );
             for my $place (@old_places) {
                 $place->remove;
             }
@@ -382,8 +388,7 @@ sub _new_entry {
     );
     $entry->allow_comments( $item->{mt_allow_comments} )
         if exists $item->{mt_allow_comments};
-    $entry->title( $item->{title} )
-        if exists $item->{title};
+    $entry->title( $item->{title} ) if exists $item->{title};
 
     $class->_apply_basename( $entry, $item, \%param );
 
@@ -479,6 +484,21 @@ sub newPost {
     else {
         ( $blog_id, $user, $pass, $item, $publish ) = @_;
     }
+
+    _validate_params( [ $blog_id, $user, $pass, $publish ] ) or return;
+    my $values;
+    foreach my $k ( keys %$item ) {
+        if ( 'categories' eq $k || 'mt_tb_ping_urls' eq $k ) {
+
+            # XMLRPC supports categories array and mt_tb_ping_urls array
+            _validate_params( \@{ $item->{$k} } ) or return;
+        }
+        else {
+            push @$values, $item->{$k};
+        }
+    }
+    _validate_params( \@$values ) or return;
+
     $class->_new_entry(
         blog_id => $blog_id,
         user    => $user,
@@ -491,6 +511,21 @@ sub newPost {
 sub newPage {
     my $class = shift;
     my ( $blog_id, $user, $pass, $item, $publish ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass, $publish ] ) or return;
+    my $values;
+    foreach my $k ( keys %$item ) {
+        if ( 'mt_tb_ping_urls' eq $k ) {
+
+            # XMLRPC supports mt_tb_ping_urls array
+            _validate_params( \@{ $item->{$k} } ) or return;
+        }
+        else {
+            push @$values, $item->{$k};
+        }
+    }
+    _validate_params( \@$values ) or return;
+
     $class->_new_entry(
         blog_id => $blog_id,
         user    => $user,
@@ -636,6 +671,21 @@ sub editPost {
     else {
         ( $entry_id, $user, $pass, $item, $publish ) = @_;
     }
+
+    _validate_params( [ $entry_id, $user, $pass, $publish ] ) or return;
+    my $values;
+    foreach my $k ( keys %$item ) {
+        if ( 'categories' eq $k || 'mt_tb_ping_urls' eq $k ) {
+
+            # XMLRPC supports categories array and mt_tb_ping_urls array
+            _validate_params( \@{ $item->{$k} } ) or return;
+        }
+        else {
+            push @$values, $item->{$k};
+        }
+    }
+    _validate_params( \@$values ) or return;
+
     $class->_edit_entry(
         entry_id => $entry_id,
         user     => $user,
@@ -648,6 +698,22 @@ sub editPost {
 sub editPage {
     my $class = shift;
     my ( $blog_id, $entry_id, $user, $pass, $item, $publish ) = @_;
+
+    _validate_params( [ $blog_id, $entry_id, $user, $pass, $publish ] )
+        or return;
+    my $values;
+    foreach my $k ( keys %$item ) {
+        if ( 'mt_tb_ping_urls' eq $k ) {
+
+            # XMLRPC supports mt_tb_ping_urls array
+            _validate_params( \@{ $item->{$k} } ) or return;
+        }
+        else {
+            push @$values, $item->{$k};
+        }
+    }
+    _validate_params( \@$values ) or return;
+
     $class->_edit_entry(
         blog_id  => $blog_id,
         entry_id => $entry_id,
@@ -668,6 +734,9 @@ sub getUsersBlogs {
         $class = __PACKAGE__;
     }
     my ( $appkey, $user, $pass ) = @_;
+
+    _validate_params( [ $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ($author) = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -716,6 +785,9 @@ sub getUserInfo {
         $class = __PACKAGE__;
     }
     my ( $appkey, $user, $pass ) = @_;
+
+    _validate_params( [ $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ($author) = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -741,8 +813,7 @@ sub _get_entries {
     die _fault( MT->translate("Invalid login") ) unless $author;
     die _fault( MT->translate("Permission denied.") )
         if !$author->is_superuser
-            && (   !$perms
-                || !$perms->can_do('get_entries_via_xmlrpc_server') );
+        && ( !$perms || !$perms->can_do('get_entries_via_xmlrpc_server') );
     my $iter = MT->model($obj_type)->load_iter(
         { blog_id => $blog_id },
         {   'sort'    => 'authored_on',
@@ -808,6 +879,9 @@ sub getRecentPosts {
     else {
         ( $blog_id, $user, $pass, $num ) = @_;
     }
+
+    _validate_params( [ $blog_id, $user, $pass, $num ] ) or return;
+
     $class->_get_entries(
         blog_id => $blog_id,
         user    => $user,
@@ -819,6 +893,9 @@ sub getRecentPosts {
 sub getRecentPostTitles {
     my $class = shift;
     my ( $blog_id, $user, $pass, $num ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass, $num ] ) or return;
+
     $class->_get_entries(
         blog_id     => $blog_id,
         user        => $user,
@@ -831,6 +908,9 @@ sub getRecentPostTitles {
 sub getPages {
     my $class = shift;
     my ( $blog_id, $user, $pass ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+
     $class->_get_entries(
         blog_id => $blog_id,
         user    => $user,
@@ -867,16 +947,13 @@ sub _delete_entry {
 
     # Rebuild archives
     if (%recipe) {
-        $mt->rebuild_archives(
-            Blog   => $blog,
-            Recipe => \%recipe,
-        ) or die _fault( $class->errstr );
+        $mt->rebuild_archives( Blog => $blog, Recipe => \%recipe, )
+            or die _fault( $class->errstr );
     }
 
     # Rebuild index files
     if ( $mt->config('RebuildAtDelete') ) {
-        $mt->rebuild_indexes( Blog => $blog )
-            or die _fault( $class->errstr );
+        $mt->rebuild_indexes( Blog => $blog ) or die _fault( $class->errstr );
     }
 
     $mt->log(
@@ -903,6 +980,9 @@ sub deletePost {
         $class = __PACKAGE__;
     }
     my ( $appkey, $entry_id, $user, $pass, $publish ) = @_;
+
+    _validate_params( [ $entry_id, $user, $pass, $publish ] ) or return;
+
     $class->_delete_entry(
         entry_id => $entry_id,
         user     => $user,
@@ -914,6 +994,9 @@ sub deletePost {
 sub deletePage {
     my $class = shift;
     my ( $blog_id, $user, $pass, $entry_id ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass, $entry_id ] ) or return;
+
     $class->_delete_entry(
         blog_id  => $blog_id,
         entry_id => $entry_id,
@@ -942,8 +1025,8 @@ sub _get_entry {
     die _fault( MT->translate("Not privileged to get entry") )
         if !$author->is_superuser
             && ( !$perms || !$perms->can_edit_entry( $entry, $author ) );
-    my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d",
-        unpack 'A4A2A2A2A2A2', $entry->authored_on;
+    my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d", unpack 'A4A2A2A2A2A2',
+        $entry->authored_on;
     my $link = $entry->permalink;
     require MT::Tag;
     my $delim = chr( $author->entry_prefs->{tag_delim} );
@@ -987,12 +1070,18 @@ sub _get_entry {
 sub getPost {
     my $class = shift;
     my ( $entry_id, $user, $pass ) = @_;
+
+    _validate_params( [ $entry_id, $user, $pass ] ) or return;
+
     $class->_get_entry( entry_id => $entry_id, user => $user, pass => $pass );
 }
 
 sub getPage {
     my $class = shift;
     my ( $blog_id, $entry_id, $user, $pass ) = @_;
+
+    _validate_params( [ $blog_id, $entry_id, $user, $pass ] ) or return;
+
     $class->_get_entry(
         blog_id  => $blog_id,
         entry_id => $entry_id,
@@ -1008,9 +1097,8 @@ sub supportedMethods {
         'metaWeblog.getPost',    'metaWeblog.newPost',  'metaWeblog.editPost',
         'metaWeblog.getRecentPosts', 'metaWeblog.newMediaObject',
         'metaWeblog.getCategories',  'metaWeblog.deletePost',
-        'metaWeblog.getUsersBlogs',
-        'wp.newPage', 'wp.getPages', 'wp.getPage', 'wp.editPage',
-        'wp.deletePage',
+        'metaWeblog.getUsersBlogs', 'wp.newPage', 'wp.getPages', 'wp.getPage',
+        'wp.editPage', 'wp.deletePage',
 
         # not yet supported: metaWeblog.getTemplate, metaWeblog.setTemplate
         'mt.getCategoryList', 'mt.setPostCategories', 'mt.getPostCategories',
@@ -1044,6 +1132,9 @@ sub supportedTextFilters {
 sub getCategoryList {
     my $class = shift;
     my ( $blog_id, $user, $pass ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ( $author, $perms ) = $class->_login( $user, $pass, $blog_id );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -1068,13 +1159,15 @@ sub getCategoryList {
 sub getCategories {
     my $class = shift;
     my ( $blog_id, $user, $pass ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ( $author, $perms ) = $class->_login( $user, $pass, $blog_id );
     die _fault( MT->translate("Invalid login") ) unless $author;
     die _fault( MT->translate("Permission denied.") )
         if !$author->is_superuser
-            && (   !$perms
-                || !$perms->can_do('get_categories_via_xmlrpc_server') );
+        && ( !$perms || !$perms->can_do('get_categories_via_xmlrpc_server') );
     require MT::Category;
     my $iter = MT::Category->load_iter( { blog_id => $blog_id } );
     my @data;
@@ -1105,13 +1198,15 @@ sub getCategories {
 sub getTagList {
     my $class = shift;
     my ( $blog_id, $user, $pass ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ( $author, $perms ) = $class->_login( $user, $pass, $blog_id );
     die _fault( MT->translate("Invalid login") ) unless $author;
     die _fault( MT->translate("Permission denied.") )
         if !$author->is_superuser
-            && (   !$perms
-                || !$perms->can_do('get_tag_list_via_xmlrpc_server') );
+        && ( !$perms || !$perms->can_do('get_tag_list_via_xmlrpc_server') );
     require MT::Tag;
     require MT::ObjectTag;
     my $iter = MT::Tag->load_iter(
@@ -1137,6 +1232,9 @@ sub getTagList {
 sub getPostCategories {
     my $class = shift;
     my ( $entry_id, $user, $pass ) = @_;
+
+    _validate_params( [ $entry_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     require MT::Entry;
     my $entry = MT::Entry->load($entry_id)
@@ -1167,6 +1265,12 @@ sub getPostCategories {
 sub setPostCategories {
     my $class = shift;
     my ( $entry_id, $user, $pass, $cats ) = @_;
+
+    _validate_params( [ $entry_id, $user, $pass ] ) or return;
+    foreach my $c (@$cats) {
+        _validate_params( [ values %$c ] ) or return;
+    }
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     require MT::Entry;
     require MT::Placement;
@@ -1213,11 +1317,13 @@ sub setPostCategories {
 sub getTrackbackPings {
     my $class = shift;
     my ($entry_id) = @_;
+
+    _validate_params( [$entry_id] ) or return;
+
     require MT::Trackback;
     require MT::TBPing;
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
-    my $tb = MT::Trackback->load( { entry_id => $entry_id } )
-        or return [];
+    my $tb = MT::Trackback->load( { entry_id => $entry_id } ) or return [];
     my $iter = MT::TBPing->load_iter( { tb_id => $tb->id } );
     my @data;
 
@@ -1235,6 +1341,9 @@ sub getTrackbackPings {
 sub publishPost {
     my $class = shift;
     my ( $entry_id, $user, $pass ) = @_;
+
+    _validate_params( [ $entry_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     require MT::Entry;
     my $entry = MT::Entry->load($entry_id)
@@ -1254,6 +1363,8 @@ sub runPeriodicTasks {
     my $class = shift;
     my ( $user, $pass ) = @_;
 
+    _validate_params( [ $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();
     my $author = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -1267,6 +1378,8 @@ sub publishScheduledFuturePosts {
     my $class = shift;
     my ( $blog_id, $user, $pass ) = @_;
 
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();
     my $author = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -1303,8 +1416,7 @@ sub publishScheduledFuturePosts {
         if ( $entry && $entry->authored_on <= $now ) {
             $entry->status( MT::Entry::RELEASE() );
             $entry->discover_tb_from_entry();
-            $entry->save
-                or die $entry->errstr;
+            $entry->save or die $entry->errstr;
 
             $types{ $entry->class } = 1;
             start_background_task(
@@ -1321,18 +1433,17 @@ sub publishScheduledFuturePosts {
     $blog->save if $changed && ( keys %types );
 
     if ($changed) {
-        $mt->rebuild_indexes( Blog => $blog )
-            or die $mt->errstr;
+        $mt->rebuild_indexes( Blog => $blog ) or die $mt->errstr;
     }
-    {   responseCode   => 'success',
-        publishedCount => $total_changed,
-    };
+    { responseCode => 'success', publishedCount => $total_changed, };
 }
 
 sub getNextScheduled {
     my $class = shift;
     my ( $user, $pass ) = @_;
 
+    _validate_params( [ $user, $pass ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();
     my $author = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -1345,6 +1456,11 @@ sub getNextScheduled {
 sub setRemoteAuthToken {
     my $class = shift;
     my ( $user, $pass, $remote_auth_username, $remote_auth_token ) = @_;
+
+    _validate_params(
+        [ $user, $pass, $remote_auth_username, $remote_auth_token ] )
+        or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ($author) = $class->_login( $user, $pass );
     die _fault( MT->translate("Invalid login") ) unless $author;
@@ -1357,13 +1473,16 @@ sub setRemoteAuthToken {
 sub newMediaObject {
     my $class = shift;
     my ( $blog_id, $user, $pass, $file ) = @_;
+
+    _validate_params( [ $blog_id, $user, $pass ] ) or return;
+    _validate_params( [ values %$file ] ) or return;
+
     my $mt = MT::XMLRPCServer::Util::mt_new();   ## Will die if MT->new fails.
     my ( $author, $perms ) = $class->_login( $user, $pass, $blog_id );
     die _fault( MT->translate("Invalid login") ) unless $author;
     die _fault( MT->translate("Not privileged to upload files") )
         if !$author->is_superuser
-            && (   !$perms
-                || !$perms->can_do('upload_asset_via_xmlrpc_server') );
+        && ( !$perms || !$perms->can_do('upload_asset_via_xmlrpc_server') );
 
     require MT::Blog;
     require File::Spec;
@@ -1403,15 +1522,15 @@ sub newMediaObject {
     }
 
     my $local_file = File::Spec->catfile( $blog->site_path, $file->{name} );
-    my $ext
-        = ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2];
+    my $ext = ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2];
     require MT::Asset::Image;
     if ( MT::Asset::Image->can_handle($ext) ) {
         require MT::Image;
         my $fh;
         my $data = $file->{bits};
         open( $fh, "+<", \$data );
-        close($fh), die _fault(
+        close($fh),
+            die _fault(
             MT->translate(
                 "Saving [_1] failed: [_2]",
                 $file->{name},
@@ -1454,8 +1573,7 @@ sub newMediaObject {
     my $asset_pkg = MT::Asset->handler_for_file($local_basename);
     my $is_image  = 0;
     if ( defined($w) && defined($h) ) {
-        $is_image = 1
-            if $asset_pkg->isa('MT::Asset::Image');
+        $is_image = 1 if $asset_pkg->isa('MT::Asset::Image');
     }
     else {
 
