PHP 脚本无法打开某些网址

PHP script can't open certain URLs

我正在通过 Axios 调用一个 PHP 脚本来检查作为参数传递给它的 URL 是否可以嵌入到 iframe 中。该 PHP 脚本从使用 $_GET[] 打开 URL 开始。

奇怪的是,带有 cross-origin-opener-policy: same-origin 的页面(如 https://twitter.com/) can be opened with $_GET[], whereas a page with Referrer Policy: strict-origin-when-cross-origin (like 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

这只是我对您的代码可能存在的错误的粗略猜测。

我注意到你这样做了:

来自 $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 如果你想 false0true1 然后改变 $response = true;$response = 1; 为 true 和 $response = false; $response = 0; 到处都是 false

我希望能有所帮助

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

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

中的参数

当您尝试访问与 Javascript 运行 所在的域不同的域时,您看到的是标准 CORS 错误。 CORS 表示 Javascript 运行 在一台主机上无法向另一台主机发出 http 请求,除非另一台主机明确允许。在这种情况下,位于 http://localhost:3000 的 Javascript 运行ning 正在向远程站点 https://path.to.cdn/ 发出 http 请求。这是一个 cross-origin 请求(localhost !== path.to.cdn),server/script 在 path.to.cdn 上接收到该请求不是 returning 任何特定的 CORS headers 允许该请求,因此请求被阻止。

请注意,如果 the request is classed as "simple",它 实际上 运行。因此,您的 PHP 一直在工作,但是如果正确的 headers 未被 return 编辑,结果将被阻止显示在您的浏览器中。这可能会导致混淆 bcs,例如,当它从慢速站点获取 headers 时,您可能会注意到延迟,而对于快速站点来说它非常快。或者,也许您的日志记录一直在工作,尽管您的浏览器中没有显示任何内容。

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

  1. iframeHeaderChecker 更新为 return 适当的 CORS headers,以便允许您的 cross-origin JS 请求。快速地,不安全黑客允许从任何人和任何地方访问不是一个好主意长期!)您可以添加:

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

    但最好更新它以更具体地限制对您的应用程序的访问,而不是其他人。您必须根据您的应用程序和基础架构的具体情况评估最佳方法。这里有很多关于 CORS/PHP/AJAX 的问题,以供参考。您还可以在 Web 服务器级别而不是应用程序级别配置它,例如 here's how to configure Apache to return those headers.

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