-1

我通过AxiosPHP 脚本调用,检查作为参数传递给它的 URL 是否可以嵌入到 iframe 中。该 PHP 脚本以使用$_GET[].

奇怪的是,带有cross-origin-opener-policy: same-origin(如https://twitter.com/)的页面可以打开$_GET[],而带有Referrer Policy: strict-origin-when-cross-origin(如https://calia.order.liven.com.au/)的页面不能。

我不明白为什么,这很烦人,因为对于无法打开的页面,$_GET[]我无法对它们执行检查 - 脚本失败(意味着我没有得到响应并且Axios调用运行catch()块)。

所以基本上有 3 种类型的页面:(1) 允许 iframe 嵌入的那些,(2) 不允许的,以及 (3) 不仅不允许而且甚至无法打开执行的烦人的页面这张支票。

有没有办法用 PHP 打开任何页面,如果没有,我该怎么做才能防止我的脚本在几秒钟后失败?

PHP 脚本:

$source = $_GET['url'];
$response = true;

try {
  $headers = get_headers($source, 1);
  $headers = array_change_key_case($headers, CASE_LOWER);

  if (isset($headers['content-security-policy'])) {
    $response = false;
  }
  else if (isset($headers['x-frame-options']) &&
    $headers['x-frame-options'] == 'DENY' ||
    $headers['x-frame-options'] == 'SAMEORIGIN'
  ) {
    $response = false;
  }
} catch (Exception $ex) {
  $response = $ex;
}

echo $response;

编辑:下面是控制台错误。

Access to XMLHttpRequest at 'https://path.to.cdn/iframeHeaderChecker?url=https://calia.order.liven.com.au/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CustomLink.vue?b495:61 Error: Network Error
    at createError (createError.js?2d83:16)
    at XMLHttpRequest.handleError (xhr.js?b50d:84)
VM4758:1 GET https://path.to.cdn/iframeHeaderChecker?url=https://calia.order.com.au/ net::ERR_FAILED
4

2 回答 2

3

您显示的错误来自 Javascript,而不是来自 PHP。 get_headers()返回false失败,它不会抛出异常 -catch()永远不会发生。 get_headers()只需发出一个 http 请求,例如您的浏览器或 curl,失败的唯一原因是 URL 格式错误或远程站点已关闭等。

http://localhost:3000被阻止的是使用 Javascript 进行的访问https://path.to.cdn/iframeHeaderChecker,而不是 PHP 对您作为参数传递的 URL 的访问$_GET['url']

当您尝试访问与运行 Javascript 的域不同的域时,您看到的是标准 CORS 错误。 CORS意味着在一台主机上运行的 Javascript 不能向另一台主机发出 http 请求,除非另一台主机明确允许。在这种情况下,运行的 Javascript 正在http://localhost:3000向远程站点发出 http 请求https://path.to.cdn/。这是一个跨域请求(localhost !== path.to.cdn),并且接收该请求的服务器/脚本path.to.cdn没有返回任何允许该请求的特定 CORS 标头,因此该请求被阻止。

请注意,如果请求被归类为 "simple",它将实际运行。因此,您的 PHP 一直在工作,但是没有返回正确的标头,结果被阻止显示在您的浏览器中。这可能会导致混淆 bcs,例如,当它从慢速站点获取标头时,您可能会注意到延迟,而对于快速站点来说,它是超快的。或者,尽管您的浏览器中没有显示任何内容,但您看到的日志记录一直在工作。

我的理解是https://path.to.cdn/iframeHeaderChecker你的 PHP 脚本,你在问题中显示的一些代码?如果是这样,您有 2 个选择:

  1. 更新iframeHeaderChecker以返回适当的 CORS 标头,以便允许您的跨域 JS 请求。作为一种快速、不安全的黑客攻击,允许任何人在任何地方进行访问(从长远来看,这不是一个好主意!),您可以添加:

    header("Access-Control-Allow-Origin: *");
    

    但最好更新它以更具体地限制对您的应用程序的访问,而不是对其他所有人的访问。您必须根据应用程序和基础架构的具体情况来评估最佳方式。这里有很多关于 CORS/PHP/AJAX 的问题,以供参考。您还可以在 Web 服务器级别而不是应用程序级别进行配置,例如这里是如何配置 Apache 以返回这些标头

  2. 如果iframeHeaderChecker是与调用它的 Javascript 相同的应用程序的一部分,它是否也可以在本地使用http://localhost:3000?如果是这样,请更新您的 JS 以使用本地版本,而不是远程版本,这样就path.to.cdn可以避免整个问题!

于 2021-04-22T09:16:47.253 回答
0

这只是我对您的代码可能有什么问题的粗略猜测。

我注意到你这样做:

比较来自$headers但不确保它们与您比较的值具有相同大写字母的值。应用:strtoupper()

检查 isset() 但不测试 key_exist 在应用之前是否存在:key_exist()

检查 isset() 但也许你应该使用!empty()而不是isset() 比较结果:

$value = "";
var_dump(isset($value)); // (bool) true
var_dump(!empty($value)); // (bool) false

$value = "something";
var_dump(isset($value)); // (bool) true
var_dump(!empty($value)); // (bool) true

unset($value);
var_dump(isset($value)); // (bool) false
var_dump(!empty($value)); // (bool) false

应用更改的代码:

<?php

error_reporting(E_ALL);
declare(strict_types=1);

header('Access-Control-Allow-Origin: *');

ob_start();

try {

    $response = true;

    if (!key_exists('url', $_GET)) {
        $msg = '$_GET does not have a key "url"';
        throw new \RuntimeException($msg);
    }
    $source = $_GET['url'];

    if ($source !== filter_var($source, \FILTER_SANITIZE_URL)) {
        $msg = 'Passed url is invaid, url: ' . $source;
        throw new \RuntimeException($msg);
    }

    if (filter_var($source, \FILTER_VALIDATE_URL) === FALSE) {
        $msg = 'Passed url is invaid, url: ' . $source;
        throw new \RuntimeException($msg);
    }

    $headers = get_headers($source, 1);

    if (!is_array($headers)) {
        $msg = 'Headers should be array but it is: ' . gettype($headers);
        throw new \RuntimeException($msg);
    }

    $headers = array_change_key_case($headers, \CASE_LOWER);

    if (  key_exists('content-security-policy', $headers) &&
            isset($headers['content-security-policy'])
        ) {
            $response = false;
    }
    elseif (  key_exists('x-frame-options', $headers) && 
                (
                    strtoupper($headers['x-frame-options']) == 'DENY' ||
                    strtoupper($headers['x-frame-options']) == 'SAMEORIGIN'
                )
    ) {
            $response = false;
    }

} catch (Exception $ex) {
    $response = "Error: " . $ex->getMessage() . ' at: ' . $ex->getFile() . ':' . $ex->getLine();
}

$phpOutput = ob_get_clean();
if (!empty($phpOutput)) {
    $response .= \PHP_EOL . 'PHP Output: ' . $phpOutput;
}

echo $response;

使用Throwable代替Exception也会捕获 PHP7 中的错误。

请记住:

$response = true;
echo $response; // prints "1"

$response = false;
echo $response; // prints ""

所以$response = false你会得到一个空字符串,而不是0 如果你想拥有0forfalse然后将to更改1为true 和to for无处不在。true$response = true;$response = 1;$response = false;$response = 0;false

我希望以某种方式有所帮助

于 2021-04-20T03:11:43.177 回答