【问题标题】:iframe not reading cookies in Chromeiframe 未在 Chrome 中读取 cookie
【发布时间】:2017-12-19 01:37:19
【问题描述】:

Chrome 不允许子 iframe 读取自己的 cookie。

我有一个带有子 iframe 的父网页:

  • 父母https://first-site.com
  • <iframe src="https://second-site.com"> 的子级(在父级内部)
  • cookie 设置为
    • 路径:'/'
    • 安全:真
    • httpOnly: 错误
    • 域:'.second-site.com'

我控制这两个站点,并且我希望 iframe 在 iframe 中执行需要读取 .second-site.com 的 cookie 的操作。外部父级不需要对此有任何了解。

它适用于除 Chrome 之外的所有浏览器。

Chrome 根本不会让子页面自己的 cookie 对孩子可用。

在其自己的窗口中访问子页面并执行该操作适用于所有浏览器,包括 Chrome。

我已经在所有排列中尝试了这两个选项:

  • 为 cookie 设置 secure:falsesecure:true
  • 为 iframe 设置 sandbox="allow-same-origin allow-scripts",或删除 sandbox 属性

Chrome 有何不同,Chrome 中的 iframe 如何访问自己的 cookie?

【问题讨论】:

    标签: javascript google-chrome iframe cookies cross-domain


    【解决方案1】:

    这个问题是很久以前提出的,但它仍然相关。

    我遇到了同样的问题并得到了部分解决方案。

    您可以尝试在您的服务器上创建一个代理,它将请求重定向到目标域。这样,您将能够从响应中拦截 cookie,用所需的参数覆盖它们,然后将它们发送给客户端

    例子:

    你的index.html

    <html>
        <head>
            <meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-eval' 'unsafe-inline' data: gap: https://ssl.gstatic.com 'unsafe-eval'; frame-src *; connect-src *; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
        </head>
        <body>
            <script>
                const uuidv4 = () => ([1e7]+1e3+4e3+8e3+1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
                const id = localStorage.getItem('id') || uuidv4();
                localStorage.setItem('id', id);
                const iframe = document.createElement('iframe');
                iframe.setAttribute('width', '100%');
                iframe.setAttribute('height', '100%');
                iframe.setAttribute('src', `https://proxy.domain/?proxy_layer_id=${id}`);
                document.body.appendChild(iframe);
            </script>
        </body>
    </html>
    

    你的代理服务器index.php

    <?php
    
    error_reporting(E_ALL);
    ini_set("display_errors", "1");
    
    $defaultTargetDomain = "target.domain";
    $proxyDomain = "proxy.domain/";
    
    $ck = $_COOKIE;
    $userAgent = $_SERVER["HTTP_USER_AGENT"] ?? null;
    $id = $_GET["proxy_layer_id"] ?? null;
    $remoteDomain = $_GET["proxy_layer_domain"] ?? $defaultTargetDomain; // Use the default target domain if no other domain has been specified
    $remotePath = $_GET["proxy_layer_path"] ?? null;
    $sw = $_GET["proxy_layer_sw"] ?? false;
    
    /* Decode remote domain */
    if ($remoteDomain != null) {
        $remoteDomain = urldecode($remoteDomain);
    }
    
    /* Decode and parse remote path */
    if ($remotePath != null) {
       $_url = parse_url($remotePath);
       $remotePath = $_url["path"]."?".($_url["query"] ?? "");
    }
    
    /* Generate service worker if requested */
    if ($sw) {
        $sw = file_get_contents("./sw.js");
        $sw = str_replace("%id%", $id, $sw);
        $sw = str_replace("%target%", $remoteDomain, $sw);
        $sw = str_replace("%proxy%", $proxyDomain, $sw);
        header("Content-Type: application/javascript; charset=UTF-8");
        print($sw);
        return;
    }
    
    function processCookies(&$cookies = []) {
        foreach ($cookies as $cookie) {
            $values = trim(explode(":", $cookie)[1] ?? "");
            if (strlen($values) < 1) continue;
            $values = explode("; ", $values);
            foreach ($values as $value) {
                list($key, $value) = explode("=", urldecode($value));
                $ck[$key] = $value;
            }
        }
    }
    
    /* Generate final target URL */
    $site = "https://" . $remoteDomain . $remotePath;
    
    $request = $_SERVER["REQUEST_URI"];
    
    $ch = curl_init();
    
    /* If there was a POST request, then forward that as well*/
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $_POST);
    }
    curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
    curl_setopt($ch, CURLOPT_URL, $site . $request);
    curl_setopt($ch, CURLOPT_HEADER, true);
    
    $headers = getallheaders();
    
    /* Translate some headers to make the remote party think we actually browsing that site */
    $extraHeaders = [];
    if (isset($headers["Referer"])) {
        $extraHeaders[] = "Referer: " . str_replace($proxyDomain, $remoteDomain, $headers["Referer"]);
    }
    if (isset($headers["Origin"])) {
        $extraHeaders[] = "Origin: " . str_replace($proxyDomain, $remoteDomain, $headers["Origin"]);
    }
    
    /* Forward cookie as it came.  */
    curl_setopt($ch, CURLOPT_HTTPHEADER, $extraHeaders);
    
    $cookie = [];
    
    foreach ($ck as $key => $value) {
        $cookie[] = "{$key}={$value}";
    }
    
    if (count($cookie)) {
        curl_setopt($ch, CURLOPT_COOKIE, implode("; ", $cookie));
    }
    
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    
    $response = curl_exec($ch);
    
    $lastUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    
    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    
    curl_close($ch);
    
    $headers = substr($response, 0, $header_size);
    $body = substr($response, $header_size);
    
    $headerArray = explode(PHP_EOL, $headers);
    
    $cookies = [];
    $setCookies = [];
    
    /* Process response headers */
    foreach($headerArray as $header) {
        $skip = false;
    
        $colonPos = strpos($header, ":");
    
        if ($colonPos !== false) {
            $headerName = strtolower(substr($header, 0, $colonPos));
    
            /* Serialize html response */
            if (trim($headerName) == "content-type" && strpos($header, "text/html") !== false) {
                $query = http_build_query([
                    "proxy_layer_id" => $id,
                    "proxy_layer_domain" => $remoteDomain,
                    "proxy_layer_sw" => true
                ]);
    
                /* Generate injection */
                $injection = file_get_contents("./injection.js");
                str_replace("%query%", $query, $injection);
    
                /* Add your own js to install the service worker */
                $body = str_replace("<head>", "<head><script>$injection</script>", $body);
    
                $dom = new DOMDocument();
                @$dom->loadHTML($body);
    
                /* Get all hyperlinks */
                $links = $dom->getElementsByTagName('a');
    
                /* Proxy all target hyperlinks */
                foreach ($links as $link) {
                    $href = $link->getAttribute('href');
                    $scheme = parse_url($href, PHP_URL_SCHEME);
                    if (!$href || $href[0] === "#" || $scheme) continue;
                    $prefix = $href[0] == "/" ? "" : "/";
                    $path = $prefix . $href;
                    $query = [
                        "proxy_layer_id" => $id,
                        "proxy_layer_domain" => $remoteDomain,
                        "proxy_layer_path" => $path
                    ];
                    $newLink = "https://" . $proxyDomain . "?" . http_build_query($query);
                    $body = str_replace("\"$href\"", "\"$newLink\"", $body);
                }
            }
    
            /* Ignore content headers, let the webserver decide how to deal with the content */
            //if (trim($headerName) == "content-encoding") continue;
            //if (trim($headerName) == "content-length") continue;
            //if (trim($headerName) == "transfer-encoding") continue;
            //if (trim($headerName) == "location") continue;
    
            /* Ignore and get all cookie headers */
            if (trim($headerName) == "cookie") {
                $cookies[] = $header;
                $skip = true;
            }
    
            /* Change cookie domain for the proxy */
            if (trim($headerName) == 'set-cookie') {
                $header = str_replace('domain=' . $remoteDomain, 'domain=' . $proxyDomain, $header);
                $setCookies[] = $header;
                $skip = true;
            }
        }
    
        if ($skip) continue;
    
        header($header, false);
    }
    
    /* Merge all cookies */
    processCookies($cookies);
    processCookies($setCookies);
    
    /* Set cookies */
    foreach ($ck as $key => $value) {
        setcookie($key, $value, ["secure" => true, "samesite" => "None"]);
    }
    
    echo $body;
    

    你的 sw.js

    /**
    This service worker intercepts all http requests and redirects them to the proxy server
    */
    
    const id = '%id%';
    const target = '%target%';
    const proxy = '%proxy%';
    
    self.addEventListener('install', (event) => {
        event.waitUntil(self.skipWaiting());
        self.skipWaiting();
        console.log('Installed', event);
    });
    
    self.addEventListener('activate', (event) => {
        event.waitUntil(self.clients.claim());
        console.log('Activated', event);
    });
    
    self.addEventListener('fetch', (event) => {
        const url = decodeURI(event.request.url);
        if (!url.startsWith(location.origin) && !url.startsWith(`https://${proxy}`) && !url.startsWith(location.host) && (url.startsWith('http') || url.includes(target))) return; // Filter
        const [path, params] = (() => {
            if (url.startsWith('http')) {
                return url.split('/').slice(location.origin.split('/').length).join('/').split('?');
            } else {
                return url.split('?');
            }
        })();
        const request = new Request(encodeURI(`https://${proxy}?proxy_layer_id=${id}&proxy_layer_domain=${target}${typeof path === 'string' && path.length > 1 ? `&proxy_layer_path=/${path}` : ''}${typeof params === 'string' && params.length > 0 ? `&${params}` : ''}`), event.request);
        event.respondWith(new Promise((resolve, reject) => fetch(request).then(resolve).catch(e => {
            console.log("e", e);
            reject(e);
        })));
    });
    

    你的 injection.js

    const serwiceWorkerPath = '/';
    const query = '%query%';
    
    navigator.serviceWorker.register(`${serviceWorkerPath}?${query}`).then(() => {
        return navigator.serviceWorker.ready;
    }).then((reg) => {
        console.log('Service Worker is ready', reg);
        if (!localStorage.getItem('sw')) { // Reload if the service worker installed for the first time
            localStorage.setItem('sw', true);
            location.reload();
        }
    }).catch((error) => {
        console.log('Error: ', error);
    });
    

    因此,您的 iframe 的内容将在 document.cookie 中包含实际的 cookie。

    PS: 就我而言,所有端点都在不同的域上

    【讨论】:

      【解决方案2】:

      我的服务器自动设置了一个名为SameSite 的相对较新的cookie 属性。禁用此功能(同时保留问题中列出的设置)允许 iframe 在 Chrome 中访问其自己的 cookie。

      另见Chrome feature status & IETF draft

      2020 年 8 月更新

      Chrome 现在会阻止未设置 SameSite 的 cookie,因此您需要将其显式设置为 samesite=nonesecure=true

      【讨论】:

      • 如何禁用 SameSite 属性?
      • 也必须设置安全标志! (仅仅使用 HTTPS 是不够的)
      • 此解决方案不再适用于 Chrome 84 版本。请指教。
      • @ChrisRuck 已更新;现在您必须将 SameSite 显式设置为“无”
      • 有人能告诉我这是否仍然有效/在 2021 年再次有效吗?我的 chrome 版本是:90.0.4430.93
      猜你喜欢
      • 2013-03-26
      • 2013-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-11
      • 2020-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多