N°2154 - Security breach

This commit is contained in:
bruno DA SILVA
2020-01-02 14:12:42 +01:00
committed by odain
parent ee61c1e8fb
commit c115f64cb5
247 changed files with 22350 additions and 91 deletions

View File

@@ -0,0 +1,143 @@
<?php
/**
* Created by Bruno DA SILVA, working for Combodo
* Date: 31/12/2019
* Time: 14:12
*/
namespace Combodo\iTop\Config\Validator;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class ConfigNodesVisitor extends NodeVisitorAbstract
{
private $aAllowedNodeClasses = array();
public function __construct()
{
$this->aAllowedNodeClasses = array(
Node\Scalar::class,
Node\Name::class,
Node\Const_::class,
Node\Expr\Array_::class,
Node\Expr\ArrayDimFetch::class,
Node\Expr\ArrayItem::class,
Node\Expr\Assign::class,
Node\Expr\AssignOp::class,
Node\Expr\AssignRef::class,
Node\Expr\BinaryOp::class,
Node\Expr\BitwiseNot::class,
Node\Expr\BooleanNot::class,
Node\Expr\Cast::class,
Node\Expr\ClassConstFetch::class,
Node\Expr\ConstFetch::class,
Node\Expr\Instanceof_::class,
Node\Expr\Isset_::class,
Node\Expr\List_::class,
Node\Expr\PostDec::class,
Node\Expr\PostInc::class,
Node\Expr\PreDec::class,
Node\Expr\PreInc::class,
Node\Expr\Print_::class,
Node\Expr\Ternary::class,
Node\Expr\UnaryMinus::class,
Node\Expr\UnaryPlus::class,
Node\Expr\Variable::class,
Node\Stmt\Const_::class,
Node\Stmt\Global_::class,
);
}
/**
* @param \PhpParser\Node $node
*
* @return int|\PhpParser\Node|void|null
* @throws \Exception
*/
public function enterNode(Node $node)
{
$this->ValidateNode($node);
}
/**
* @param \PhpParser\Node $node
*
* @throws \Exception
*/
public function ValidateNode(Node $node)
{
foreach ($this->aAllowedNodeClasses as $sAllowedNodeClass)
{
if ($node instanceof $sAllowedNodeClass)
{
return;
}
}
$this->ThrowInvalidConf($node);
}
/**
* @param \PhpParser\Node $node
*
* @throws \Exception
*/
private function ThrowInvalidConf(Node $node)
{
if (in_array('name', $node->getSubNodeNames()))
{
$sMessage = sprintf(
"Invalid configuration: %s of type %s is forbidden in line %d",
$node->name,
$node->getType(),
$node->getLine()
);
}
elseif (in_array('class', $node->getSubNodeNames()))
{
if (in_array('name', $node->class->getSubNodeNames()))
{
$sMessage = sprintf(
"Invalid configuration: usage of the class '%s' (%s) is forbidden in line %d",
is_object($node->class) ? $node->class->name : $node->class,
$node->getType(),
$node->getLine()
);
}
else
{
$sMessage = sprintf(
"Invalid configuration: usage of %s is forbidden in line %d",
$node->getType(),
$node->getLine()
);
}
}
elseif ($node->hasAttribute('name'))
{
$sMessage = sprintf(
"Invalid configuration: %s of type %s is forbidden in line %d",
$node->getAttribute('name'),
$node->getType(),
$node->getLine()
);
}
else
{
$sMessage = sprintf(
"Invalid configuration: %s is forbidden in line %d",
$node->getType(),
$node->getLine()
);
}
throw new \Exception($sMessage);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Created by Bruno DA SILVA, working for Combodo
* Date: 31/12/2019
* Time: 12:29
*/
namespace Combodo\iTop\Config\Validator;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
class iTopConfigAstValidator
{
/**
* validate.
*
* @param $sConfig
* @param \PhpParser\Parser|null $oParser
*
* @throws \Exception
*/
public function validate($sConfig)
{
$oParser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$oNodeVisitor = new ConfigNodesVisitor();
try {
$aInitialNodes = $oParser->parse($sConfig);
} catch (\Error $e) {
$sMessage = 'Invalid configuration: '. \Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
throw new \Exception($sMessage, 0, $e);
}catch (\Exception $e) {
$sMessage = 'Invalid configuration: '. \Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
throw new \Exception($sMessage, 0, $e);
}
$oTraverser = new NodeTraverser();
$oTraverser->addVisitor($oNodeVisitor);
$oTraverser->traverse($aInitialNodes);
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Created by Bruno DA SILVA, working for Combodo
* Date: 31/12/2019
* Time: 12:29
*/
namespace Combodo\iTop\Config\Validator;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\Parser;
use PhpParser\ParserFactory;
class iTopConfigSyntaxValidator
{
/**
* validate
*
* @param $sConfig
* @param $bAllowUnsecure
*/
public function validate($sConfig, $bAllowUnsecure)
{
exec('php -v', $aOutput, $iReturnVar);
$bCanRunCli = ($iReturnVar == 0);
if ($bCanRunCli)
{
$this->CheckSyntaxSecure($sConfig);
}
elseif($bAllowUnsecure)
{
$this->CheckSyntaxNotSecure($sConfig);
}
else
{
throw new \Exception('Cannot check configuration syntax: PHP CLI is not accessible.'."\n".implode("\n", $aOutput));
}
}
/**
* This will use the php cli linter in order to check the syntax,
*
* The php cli may not be based on the same php version, but since the cron run using the cli, we can assume that it is well configured anyway...
* Also, the config syntax is very limited so there should not be a problem with checking the validity against another php version
*
* @param $sConfig
* @param $iReturnVar
* @param $aOutput
*
* @return array
*/
private function CheckSyntaxSecure($sConfig)
{
$sTempFile = tempnam(sys_get_temp_dir(), 'syntax_check_me_').'.temp.txt';
file_put_contents($sTempFile, $sConfig);
exec("php -l $sTempFile 2>&1", $aOutput, $iReturnVar);
unlink($sTempFile);
if ($iReturnVar != 0)
{
throw new \Exception(implode("\n", $aOutput));
}
}
/**
* @param $sRawConfig
*/
private function CheckSyntaxNotSecure($sRawConfig)
{
try
{
ini_set('display_errors', 1);
ob_start();
// in PHP < 7.0.0 syntax errors are in output
// in PHP >= 7.0.0 syntax errors are thrown as Error
$sConfig = preg_replace(array('#^\s*<\?php#', '#\?>\s*$#'), '', $sRawConfig);
eval('if(0){'.trim($sConfig).'}');
$sNoise = trim(ob_get_contents());
ob_end_clean();
}
catch (Error $e)
{
// ParseError only thrown in PHP7
throw new Exception('Error in configuration: '.$e->getMessage().' at line '.$e->getLine());
}
if (strlen($sNoise) > 0)
{
if (preg_match("/(Error|Parse error|Notice|Warning): (.+) in \S+ : eval\(\)'d code on line (\d+)/i", strip_tags($sNoise), $aMatches))
{
$sMessage = $aMatches[2];
$sLine = $aMatches[3];
$sMessage = Dict::Format('config-parse-error', $sMessage, $sLine);
throw new Exception($sMessage);
}
else
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new Exception('Syntax error in configuration file: <tt>'.$sNoise.'</tt>');
}
}
}
}