【问题标题】:Calling a PHP class method using AJAX使用 AJAX 调用 PHP 类方法
【发布时间】:2023-03-31 04:40:03
【问题描述】:

我在我的 PHP 类中通过 AJAX 调用了以下代码:

PHP:

class Ajax extends Controller {

    private $class;
    private $method;
    private $params;

    function __construct()
    {
        $this->params = $_POST; // Call params
        $call = explode('->', $this->params['call']);
        $this->class = new $call[0]; // e.g. controller->method
        $this->method = $call[1];
        array_shift($this->params);
        $this->parse();
    }

    public function index()
    {
        //Dummy
    }

    public function parse()
    {
        $r = '';
        $r = call_user_func_array(array($this->class, $this->method), $this->params);
        echo $r;
    }
}

客户:

function creditCheck2(id)
{
    $.post(ROOT + 'Ajax', {call: 'Record->creditState', id: id, enquiryid: enquiryId}, function(data) {
        alert(data)
    }, 'json')
}

它似乎工作得很好,但它安全吗?还能做得更好吗?


仅供参考,我已经添加了我的代码以及答案建议的更改:

class Call extends Controller {

  private $class;
  private $method;
  private $params;
  private $authClasses = array(
      'Gallery'
  );

  function __construct()
  {
    $this->params = $_POST; // Call params
    $call = explode('->', $this->params['call']);

    if(!in_array($call[0], $this->authClasses))
    {
      die();
    }
    $this->class = new $call[0]; // e.g. controller->method
    $this->method = $call[1];
    unset($this->params['call']);
    $this->parse();
  }

  public function parse()
  {
    $r = '';
    $param = array();

    // Params in any order...
    $mRef = new ReflectionMethod($this->class, $this->method);
    foreach($mRef->getParameters() as $p) {
      $param[$p->name] = $this->params[$p->name];
    }
    $this->params = $param;

    if($r = @call_user_func_array(array($this->class, $this->method), $this->params))
    {
      echo $r;
    }
    else {

    }
  }

}

【问题讨论】:

  • 我个人使用 __call 魔术方法并保护该方法,从而消除任何意外错误。此外,根据您的方法执行的逻辑,您可能希望向参数添加某种过滤器。
  • @IanBrindley 过滤器是指传递另一个定义其他参数是什么数据类型的参数吗?例如...id:id,enquiryid:enquiryId,过滤器:'numeric'...
  • @imperium2335,我已经更新了反射样本。所有功劳归功于 Jon 指出的参考。

标签: php javascript ajax oop code-injection


【解决方案1】:

小问题

array_shift($this->params) 不必要地假定 params 数组中的第一项总是call 可能会更好。这不是真的,它不同意您稍早做的直接访问$this->params['call']array_shift 应该简单地替换为 unset($this->params['call'])

更大的问题

还有一个问题是参数数组中的值顺序必须与您尝试调用的方法的签名中的参数顺序相匹配。我不认为有保证顺序将与AJAX请求中的参数顺序相同,所以这是一个理论上的问题。

非常大的问题

更重要的是,这种处理方式强制 AJAX 代码的作者在您尝试调用的方法的签名中匹配参数的顺序。 这引入了可怕的耦合,是一个主要问题。 更糟糕的是,错误地更改参数的顺序不会很明显。考虑:

public function bankTransfer($fromAccount, $toAccount, $amount);

$.post(ROOT + 'Ajax', {
    call: 'Bank->bankTransfer', 
    from: "sender",
    to: "recipient",
    amount: 42
}, function(data) { ... });

这会奏效。但如果你这样做

$.post(ROOT + 'Ajax', {
    call: 'Bank->bankTransfer', 
    to: "recipient", // swapped the order of
    from: "sender",  // these two lines
    amount: 42
}, function(data) { ... });

您将得到与预期相反的结果。我相信很明显这是非常糟糕

要解决此问题,您必须使用反射将$this->params 中的数组键与被调用方法的参数的正式名称匹配。

安全

最后,这段代码是不安全的,因为任何人都可以发出请求,指示您的代码使用适当的参数调用任何类的任何方法——甚至是不应从 Web 环境访问的方法。

这是另一个严重的问题,除非您在调度逻辑中引入某种类型的过滤,否则无法真正解决。

【讨论】:

  • +1 谢谢乔恩,这是个好主意,将研究反射。能给我举个简单的例子吗?
  • @imperium2335:基本上你创建一个ReflectionMethod 实例并调用getParameters。这将返回您可以查询的ReflectionParameter 对象数组——它们有一个getName 方法等。因此,您将getName$this->params 中的数组键相匹配,以发现传递参数的顺序。例如参见php.net/manual/en/reflectionmethod.invokeargs.php#100041,这不是一种真正的做事方式,但它说明了我所描述的内容。
【解决方案2】:

它似乎工作得很好,但它安全吗?还能做得更好吗?

您是使用自己的框架还是使用其他框架?如果攻击者知道您的框架内可能存在什么,我相信它根本不安全。例如:你的框架中有数据库类,攻击者可以这样做:

{call: 'Database->execute', sql: 'SELECT * FROM information_schema.`tables`'}

过滤

您可以限制允许用户访问的课程数量。例如:

if (!in_array($this->class, array('Record', 'Hello'))) {
    die();
}

反射

这是我刚刚学习的反射示例(感谢@Jon 提供参考)。这解决了传递参数的顺序与 PHP 函数不同的问题。

class Email
{
    public function send($from, $to, $msg) {
        return "Send $from to $to: $msg";
    }
}

$rawParam = array('msg' => 'Hello World', 
               'to' => 'to@gmail.com', 
               'from' => 'from@gmail.com');
$param = array();

// Rearrange
$methodRef = new ReflectionMethod('Email', 'send');
foreach($methodRef->getParameters() as $p) {
    $param[$p->name] = $rawParam[$p->name];
}

var_dump($rawParam);
var_dump($param);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-27
    • 2015-07-04
    • 2016-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多