diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
index dde68082b731661728fe843cc9f573f5240c97d8..1f5072b2974332448573ade69c785929618506cc 100644
--- a/core/lib/Drupal/Core/Database/Query/Select.php
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -541,6 +541,8 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume
   }
 
   public function orderBy($field, $direction = 'ASC') {
+    // Only allow ASC and DESC, default to ASC.
+    $direction = strtoupper($direction) == 'DESC' ? 'DESC' : 'ASC';
     $this->order[$field] = $direction;
     return $this;
   }
@@ -748,7 +750,7 @@ public function __toString() {
       $query .= "\nORDER BY ";
       $fields = array();
       foreach ($this->order as $field => $direction) {
-        $fields[] = $field . ' ' . $direction;
+        $fields[] = $this->connection->escapeField($field) . ' ' . $direction;
       }
       $query .= implode(', ', $fields);
     }
diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
index db66aefa27ccd597087a8947bc71512603319d1b..645fd78b07c5456960907097eb767b194d96ce2b 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
@@ -363,9 +363,19 @@ public function addJoin($type, $table, $alias = NULL, $condition = NULL, $argume
    * called.
    *
    * @param $field
-   *   The field on which to order.
+   *   The field on which to order. The field is escaped for security so only
+   *   valid field and alias names are possible. To order by an expression, add
+   *   the expression with addExpression() first and then use the alias to order
+   *   on.
+   *
+   *   Example:
+   *   <code>
+   *   $query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'order_field');
+   *   $query->orderBy('order_field', 'ASC');
+   *   </code>
    * @param $direction
-   *   The direction to sort. Legal values are "ASC" and "DESC".
+   *   The direction to sort. Legal values are "ASC" and "DESC". Any other value
+   *   will be converted to "ASC".
    * @return \Drupal\Core\Database\Query\SelectInterface
    *   The called object.
    */
diff --git a/core/lib/Drupal/Core/Database/Query/TableSortExtender.php b/core/lib/Drupal/Core/Database/Query/TableSortExtender.php
index a9bcd400c5ec6d3d165bbe005f18feefcf3698f4..4786f5973c56f278e85d390f0b61af8c6a5b15cf 100644
--- a/core/lib/Drupal/Core/Database/Query/TableSortExtender.php
+++ b/core/lib/Drupal/Core/Database/Query/TableSortExtender.php
@@ -46,10 +46,9 @@ public function orderByHeader(array $header) {
       // Based on code from db_escape_table(), but this can also contain a dot.
       $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
 
-      // Sort order can only be ASC or DESC.
-      $sort = drupal_strtoupper($ts['sort']);
-      $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
-      $this->orderBy($field, $sort);
+      // orderBy() will ensure that only ASC/DESC values are accepted, so we
+      // don't need to sanitize that here.
+      $this->orderBy($field, $ts['sort']);
     }
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index f5c84b445deb0b9307e89111f361321460bcad14..883513e2a57713e3083ef4b8a52a0b5f71535b83 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -222,8 +222,8 @@ protected function addSort() {
         // if the direction is descending.
         $function = $direction == 'ASC' ? 'min' : 'max';
         $expression = "$function($sql_alias)";
-        $this->sqlQuery->addExpression($expression);
-        $this->sqlQuery->orderBy($expression, $direction);
+        $expression_alias = $this->sqlQuery->addExpression($expression);
+        $this->sqlQuery->orderBy($expression_alias, $direction);
       }
     }
     return $this;
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/QueryAggregate.php b/core/lib/Drupal/Core/Entity/Query/Sql/QueryAggregate.php
index 2658f0de642eefe84a8df34e6a69a906342af59e..c76a773e67dd76278eeb7728764f3211dff28b19 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/QueryAggregate.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/QueryAggregate.php
@@ -124,7 +124,7 @@ protected function addGroupBy() {
   protected function addSortAggregate() {
     if(!$this->count) {
       foreach ($this->sortAggregate as $alias => $sort) {
-        $this->sqlQuery->orderBy($this->sqlExpressions[$alias], $sort['direction']);
+        $this->sqlQuery->orderBy($alias, $sort['direction']);
       }
     }
     return $this;
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index fc4308ddce2d5b34bf30f9c8cc52840948d4a7c6..1f5201eba09b05475deaf4efa2b603fa4629e283 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -370,9 +370,11 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $en
       ->range(0, $new_replies);
 
     // 2. Find the first thread.
-    $first_thread = db_select($unread_threads_query, 'thread')
+    $first_thread_query = db_select($unread_threads_query, 'thread');
+    $first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder');
+    $first_thread = $first_thread_query
       ->fields('thread', array('thread'))
-      ->orderBy('SUBSTRING(thread, 1, (LENGTH(thread) - 1))')
+      ->orderBy('torder')
       ->range(0, 1)
       ->execute()
       ->fetchField();
diff --git a/core/tests/Drupal/Tests/Core/Database/OrderByTest.php b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e69b11a25adbfcb107a65cfedf5997d2fb21c60b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Database\OrderByTest.
+ */
+
+namespace Drupal\Tests\Core\Database;
+
+use Drupal\Core\Database\Query\Select;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the orderBy() method of select queries.
+ */
+class OrderByTest extends UnitTestCase {
+
+  /**
+   * The select query object to test.
+   *
+   * @var \Drupal\Core\Database\Query\Select
+   */
+  protected $query;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Order by',
+      'description' => 'Tests the orderBy() method of select queries.',
+      'group' => 'Database',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMockForAbstractClass();
+    $this->query = new Select('test', NULL, $connection);
+  }
+
+  /**
+   * Checks that invalid sort directions in ORDER BY get converted to ASC.
+   */
+  public function testInvalidDirection() {
+    $this->query->orderBy('test', 'invalid direction');
+    $order_bys = $this->query->getOrderBy();
+    $this->assertEquals($order_bys['test'], 'ASC', 'Invalid order by direction is converted to ASC.');
+  }
+
+  /**
+   * Tests that fields passed for ordering get escaped properly.
+   */
+  public function testFieldEscaping() {
+    $this->query->orderBy('x; DROP table node; --');
+    $sql = $this->query->__toString();
+    $this->assertStringEndsWith('ORDER BY xDROPtablenode ASC', $sql, 'Order by field is escaped correctly.');
+  }
+}