Merge pull request #5817 from nextcloud/add-basic-sql-injection-checker
Add Phan plugin to check for SQL injectionspull/5820/head
commit
3d9fd4d0d1
@ -0,0 +1,44 @@ |
||||
<?php |
||||
/** |
||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
$expected = <<<EOT |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:23 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:35 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:37 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:39 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:41 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:43 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:54 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:61 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:62 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:69 SqlInjectionChecker Potential SQL injection detected |
||||
build/.phan/tests/SqlInjectionCheckerTest.php:70 SqlInjectionChecker Potential SQL injection detected |
||||
|
||||
EOT; |
||||
|
||||
$result = shell_exec('php '. __DIR__ . '/../../lib/composer/etsy/phan/phan -k build/.phan/config.php --include-analysis-file-list build/.phan/tests/* --directory build/.phan/tests/'); |
||||
|
||||
if($result !== $expected) { |
||||
echo("Output of phan doesn't match expectation\n"); |
||||
echo("Expected: $expected\n"); |
||||
echo("Result: $result\n"); |
||||
exit(1); |
||||
} |
||||
@ -0,0 +1,134 @@ |
||||
<?php |
||||
/** |
||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
use Phan\PluginV2; |
||||
use Phan\PluginV2\AnalyzeNodeCapability; |
||||
use Phan\PluginV2\PluginAwareAnalysisVisitor; |
||||
|
||||
class SqlInjectionCheckerPlugin extends PluginV2 implements AnalyzeNodeCapability{ |
||||
public static function getAnalyzeNodeVisitorClassName() : string { |
||||
return SqlInjectionCheckerVisitor::class; |
||||
} |
||||
} |
||||
|
||||
class SqlInjectionCheckerVisitor extends PluginAwareAnalysisVisitor { |
||||
|
||||
private function throwError() { |
||||
$this->emit( |
||||
'SqlInjectionChecker', |
||||
'Potential SQL injection detected', |
||||
[], |
||||
\Phan\Issue::SEVERITY_CRITICAL |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Checks whether the query builder functions are using prepared statements |
||||
* |
||||
* @param \ast\Node $node |
||||
*/ |
||||
private function checkQueryBuilderParameters(\ast\Node $node) { |
||||
$dangerousFunctions = [ |
||||
'eq', |
||||
'neq', |
||||
'lt', |
||||
'lte', |
||||
'gt', |
||||
'gte', |
||||
'like', |
||||
'iLike', |
||||
'notLike', |
||||
]; |
||||
|
||||
$safeFunctions = [ |
||||
'createNamedParameter', |
||||
'createPositionalParameter', |
||||
'createParameter', |
||||
]; |
||||
|
||||
$functionsToSearch = [ |
||||
'set', |
||||
'setValue', |
||||
]; |
||||
|
||||
$expandedNode = \Phan\Language\UnionType::fromNode($this->context, $this->code_base, $node); |
||||
$expandedNodeType = (string)$expandedNode->asExpandedTypes($this->code_base); |
||||
|
||||
if($expandedNodeType === '\OCP\DB\QueryBuilder\IQueryBuilder') { |
||||
/** @var \ast\Node $child */ |
||||
foreach($node->children as $child) { |
||||
if(isset($child->kind) && $child->kind === 128) { |
||||
if(isset($child->children)) { |
||||
/** @var \ast\Node $subChild */ |
||||
foreach ($child->children as $subChild) { |
||||
// For set actions |
||||
if(isset($node->children['method']) && in_array($node->children['method'], $functionsToSearch, true) && !is_string($subChild)) { |
||||
if(!isset($subChild->children['method']) || !in_array($subChild->children['method'], $safeFunctions, true)) { |
||||
$this->throwError(); |
||||
} |
||||
} |
||||
|
||||
if(isset($subChild->children['method'])) { |
||||
// For all "eq" etc. actions |
||||
$method = $subChild->children['method']; |
||||
if(!in_array($method, $dangerousFunctions, true)) { |
||||
return; |
||||
} |
||||
|
||||
/** @var \ast\Node $functionNode */ |
||||
$functionNode = $subChild->children['args']; |
||||
|
||||
/** @var \ast\Node $secondParameterNode */ |
||||
$secondParameterNode = $functionNode->children[1]; |
||||
$expandedNode = \Phan\Language\UnionType::fromNode($this->context, $this->code_base, $secondParameterNode); |
||||
|
||||
// For literals with a plain string or integer inside |
||||
if(isset($secondParameterNode->children['method']) && $secondParameterNode->children['method'] === 'literal') { |
||||
/** @var \ast\Node $functionNode */ |
||||
$functionNode = $secondParameterNode->children['args']; |
||||
|
||||
$expandedNode = \Phan\Language\UnionType::fromNode($this->context, $this->code_base, $functionNode); |
||||
if(isset($functionNode->children[0]) && (is_string($functionNode->children[0]) || is_int($functionNode->children[0]))) { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
// If it is an IParameter or a pure string no error is thrown |
||||
if((string)$expandedNode !== '\OCP\DB\QueryBuilder\IParameter' && !is_string($secondParameterNode)) { |
||||
$this->throwError(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public function visitMethodCall(\ast\Node $node) { |
||||
$this->checkQueryBuilderParameters($node); |
||||
} |
||||
|
||||
} |
||||
|
||||
return new SqlInjectionCheckerPlugin(); |
||||
@ -0,0 +1,72 @@ |
||||
<?php |
||||
/** |
||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); |
||||
$builder->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $_GET['asdf'])); |
||||
|
||||
class SqlInjectionCheckerTest { |
||||
private $qb; |
||||
|
||||
public function __construct(\OCP\IDBConnection $dbConnection) { |
||||
$this->qb = $dbConnection->getQueryBuilder(); |
||||
} |
||||
|
||||
public function testEqAndNeq() { |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $this->qb->expr()->literal('myString'))); |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $this->qb->expr()->literal(0))); |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $this->qb->expr()->literal($_GET['bar']))); |
||||
$asdf = '123'; |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $this->qb->expr()->literal($asdf))); |
||||
$asdf = 1; |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->neq('asdf', $asdf)); |
||||
$asdf = '123'; |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->lt('asdf', $asdf)); |
||||
$this->qb->select('*')->from('ado')->where($this->qb->expr()->eq('s.resourceid', 'a.id')); |
||||
$this->qb->select('*')->from('ado')->andWhere($this->qb->expr()->gte('asdf', $_GET['asdf'])); |
||||
$this->qb->select('*') |
||||
->from('ado') |
||||
->where($this->qb->expr()->eq('asdf', $this->qb->createNamedParameter('asdf'))); |
||||
$this->qb->select('*') |
||||
->from('ado') |
||||
->where($this->qb->expr()->eq('asdf', $this->qb->createPositionalParameter('asdf'))); |
||||
} |
||||
|
||||
public function testInstantiatingDatabaseConnection() { |
||||
$qb = \OC::$server->getDatabaseConnection(); |
||||
$qb->getQueryBuilder()->select('*')->from('ado')->where($this->qb->expr()->eq('asdf', $_GET['asdf'])); |
||||
} |
||||
|
||||
public function testSet() { |
||||
$this->qb->update('file_locks')->set('lock', $this->qb->createNamedParameter('lukaslukaslukas')); |
||||
$this->qb->update('file_locks')->set('lock', '1234'); |
||||
$asdf = '1234'; |
||||
$this->qb->update('file_locks')->set('lock', $asdf); |
||||
$this->qb->update('file_locks')->set('lock', $_GET['asdf']); |
||||
} |
||||
|
||||
public function testSetValue() { |
||||
$this->qb->update('file_locks')->setValue('lock', $this->qb->createNamedParameter('lukaslukaslukas')); |
||||
$this->qb->update('file_locks')->setValue('lock', '1234'); |
||||
$asdf = '1234'; |
||||
$this->qb->update('file_locks')->setValue('lock', $asdf); |
||||
$this->qb->update('file_locks')->setValue('lock', $_GET['asdf']); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue