교차 출처 네트워크 요청

일반 웹페이지는 fetch() 또는 XMLHttpRequest API를 사용하여 원격 서버에서 데이터를 주고받을 수 있지만 동일한 출처 정책으로 제한됩니다. 콘텐츠 스크립트는 콘텐츠 스크립트가 삽입된 웹 출처를 대신하여 요청을 시작하므로 콘텐츠 스크립트에도 동일한 출처 정책이 적용됩니다. 확장 프로그램 출처는 그다지 제한되지 않습니다. 확장 프로그램 서비스 워커 또는 포그라운드 탭에서 실행되는 스크립트는 확장 프로그램이 교차 출처 권한을 요청하는 한 원본 외부의 원격 서버와 통신할 수 있습니다.

확장 프로그램 출처

실행 중인 각 확장 프로그램은 별도의 자체 보안 원본에 있습니다. 추가 권한을 요청하지 않고도 확장 프로그램은 fetch()를 호출하여 설치 내의 리소스를 가져올 수 있습니다. 예를 들어 확장 프로그램의 config_resources/ 폴더에 config.json라는 JSON 구성 파일이 포함되어 있으면 확장 프로그램은 다음과 같이 파일 콘텐츠를 검색할 수 있습니다.

const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();

확장 프로그램이 https://www.google.com과 같이 자체 이외의 보안 출처를 사용하려고 하는 경우, 확장 프로그램이 적절한 교차 출처 권한을 요청하지 않는 한 브라우저에서 이를 허용하지 않습니다.

교차 출처 권한 요청

확장 프로그램의 출처 외부의 원격 서버에 대한 액세스를 요청하려면 매니페스트 파일의 host_permissions 섹션에 호스트, 일치 패턴 또는 둘 다를 추가합니다.

{
  "name": "My extension",
  ...
  "host_permissions": [
    "https://www.google.com/"
  ],
  ...
}

교차 출처 권한 값은 다음과 같이 정규화된 호스트 이름일 수 있습니다.

  • "https://www.google.com/"
  • "https://www.gmail.com/"

또는 다음과 같은 일치 패턴이 될 수 있습니다.

  • "https://*.google.com/"
  • "https://*/"

'https://*/'의 일치 패턴을 사용하면 연결 가능한 모든 도메인에 대한 HTTPS 액세스가 허용됩니다. 여기에서 일치 패턴은 콘텐츠 스크립트 일치 패턴과 비슷하지만 호스트 뒤에 오는 모든 경로 정보는 무시됩니다.

또한 액세스 권한은 호스트와 스키마별로 부여됩니다. 확장 프로그램이 특정 호스트 또는 호스트 집합에 대한 보안 및 비보안 HTTP 액세스를 모두 원하는 경우 권한을 별도로 선언해야 합니다.

"host_permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

Fetch()와 XMLHttpRequest() 비교

fetch()는 서비스 워커를 위해 특별히 제작되었으며 동기 작업에서 벗어나 더 광범위한 웹 트렌드를 따릅니다. XMLHttpRequest() API는 서비스 워커 외부의 확장 프로그램에서 지원되며 이 API를 호출하면 확장 프로그램 서비스 워커의 가져오기 핸들러가 트리거됩니다. 가능한 경우 새 작업에서 fetch()를 우선해야 합니다.

보안 고려사항

교차 사이트 스크립팅 취약점 방지

fetch()를 통해 검색된 리소스를 사용할 때 오프스크린 문서, 측면 패널 또는 팝업이 교차 사이트 스크립팅의 피해를 입지 않도록 주의해야 합니다. 특히 innerHTML와 같은 위험한 API는 사용하지 않는 것이 좋습니다. 예를 들면 다음과 같습니다.

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
    ...

대신 스크립트를 실행하지 않는 더 안전한 API를 사용하세요.

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);

const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;

교차 출처 요청으로 콘텐츠 스크립트 액세스 제한

콘텐츠 스크립트를 대신하여 교차 출처 요청을 실행할 때 콘텐츠 스크립트를 가장하려고 할 수 있는 악성 웹페이지를 차단해야 합니다. 특히 콘텐츠 스크립트에서 임의의 URL을 요청하도록 허용하지 않아야 합니다.

콘텐츠 스크립트가 항목 가격을 찾을 수 있도록 확장 프로그램이 교차 출처 요청을 수행하는 경우를 생각해 보세요. 그다지 안전하지 않은 접근 방식 중 하나는 콘텐츠 스크립트가 백그라운드 페이지에서 가져올 정확한 리소스를 지정하도록 하는 것입니다.

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == 'fetchUrl') {
      // WARNING: SECURITY PROBLEM - a malicious web page may abuse
      // the message handler to get access to arbitrary cross-origin
      // resources.
      fetch(request.url)
        .then(response => response.text())
        .then(text => sendResponse(text))
        .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  }
);
chrome.runtime.sendMessage(
  {
    contentScriptQuery: 'fetchUrl',
    url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
  },
  response => parsePrice(response.text())
);

위의 접근 방식에서 콘텐츠 스크립트는 확장 프로그램에 확장 프로그램이 액세스할 수 있는 URL을 가져오도록 요청할 수 있습니다. 악성 웹페이지는 이러한 메시지를 위조하여 확장 프로그램이 교차 출처 리소스에 액세스하도록 속일 수 있습니다.

대신 가져올 수 있는 리소스를 제한하는 메시지 핸들러를 설계합니다. 아래에서는 콘텐츠 스크립트에서 itemId만 제공하며 전체 URL은 제공하지 않습니다.

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == 'queryPrice') {
      const url = `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
      fetch(url)
        .then(response => response.text())
        .then(text => parsePrice(text))
        .then(price => sendResponse(price))
        .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  }
);
chrome.runtime.sendMessage(
  {contentScriptQuery: 'queryPrice', itemId: 12345},
  price => ...
);

HTTP보다 HTTPS 선호

또한 HTTP를 통해 검색되는 리소스에 특히 주의하세요. 확장 프로그램이 적대적인 네트워크에서 사용되는 경우 네트워크 공격자 (일명 "man-in-the-middle")가 응답을 수정하여 잠재적으로 확장 프로그램을 공격할 수 있습니다. 대신 가능하면 HTTPS를 사용하세요.

콘텐츠 보안 정책 조정

매니페스트에 content_security_policy 속성을 추가하여 확장 프로그램의 기본 콘텐츠 보안 정책을 수정하는 경우 연결하려는 호스트가 모두 허용되는지 확인해야 합니다. 기본 정책은 호스트에 대한 연결을 제한하지 않지만 connect-src 또는 default-src 지시어를 명시적으로 추가할 때는 주의해야 합니다.