diff options
author | webchick <webchick@24967.no-reply.drupal.org> | 2011-05-17 23:57:40 -0500 |
---|---|---|
committer | webchick <webchick@24967.no-reply.drupal.org> | 2011-05-17 23:57:40 -0500 |
commit | e77f87506c0355bb357bf32651148768e4a302b8 (patch) | |
tree | 57ee327a006e415b41612807ebe8468f4b03c68f | |
parent | e27392c158c328ab3440c634c99212e302ef2f74 (diff) | |
download | brdo-e77f87506c0355bb357bf32651148768e4a302b8.tar.gz brdo-e77f87506c0355bb357bf32651148768e4a302b8.tar.bz2 |
Issue #1105848 by cafuego: Fixed Unsafe query comments possible via UI.
-rw-r--r-- | includes/database/database.inc | 57 | ||||
-rw-r--r-- | includes/database/mysql/query.inc | 8 | ||||
-rw-r--r-- | includes/database/pgsql/query.inc | 4 | ||||
-rw-r--r-- | includes/database/query.inc | 22 | ||||
-rw-r--r-- | includes/database/select.inc | 5 | ||||
-rw-r--r-- | includes/database/sqlite/query.inc | 8 | ||||
-rw-r--r-- | modules/simpletest/tests/database_test.test | 21 |
7 files changed, 101 insertions, 24 deletions
diff --git a/includes/database/database.inc b/includes/database/database.inc index 4539b37a7..4cc1a33d7 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -541,6 +541,63 @@ abstract class DatabaseConnection extends PDO { } /** + * Flatten an array of query comments into a single comment string. + * + * The comment string will be sanitized to avoid SQL injection attacks. + * + * @param $comments + * An array of query comment strings. + * + * @return + * A sanitized comment string. + */ + public function makeComment($comments) { + if (empty($comments)) + return ''; + + // Flatten the array of comments. + $comment = implode('; ', $comments); + + // Sanitize the comment string so as to avoid SQL injection attacks. + return '/* ' . $this->filterComment($comment) . ' */ '; + } + + /** + * Sanitize a query comment string. + * + * Ensure a query comment does not include strings such as "* /" that might + * terminate the comment early. This avoids SQL injection attacks via the + * query comment. The comment strings in this example are separated by a + * space to avoid PHP parse errors. + * + * For example, the comment: + * @code + * db_update('example') + * ->condition('id', $id) + * ->fields(array('field2' => 10)) + * ->comment('Exploit * / DROP TABLE node; --') + * ->execute() + * @endcode + * + * Would result in the following SQL statement being generated: + * @code + * "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..." + * @endcode + * + * Unless the comment is sanitised first, the SQL server would drop the + * node table and ignore the rest of the SQL statement. + * + * @param $comment + * A query comment string. + * + * @return + * A sanitized version of the query comment string. + */ + protected function filterComment($comment = '') { + return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment); + } + + /** * Executes a query string against the database. * * This method provides a central handler for the actual execution of every diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index f7fb52f04..888b6a5a4 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -42,8 +42,8 @@ class InsertQuery_mysql extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -92,8 +92,8 @@ class TruncateQuery_mysql extends TruncateQuery { // not transactional, and result in an implicit COMMIT. When we are in a // transaction, fallback to the slower, but transactional, DELETE. if ($this->connection->inTransaction()) { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; } else { diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index fe7909e17..f3783a9ca 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -103,8 +103,8 @@ class InsertQuery_pgsql extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); diff --git a/includes/database/query.inc b/includes/database/query.inc index 7f3e9ff85..23b652f9b 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -361,6 +361,9 @@ abstract class Query implements QueryPlaceholderInterface { * for easier debugging and allows you to more easily find where a query * with a performance problem is being generated. * + * The comment string will be sanitized to remove * / and other characters + * that may terminate the string early so as to avoid SQL injection attacks. + * * @param $comment * The comment string to be inserted into the query. * @@ -623,9 +626,8 @@ class InsertQuery extends Query { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -815,9 +817,8 @@ class DeleteQuery extends Query implements QueryConditionInterface { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; @@ -884,8 +885,8 @@ class TruncateQuery extends Query { * The prepared statement. */ public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; } @@ -1111,9 +1112,8 @@ class UpdateQuery extends Query implements QueryConditionInterface { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Expressions take priority over literal fields, so we process those first // and remove any literal fields that conflict. diff --git a/includes/database/select.inc b/includes/database/select.inc index 6e4b0dc48..716c2fc3d 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1439,9 +1439,8 @@ class SelectQuery extends Query implements SelectQueryInterface { } public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // SELECT $query = $comments . 'SELECT '; diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index a176ed649..6b8a72f2a 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -32,8 +32,8 @@ class InsertQuery_sqlite extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Produce as many generic placeholders as necessary. $placeholders = array_fill(0, count($this->insertFields), '?'); @@ -148,8 +148,8 @@ class DeleteQuery_sqlite extends DeleteQuery { */ class TruncateQuery_sqlite extends TruncateQuery { public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; } diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 231355ceb..c22d1fc5d 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -1325,6 +1325,27 @@ class DatabaseSelectTestCase extends DatabaseTestCase { } /** + * Test query COMMENT system against vulnerabilities. + */ + function testVulnerableComment() { + $query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --'); + $name_field = $query->addField('test', 'name'); + $age_field = $query->addField('test', 'age', 'age'); + $result = $query->execute(); + + $num_records = 0; + foreach ($result as $record) { + $num_records++; + } + + $query = (string)$query; + $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; + + $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($query, $expected, t('The flattened query contains the sanitised comment string.')); + } + + /** * Test basic conditionals on SELECT statements. */ function testSimpleSelectConditional() { |