diff options
Diffstat (limited to 'build/.phan')
-rw-r--r-- | build/.phan/config.php | 5 | ||||
-rw-r--r-- | build/.phan/plugin-checker.php | 44 | ||||
-rw-r--r-- | build/.phan/plugins/SqlInjectionCheckerPlugin.php | 134 | ||||
-rw-r--r-- | build/.phan/tests/SqlInjectionCheckerTest.php | 72 |
4 files changed, 255 insertions, 0 deletions
diff --git a/build/.phan/config.php b/build/.phan/config.php index 0e3e2788510..26421529433 100644 --- a/build/.phan/config.php +++ b/build/.phan/config.php @@ -151,4 +151,9 @@ return [ 'whitelist_issue_types' => [ // 'PhanAccessMethodPrivate', ], + + // A list of plugin files to execute + 'plugins' => [ + 'build/.phan/plugins/SqlInjectionCheckerPlugin.php', + ], ]; diff --git a/build/.phan/plugin-checker.php b/build/.phan/plugin-checker.php new file mode 100644 index 00000000000..681904264f6 --- /dev/null +++ b/build/.phan/plugin-checker.php @@ -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); +} diff --git a/build/.phan/plugins/SqlInjectionCheckerPlugin.php b/build/.phan/plugins/SqlInjectionCheckerPlugin.php new file mode 100644 index 00000000000..8cfd5ac4752 --- /dev/null +++ b/build/.phan/plugins/SqlInjectionCheckerPlugin.php @@ -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(); diff --git a/build/.phan/tests/SqlInjectionCheckerTest.php b/build/.phan/tests/SqlInjectionCheckerTest.php new file mode 100644 index 00000000000..3824d5c1704 --- /dev/null +++ b/build/.phan/tests/SqlInjectionCheckerTest.php @@ -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']); + } +}
\ No newline at end of file |