Index: src/wp-includes/query.php
===================================================================
--- src/wp-includes/query.php	(revision 29020)
+++ src/wp-includes/query.php	(working copy)
@@ -2200,6 +2200,98 @@
 	}
 
 	/**
+	 * If the passed orderby value is allowed, convert the alias to a
+	 * properly-prefixed orderby value.
+	 *
+	 * @since 4.0.0
+	 * @access protected
+	 *
+	 * @global wpdb $wpdb WordPress database access abstraction object.
+	 *
+	 * @param string $orderby Alias for the field to order by.
+	 * @return string|bool Table-prefixed value to used in the ORDER clause. False otherwise.
+	 */
+	protected function parse_orderby( $orderby ) {
+		global $wpdb;
+
+		// Used to filter values.
+		$allowed_keys = array(
+			'post_name', 'post_author', 'post_date', 'post_title', 'post_modified',
+			'post_parent', 'post_type', 'name', 'author', 'date', 'title', 'modified',
+			'parent', 'type', 'ID', 'menu_order', 'comment_count', 'rand',
+		);
+
+		$meta_key = $this->get( 'meta_key' );
+		if ( ! empty( $meta_key ) ) {
+			$allowed_keys[] = $meta_key;
+			$allowed_keys[] = 'meta_value';
+			$allowed_keys[] = 'meta_value_num';
+		}
+
+		if ( ! in_array( $orderby, $allowed_keys ) ) {
+			return false;
+		}
+
+		switch ( $orderby ) {
+
+			case 'post_name':
+			case 'post_author':
+			case 'post_date':
+			case 'post_title':
+			case 'post_modified':
+			case 'post_parent':
+			case 'post_type':
+			case 'ID':
+			case 'menu_order':
+			case 'comment_count':
+				$orderby = "$wpdb->posts.{$orderby}";
+				break;
+			case 'rand':
+				$orderby = 'RAND()';
+				break;
+			case $meta_key:
+			case 'meta_value':
+				$type = $this->get( 'meta_type' );
+				if ( ! empty( $type ) ) {
+					$meta_type = $this->meta_query->get_cast_for_type( $type );
+					$orderby = "CAST($wpdb->postmeta.meta_value AS {$meta_type})";
+				} else {
+					$orderby = "$wpdb->postmeta.meta_value";
+				}
+				break;
+			case 'meta_value_num':
+				$orderby = "$wpdb->postmeta.meta_value+0";
+				break;
+			default:
+				$orderby = "$wpdb->posts.post_" . $orderby;
+				break;
+		}
+
+		return $orderby;
+	}
+
+	/**
+	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
+	 *
+	 * @since 4.0.0
+	 * @access protected
+	 *
+	 * @param string $order The 'order' query variable.
+	 * @return string The sanitized 'order' query variable.
+	 */
+	protected function parse_order( $order ) {
+		if ( ! is_string( $order ) || empty( $order ) ) {
+			return 'DESC';
+		}
+
+		if ( strtoupper( $order ) == 'ASC' ) {
+			return 'ASC';
+		} else {
+			return 'DESC';
+		}
+	}
+
+	/**
 	 * Sets the 404 property and saves whether query is feed.
 	 *
 	 * @since 2.0.0
@@ -2702,12 +2794,23 @@
 
 		$where .= $search . $whichauthor . $whichmimetype;
 
-		if ( empty($q['order']) || ((strtoupper($q['order']) != 'ASC') && (strtoupper($q['order']) != 'DESC')) )
+		if ( !isset( $q['order'] ) ) {
 			$q['order'] = 'DESC';
+		} else {
+			$q['order'] = $this->parse_order( $q['order'] );
+		}
 
-		// Order by
-		if ( empty($q['orderby']) ) {
-			$orderby = "$wpdb->posts.post_date " . $q['order'];
+		// Order by.
+		if ( empty( $q['orderby'] ) ) {
+			/*
+			 * Boolean false or empty array blanks out ORDER BY,
+			 * while leaving the value unset or otherwise empty sets the default.
+			 */
+			if ( isset( $q['orderby'] ) && ( is_array( $q['orderby'] ) || false === $q['orderby'] ) ) {
+				$orderby = '';
+			} else {
+				$orderby = "$wpdb->posts.post_date " . $q['order'];
+			}
 		} elseif ( 'none' == $q['orderby'] ) {
 			$orderby = '';
 		} elseif ( $q['orderby'] == 'post__in' && ! empty( $post__in ) ) {
@@ -2715,59 +2818,41 @@
 		} elseif ( $q['orderby'] == 'post_parent__in' && ! empty( $post_parent__in ) ) {
 			$orderby = "FIELD( {$wpdb->posts}.post_parent, $post_parent__in )";
 		} else {
-			// Used to filter values
-			$allowed_keys = array( 'name', 'author', 'date', 'title', 'modified', 'menu_order', 'parent', 'ID', 'rand', 'comment_count', 'type' );
-			if ( !empty($q['meta_key']) ) {
-				$allowed_keys[] = $q['meta_key'];
-				$allowed_keys[] = 'meta_value';
-				$allowed_keys[] = 'meta_value_num';
-			}
-			$q['orderby'] = urldecode($q['orderby']);
-			$q['orderby'] = addslashes_gpc($q['orderby']);
-
 			$orderby_array = array();
-			foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
-				// Only allow certain values for safety
-				if ( ! in_array($orderby, $allowed_keys) )
-					continue;
+			if ( is_array( $q['orderby'] ) ) {
+				foreach ( $q['orderby'] as $_orderby => $order ) {
+					$orderby = addslashes_gpc( urldecode( $_orderby ) );
+					$parsed  = $this->parse_orderby( $orderby );
 
-				switch ( $orderby ) {
-					case 'menu_order':
-						$orderby = "$wpdb->posts.menu_order";
-						break;
-					case 'ID':
-						$orderby = "$wpdb->posts.ID";
-						break;
-					case 'rand':
-						$orderby = 'RAND()';
-						break;
-					case $q['meta_key']:
-					case 'meta_value':
-						if ( isset( $q['meta_type'] ) ) {
-							$meta_type = $this->meta_query->get_cast_for_type( $q['meta_type'] );
-							$orderby = "CAST($wpdb->postmeta.meta_value AS {$meta_type})";
-						} else {
-							$orderby = "$wpdb->postmeta.meta_value";
-						}
-						break;
-					case 'meta_value_num':
-						$orderby = "$wpdb->postmeta.meta_value+0";
-						break;
-					case 'comment_count':
-						$orderby = "$wpdb->posts.comment_count";
-						break;
-					default:
-						$orderby = "$wpdb->posts.post_" . $orderby;
+					if ( ! $parsed ) {
+						continue;
+					}
+
+					$orderby_array[] = $parsed . ' ' . $this->parse_order( $order );
 				}
+				$orderby = implode( ', ', $orderby_array );
 
-				$orderby_array[] = $orderby;
+			} else {
+				$q['orderby'] = urldecode( $q['orderby'] );
+				$q['orderby'] = addslashes_gpc( $q['orderby'] );
+
+				foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
+					$parsed = $this->parse_orderby( $orderby );
+					// Only allow certain values for safety.
+					if ( ! $parsed ) {
+						continue;
+					}
+
+					$orderby_array[] = $parsed;
+				}
+				$orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
+
+				if ( empty( $orderby ) ) {
+					$orderby = "$wpdb->posts.post_date ".$q['order'];
+				} else {
+					$orderby .= " {$q['order']}";
+				}
 			}
-			$orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
-
-			if ( empty( $orderby ) )
-				$orderby = "$wpdb->posts.post_date ".$q['order'];
-			else
-				$orderby .= " {$q['order']}";
 		}
 
 		// Order search results by relevance only when another "orderby" is not specified in the query.
Index: tests/phpunit/tests/post/query.php
===================================================================
--- tests/phpunit/tests/post/query.php	(revision 29020)
+++ tests/phpunit/tests/post/query.php	(working copy)
@@ -761,4 +761,74 @@
 		$q3 = new WP_Query( array( 'post_status' => array( 'any', 'auto-draft' ) ) );
 		$this->assertNotContains( "post_status <> 'auto-draft'", $q3->request );
 	}
+
+	/**
+	 *
+	 * @ticket 17065
+	 */
+	function test_orderby_array() {
+		global $wpdb;
+
+		$q1 = new WP_Query( array(
+			'orderby' => array(
+				'type' => 'DESC',
+				'name' => 'ASC'
+			)
+		) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_type DESC, $wpdb->posts.post_name ASC",
+			$q1->request
+		);
+
+		$q2 = new WP_Query( array( 'orderby' => array() ) );
+		$this->assertNotContains( 'ORDER BY', $q2->request );
+		$this->assertNotContains( 'ORDER', $q2->request );
+
+		$q3 = new WP_Query( array( 'post_type' => 'post' ) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_date DESC",
+			$q3->request
+		);
+
+		$q4 = new WP_Query( array( 'post_type' => 'post' ) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_date DESC",
+			$q4->request
+		);
+	}
+
+	/**
+	 *
+	 * @ticket 17065
+	 */
+	function test_order() {
+		global $wpdb;
+
+		$q1 = new WP_Query( array(
+			'orderby' => array(
+				'post_type' => 'foo'
+			)
+		) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_type DESC",
+			$q1->request
+		);
+
+		$q2 = new WP_Query( array(
+			'orderby' => 'title',
+			'order'   => 'foo'
+		) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_title DESC",
+			$q2->request
+		);
+
+		$q3 = new WP_Query( array(
+			'order' => 'asc'
+		) );
+		$this->assertContains(
+			"ORDER BY $wpdb->posts.post_date ASC",
+			$q3->request
+		);
+	}
 }
\ No newline at end of file
