교차 출처 XMLHttpRequest

일반 웹페이지에서 XMLHttpRequest 객체를 사용하여 원격으로 데이터를 주고 받을 수 있습니다. 동일한 출처 정책의 제한을 받습니다. 콘텐츠 스크립트는 요청을 시작함 사용자를 대신하여 웹사이트 콘텐츠를 스크립트에도 동일한 출처 정책이 적용됩니다. (콘텐츠 스크립트에는 CORB가 적용됨 Chrome 73부터 CORS, Chrome 83부터 CORS 사용 가능) 확장 프로그램 출처에는 제한이 없습니다. 스크립트 확장 프로그램의 백그라운드 페이지 또는 포그라운드 탭에서 실행되는 경우, 외부 원격 서버와 통신할 수 확장 프로그램이 교차 출처 권한을 요청하는 경우 출처가 표시됩니다.

확장 프로그램 출처

실행 중인 각 확장 프로그램은 별도의 자체 보안 출처 내에 있습니다. 추가 요청 없이 권한이 있는 경우 확장 프로그램은 XMLHttpRequest를 사용하여 설치 내에서 리소스를 가져올 수 있습니다. 대상 예를 들어 확장 프로그램에 config.json이라는 JSON 구성 파일이 config_resources 폴더에 있는 경우 확장 프로그램은 다음과 같이 파일 콘텐츠를 검색할 수 있습니다.

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

확장 프로그램이 해당 확장 프로그램이 아닌 다른 보안 출처(예: https://www.google.com)를 사용하려고 하는 경우 확장 프로그램이 적절한 교차 출처(cross-origin)를 요청하지 않는 한 브라우저에서 허용하지 않습니다. 권한을 부여할 수 있습니다

교차 출처 권한 요청

권한 섹션에 호스트 또는 호스트 일치 패턴 (또는 둘 다)을 매니페스트 파일이 있는 경우 확장 프로그램은 원본 외부의 원격 서버에 대한 액세스를 요청할 수 있습니다.

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

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

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

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

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

'https://*/' 일치 패턴 연결할 수 있는 모든 도메인에 대한 HTTPS 액세스를 허용합니다. 여기서는 패턴은 콘텐츠 스크립트 일치 패턴과 유사하지만, 호스트는 무시됩니다.

또한 액세스 권한은 호스트와 스키마에 의해 모두 부여됩니다. 확장 프로그램이 보안되지 않은 HTTP 액세스가 있을 경우 해당 권한을 별도로 선언해야 합니다.

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

보안 고려사항

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

XMLHttpRequest를 통해 검색된 리소스를 사용할 때 백그라운드 페이지가 교차 사이트 스크립팅의 피해자가 되는 경우 특히 다음과 같이 위험한 API는 사용하지 마세요. 아래:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

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

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

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

콘텐츠 스크립트를 대신하여 교차 출처 요청을 수행하는 경우 악성 웹페이지가 발견되었습니다. 특히 콘텐츠 스크립트를 사용하여 임의의 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()));

위의 접근 방식에서 콘텐츠 스크립트는 확장 프로그램에 액세스할 수 있습니다. 악성 웹페이지는 이러한 메시지를 위조하여 확장 프로그램을 다음과 같이 속일 수 있습니다. 교차 출처 리소스에 대한 액세스 권한을 부여합니다

대신 가져올 수 있는 리소스를 제한하는 메시지 핸들러를 설계하세요. 아래에서는 itemId는 전체 URL이 아닌 콘텐츠 스크립트에서 제공됩니다.

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'queryPrice') {
        var 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 지시어를 명시적으로 추가할 때는 주의해야 합니다.