iframe 不读取 Chrome 中的 cookie
iframe not reading cookies in Chrome
Chrome 不允许 child iframe 读取自己的 cookie。
我有一个带有 child iframe 的 parent 网页:
- parent 在
https://first-site.com
- child 在
<iframe src="https://second-site.com">
(在 parent 内)
- cookie 设置为
- 路径:'/'
- 安全:真实
- httpOnly: false
- 域:'.second-site.com'
我控制这两个站点,并且我希望 iframe 在 iframe 中执行需要为 .second-site.com
读取 cookie 的操作。外层 parent 不需要知道任何关于此的信息。
它适用于所有浏览器,除了 Chrome。
Chrome 只是不让 child 页面自己的 cookie 可用于 child。
在自己的 window 中访问 child 页面并在所有浏览器中执行该操作,包括 Chrome。
我已经在所有排列中尝试了这两个选项:
- 为 cookie 设置
secure:false
或 secure:true
- 为 iframe 设置
sandbox="allow-same-origin allow-scripts"
,或删除 sandbox
属性
Chrome 有何不同,Chrome 中的 iframe 如何访问其自己的 cookie?
我的服务器自动设置了一个名为 SameSite
的相对较新的 cookie 属性。禁用此功能(同时保留问题中列出的设置)允许 iframe 访问其在 Chrome.
中的 cookie
另见 Chrome feature status & IETF draft
2020 年 8 月更新
Chrome 现在阻止未设置 SameSite
的 cookie,因此您需要将其明确设置为 samesite=none
和 secure=true
.
很久以前就问过这个问题,但它仍然相关。
我遇到了同样的问题并得到了部分解决方案。
您可以尝试在您的服务器上创建一个代理,它将请求重定向到目标域。这样,您将能够从响应中拦截 cookies,用所需参数覆盖它们,然后将它们发送到客户.
示例:
你的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:
就我而言,所有端点都在不同的域上
Chrome 不允许 child iframe 读取自己的 cookie。
我有一个带有 child iframe 的 parent 网页:
- parent 在
https://first-site.com
- child 在
<iframe src="https://second-site.com">
(在 parent 内) - cookie 设置为
- 路径:'/'
- 安全:真实
- httpOnly: false
- 域:'.second-site.com'
我控制这两个站点,并且我希望 iframe 在 iframe 中执行需要为 .second-site.com
读取 cookie 的操作。外层 parent 不需要知道任何关于此的信息。
它适用于所有浏览器,除了 Chrome。
Chrome 只是不让 child 页面自己的 cookie 可用于 child。
在自己的 window 中访问 child 页面并在所有浏览器中执行该操作,包括 Chrome。
我已经在所有排列中尝试了这两个选项:
- 为 cookie 设置
secure:false
或secure:true
- 为 iframe 设置
sandbox="allow-same-origin allow-scripts"
,或删除sandbox
属性
Chrome 有何不同,Chrome 中的 iframe 如何访问其自己的 cookie?
我的服务器自动设置了一个名为 SameSite
的相对较新的 cookie 属性。禁用此功能(同时保留问题中列出的设置)允许 iframe 访问其在 Chrome.
另见 Chrome feature status & IETF draft
2020 年 8 月更新
Chrome 现在阻止未设置 SameSite
的 cookie,因此您需要将其明确设置为 samesite=none
和 secure=true
.
很久以前就问过这个问题,但它仍然相关。
我遇到了同样的问题并得到了部分解决方案。
您可以尝试在您的服务器上创建一个代理,它将请求重定向到目标域。这样,您将能够从响应中拦截 cookies,用所需参数覆盖它们,然后将它们发送到客户.
示例:
你的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: 就我而言,所有端点都在不同的域上