diff --git a/docs/security_analysis/custom_taint_sources.md b/docs/security_analysis/custom_taint_sources.md index d3bdb0a3205..938bd3455ba 100644 --- a/docs/security_analysis/custom_taint_sources.md +++ b/docs/security_analysis/custom_taint_sources.md @@ -23,52 +23,32 @@ For example this plugin treats all variables named `$bad_data` as taint sources. ```php */ - public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $event): ?bool { + public static function addTaints(AddRemoveTaintsEvent $event): array + { $expr = $event->getExpr(); - $statements_source = $event->getStatementsSource(); - $codebase = $event->getCodebase(); - if ($expr instanceof PhpParser\Node\Expr\Variable - && $expr->name === 'bad_data' - ) { - $expr_type = $statements_source->getNodeTypeProvider()->getType($expr); - // should be a globally unique id - // you can use its line number/start offset - $expr_identifier = '$bad_data' - . '-' . $statements_source->getFileName() - . ':' . $expr->getAttribute('startFilePos'); - - if ($expr_type) { - $codebase->addTaintSource( - $expr_type, - $expr_identifier, - TaintKindGroup::ALL_INPUT, - new CodeLocation($statements_source, $expr) - ); - } + if ($expr instanceof Variable && $expr->name === 'bad_data') { + return TaintKindGroup::ALL_INPUT; } - return null; + + return []; } } ``` diff --git a/examples/plugins/TaintActiveRecords.php b/examples/plugins/TaintActiveRecords.php new file mode 100644 index 00000000000..3214e56525a --- /dev/null +++ b/examples/plugins/TaintActiveRecords.php @@ -0,0 +1,88 @@ + + */ + public static function addTaints(AddRemoveTaintsEvent $event): array + { + $expr = $event->getExpr(); + $statements_source = $event->getStatementsSource(); + + // For all property fetch expressions, walk through the full fetch path + // (e.g. `$model->property->subproperty`) and check if it contains + // any class of namespace \app\models\ + do { + $expr_type = $statements_source->getNodeTypeProvider()->getType($expr); + if (!$expr_type) { + continue; + } + + if (self::containsActiveRecord($expr_type)) { + return TaintKindGroup::ALL_INPUT; + } + } while ($expr = self::getParentNode($expr)); + + return []; + } + + /** + * @return bool `true` if union contains a type of model + */ + private static function containsActiveRecord(Union $union_type): bool + { + foreach ($union_type->getAtomicTypes() as $type) { + if (self::isActiveRecord($type)) { + return true; + } + } + + return false; + } + + /** + * @return bool `true` if namespace of type is in namespace `app\models` + */ + private static function isActiveRecord(Atomic $type): bool + { + if (!$type instanceof TNamedObject) { + return false; + } + + + return strpos($type->value, 'app\models\\') === 0; + } + + + /** + * Return next node that should be followed for active record search + */ + private static function getParentNode(Expr $expr): ?Expr + { + // Model properties are always accessed by a property fetch + if ($expr instanceof PropertyFetch) { + return $expr->var; + } + + return null; + } +} diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php index b6380df0fcd..01e60cdadc0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -12,6 +12,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Internal\Type\TypeCombiner; use Psalm\Issue\DuplicateArrayKey; @@ -442,6 +443,11 @@ private static function analyzeArrayItem( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + foreach ($item_value_type->parent_nodes as $parent_node) { $data_flow_graph->addPath( $parent_node, @@ -477,6 +483,11 @@ private static function analyzeArrayItem( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + foreach ($item_key_type->parent_nodes as $parent_node) { $data_flow_graph->addPath( $parent_node, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php index bedec945c25..d67616540a5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php @@ -26,6 +26,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\Comparator\TypeComparisonResult; @@ -503,6 +504,11 @@ private static function taintProperty( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($property_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + $data_flow_graph->addPath( $property_node, $var_node, @@ -598,6 +604,11 @@ public static function taintUnspecializedProperty( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($property_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + $data_flow_graph->addPath( $localized_property_node, $property_node, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index 679abaa347a..a55d595e8c2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -31,6 +31,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\ReferenceConstraint; use Psalm\Internal\Scanner\VarDocblockComment; @@ -821,6 +822,11 @@ private static function taintAssignment( $data_flow_graph->addNode($new_parent_node); $new_parent_nodes = [$new_parent_node->id => $new_parent_node]; + if ($added_taints !== [] && $data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $data_flow_graph->addSource($taint_source); + } + foreach ($parent_nodes as $parent_node) { $data_flow_graph->addPath( $parent_node, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php index 2b7b8b607ae..248a160097b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php @@ -16,6 +16,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\MethodIdentifier; use Psalm\Issue\DocblockTypeContradiction; use Psalm\Issue\ImpureMethodCall; @@ -169,6 +170,11 @@ public static function analyze( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + if ($stmt_left_type && $stmt_left_type->parent_nodes) { foreach ($stmt_left_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 2fd7cd1b5a7..1c1bf669414 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -20,6 +20,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\Comparator\CallableTypeComparator; use Psalm\Internal\Type\Comparator\TypeComparisonResult; @@ -1897,5 +1898,10 @@ private static function processTaintedness( $removed_taints, ); } + + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($argument_value_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 98e192c72f7..09ac0a40865 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -17,6 +17,7 @@ use Psalm\Internal\Codebase\InternalCallMapHandler; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\TaintSink; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\Comparator\CallableTypeComparator; use Psalm\Internal\Type\TemplateResult; @@ -866,6 +867,11 @@ private static function getAnalyzeNamedExpression( $removed_taints, ); } + + if ($added_taints !== []) { + $taint_source = TaintSource::fromNode($custom_call_sink); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 8f35fee56b3..ae3ecb72155 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -19,6 +19,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\DataFlow\TaintSink; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TemplateStandinTypeReplacer; @@ -738,6 +739,11 @@ private static function analyzeConstructorExpression( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== []) { + $taint_source = TaintSource::fromNode($custom_call_sink); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + foreach ($stmt_class_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( $parent_node, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php index 63c815ff521..c5b774238f4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php @@ -8,7 +8,9 @@ use Psalm\Context; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; +use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent; use Psalm\Type; use Psalm\Type\Atomic\TLiteralFloat; @@ -101,6 +103,11 @@ public static function analyze( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + if ($casted_part_type->parent_nodes) { foreach ($casted_part_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php index 3f6b2a8d19b..1f8cb15e4f9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php @@ -9,6 +9,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\TaintSink; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Issue\ForbiddenCode; use Psalm\IssueBuffer; use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent; @@ -60,6 +61,11 @@ public static function analyze( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== []) { + $taint_source = TaintSource::fromNode($eval_param_sink); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + foreach ($expr_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( $parent_node, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 9bc860de44d..b04cea8cc0d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -16,6 +16,7 @@ use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\Type\Comparator\AtomicTypeComparator; use Psalm\Internal\Type\Comparator\TypeComparisonResult; use Psalm\Internal\Type\Comparator\UnionTypeComparator; @@ -460,6 +461,11 @@ public static function taintArrayFetch( $stmt_type = $stmt_type->setParentNodes([$new_parent_node->id => $new_parent_node]); + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($new_parent_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + if ($array_key_node) { $offset_type = $offset_type->setParentNodes([$array_key_node->id => $array_key_node]); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php index 6ad6983b76e..962ff335c80 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -22,6 +22,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\DataFlowNode; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\TemplateInferredTypeReplacer; @@ -899,6 +900,11 @@ public static function processTaints( } $type = $type->setParentNodes([$property_node->id => $property_node], true); + + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($var_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } } } else { self::processUnspecialTaints( @@ -975,6 +981,11 @@ public static function processUnspecialTaints( } $type = $type->setParentNodes([$localized_property_node->id => $localized_property_node], true); + + if ($added_taints !== [] && $statements_analyzer->data_flow_graph instanceof TaintFlowGraph) { + $taint_source = TaintSource::fromNode($localized_property_node); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } } private static function handleEnumName( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php index 6c4a36ab2bf..55f14ae7132 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php @@ -14,6 +14,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\DataFlow\TaintSink; +use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\Provider\NodeDataProvider; use Psalm\Issue\MissingFile; use Psalm\Issue\UnresolvableInclude; @@ -132,6 +133,11 @@ public static function analyze( $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); + if ($added_taints !== []) { + $taint_source = TaintSource::fromNode($include_param_sink); + $statements_analyzer->data_flow_graph->addSource($taint_source); + } + foreach ($stmt_expr_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( $parent_node, diff --git a/src/Psalm/Internal/DataFlow/TaintSource.php b/src/Psalm/Internal/DataFlow/TaintSource.php index 1777afe99f7..a69cf2eb1ce 100644 --- a/src/Psalm/Internal/DataFlow/TaintSource.php +++ b/src/Psalm/Internal/DataFlow/TaintSource.php @@ -7,4 +7,14 @@ */ final class TaintSource extends DataFlowNode { + public static function fromNode(DataFlowNode $node): self + { + return new self( + $node->id, + $node->label, + $node->code_location, + $node->specialization_key, + $node->taints, + ); + } } diff --git a/tests/Config/Plugin/EventHandler/AddTaints/AddTaintsInterfaceTest.php b/tests/Config/Plugin/EventHandler/AddTaints/AddTaintsInterfaceTest.php new file mode 100644 index 00000000000..2fd9f389282 --- /dev/null +++ b/tests/Config/Plugin/EventHandler/AddTaints/AddTaintsInterfaceTest.php @@ -0,0 +1,286 @@ +setIncludeCollector(new IncludeCollector()); + return new ProjectAnalyzer( + $config, + new Providers( + $this->file_provider, + new FakeParserCacheProvider(), + ), + new ReportOptions(), + ); + } + + public function setUp(): void + { + RuntimeCaches::clearAll(); + $this->file_provider = new FakeFileProvider(); + } + + public function testTaintBadDataVariables(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'project_analyzer->trackTaintedInputs(); + + $this->expectException(CodeException::class); + $this->expectExceptionMessageMatches('/TaintedHtml/'); + + $this->analyzeFile($file_path, new Context()); + } + + public function testTaintsArePassedByTaintedAssignments(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'project_analyzer->trackTaintedInputs(); + + $this->expectException(CodeException::class); + $this->expectExceptionMessageMatches('/TaintedHtml/'); + + $this->analyzeFile($file_path, new Context()); + } + + public function testTaintsAreOverriddenByRawAssignments(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'project_analyzer->trackTaintedInputs(); + // No exceptions should be thrown + + $this->analyzeFile($file_path, new Context()); + } + + public function testAddTaintsActiveRecord(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'Micky Mouse"; + } + + $user = new User(); + echo $user->name; + ', + ); + + $this->project_analyzer->trackTaintedInputs(); + + $this->expectException(CodeException::class); + $this->expectExceptionMessageMatches('/TaintedHtml/'); + + $this->analyzeFile($file_path, new Context()); + } + + public function testAddTaintsActiveRecordList(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + ' + */ + public static function findAll(): array { + $mockUser = new self(); + $mockUser->name = "

Micky Mouse

"; + + return [$mockUser]; + } + } + + foreach (User::findAll() as $user) { + echo $user->name; + } + ', + ); + + $this->project_analyzer->trackTaintedInputs(); + + $this->expectException(CodeException::class); + $this->expectExceptionMessageMatches('/TaintedHtml/'); + + $this->analyzeFile($file_path, new Context()); + } +} diff --git a/tests/Config/Plugin/EventHandler/AddTaints/TaintBadDataPlugin.php b/tests/Config/Plugin/EventHandler/AddTaints/TaintBadDataPlugin.php new file mode 100644 index 00000000000..437eb6fd4c6 --- /dev/null +++ b/tests/Config/Plugin/EventHandler/AddTaints/TaintBadDataPlugin.php @@ -0,0 +1,30 @@ + + */ + public static function addTaints(AddRemoveTaintsEvent $event): array + { + $expr = $event->getExpr(); + + if ($expr instanceof Variable && $expr->name === 'bad_data') { + return TaintKindGroup::ALL_INPUT; + } + + return []; + } +} diff --git a/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveAllTaintsPlugin.php b/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveAllTaintsPlugin.php new file mode 100644 index 00000000000..bbc38f14f32 --- /dev/null +++ b/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveAllTaintsPlugin.php @@ -0,0 +1,20 @@ + + */ + public static function removeTaints(AddRemoveTaintsEvent $event): array + { + return TaintKindGroup::ALL_INPUT; + } +} diff --git a/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveTaintsInterfaceTest.php b/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveTaintsInterfaceTest.php new file mode 100644 index 00000000000..0740cee4f6e --- /dev/null +++ b/tests/Config/Plugin/EventHandler/RemoveTaints/RemoveTaintsInterfaceTest.php @@ -0,0 +1,173 @@ +setIncludeCollector(new IncludeCollector()); + return new ProjectAnalyzer( + $config, + new Providers( + $this->file_provider, + new FakeParserCacheProvider(), + ), + new ReportOptions(), + ); + } + + public function setUp(): void + { + RuntimeCaches::clearAll(); + $this->file_provider = new FakeFileProvider(); + } + + + public function testRemoveAllTaints(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'project_analyzer->trackTaintedInputs(); + + $this->analyzeFile($file_path, new Context()); + } + + public function testRemoveTaintsSafeArrayKeyChecker(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__, 5) . DIRECTORY_SEPARATOR, + ' + + + + + + + + ', + ), + ); + + $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + ' [ + "safe_key" => $_GET["input"], + ], + ]; + output($build);', + ); + + $this->project_analyzer->trackTaintedInputs(); + + $this->analyzeFile($file_path, new Context()); + + $this->addFile( + $file_path, + ' [ + "safe_key" => $_GET["input"], + "a" => $_GET["input"], + ], + ]; + output($build);', + ); + + $this->project_analyzer->trackTaintedInputs(); + + $this->expectException(CodeException::class); + $this->expectExceptionMessageMatches('/TaintedHtml/'); + + $this->analyzeFile($file_path, new Context()); + } +} diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index 61747f6b35f..77f2fd8d94e 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -917,77 +917,6 @@ function b(int $e): int { return $e; } $this->analyzeFile($file_path, new Context()); } - public function testRemoveTaints(): void - { - $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - ' - - - - - - - - ', - ), - ); - - $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - ' [ - "safe_key" => $_GET["input"], - ], - ]; - output($build);', - ); - - $this->project_analyzer->trackTaintedInputs(); - - $this->analyzeFile($file_path, new Context()); - - $this->addFile( - $file_path, - ' [ - "safe_key" => $_GET["input"], - "a" => $_GET["input"], - ], - ]; - output($build);', - ); - - $this->project_analyzer->trackTaintedInputs(); - - $this->expectException(CodeException::class); - $this->expectExceptionMessageMatches('/TaintedHtml/'); - - $this->analyzeFile($file_path, new Context()); - } - public function testFunctionDynamicStorageProviderHook(): void { require_once __DIR__ . '/Plugin/StoragePlugin.php';