プロキシサーバーのフィルターが原因?アクセスできないサイトを突き止める方法、自作拡張機能で確認

はじめに

社内ネットワークや学校などの環境では、インターネットアクセスの管理・セキュリティ強化のためにプロキシサーバーが導入されていることがよくあります。ときにはこのプロキシサーバーのフィルタリング機能が原因で、特定のWebサイトやサービスにアクセスできなくなることも。

プロキシサーバーとは

代理サーバーとしての役割

プロキシサーバー(proxy server)とは、ユーザーのリクエスト(通信)を“代理”として受け取り、インターネット上の目的のサーバーにアクセスする役割を担うサーバーのことです。
一般的に、以下の目的で利用されます。

キャッシュ機能: よくアクセスするサイトのデータをプロキシ側で一時保存し、ネットワーク帯域を節約する。
セキュリティ・フィルタリング: 社内ルールや企業ポリシーに反するサイトや有害サイトへのアクセスを制限する。
ログ管理: どの端末がどのサイトにアクセスしているかを記録・監視する。

フィルタリングの仕組み

プロキシサーバーにはURLやキーワードをもとにアクセスを許可・ブロックする機能が搭載されている場合があります。たとえば「SNSサイト禁止」「特定のカテゴリのサイトは禁止」のように設定されている場合、該当のサイトにアクセスしようとすると、ブラウザ上にブロック画面が表示されたり“接続エラー”になったりすることがあります。

プロキシサーバーのフィルターでサイトにアクセスできないケース

よくある症状

プロキシサーバーが原因で特定のサイトにアクセスできないとき、以下のような症状が現れることが多いです。

接続タイムアウト: ブラウザ上でエラーが表示され、ページがまったく表示されない。
ブロック画面が表示される: 「このサイトはアクセス禁止です」などの専用ページが表示される。
部分的なリソースの読み込み失敗: サイトは見られるが、画像や特定のスクリプトだけが読み込めない。
こういった場合、プロキシサーバーの管理画面を確認できれば、どのURLがブロックされたのか一目でわかることがあります。しかし、組織の方針などで管理画面にアクセスできないケースも少なくありません。

検証ツールを使って原因(ホワイトリスト)を突き止める手順

ここでは、プロキシサーバーの管理画面に頼らずに、Chromeの開発者ツール(検証ツール)などを使ってフィルタリングの原因を調べる方法を紹介します。
他のブラウザ(Firefox、Edgeなど)でも同様の手順で確認できますが、ここではChromeを例とします。

開発者ツール(検証画面)の起動

Chromeを開き、アクセスしたいサイトに移動。
キーボードのF12キー、または右上のメニューから「その他のツール > デベロッパーツール」を選択。

Networkタブの確認

開発者ツールを開いたら、上部のタブからNetworkを選択する。
すでにページが読み込まれている場合は、いったんリロード(もしくは「Disable cache」にチェックを入れてリロード)する。
すると、該当サイトの読み込みリクエストの一覧が表示される。
ここで注目すべきは、

ステータスコード: 200(OK)、404(Not Found)、403(Forbidden)、407(Proxy Authentication Required)など
URL: ブロックの原因となっているかもしれないドメインやファイルパス
もしフィルタリングが作動している場合、特定のリソースだけ403や404、あるいは proxy error が返っているといったことが確認できるでしょう。

Consoleタブのエラーを確認

Networkタブにエラーがない場合でも、Consoleタブにエラーメッセージが残っていることがあります。

ブロックされたリソースのURL
「Failed to load resource: the server responded with a status of 403」などの文言
これらのエラー文言から、フィルタリングによるエラーかどうか判断材料になります。

実際にブロックされているURLをブラウザで直接開いてみる

NetworkタブやConsoleタブで見つけたエラーURLに対して、

ブラウザのアドレスバーに直接貼り付けてアクセス
「このURL自体がブロックされているのか」を確認
これにより、主サイト自体は表示できても、広告や解析用のドメインがブロックされてページが中途半端に崩れているというケースも特定できるはずです。

補足:コマンドラインの活用例

もし開発者ツールだけでは原因が分かりづらい場合、curlやpingなどのコマンドラインツールを使っても検証ができます。

curlでの確認

ターミナルやコマンドプロンプトで下記のように入力します:

curl -I http://アクセスしたいサイトのURL

ヘッダー情報(ステータスコードなど)が返ってきますが、もしプロキシが介入している場合は「プロキシのエラーメッセージ」「Access Denied」などが返ってくることがあります。

pingでの接続確認

ping アクセスしたいサイトのドメイン

pingはICMPプロトコルを使っているため、Webアクセスのフィルタリングと挙動が異なることもありますが、サーバー側に全く応答がないのか、そもそもDNS解決できないのか、といった点を切り分けるヒントになります。

adobe ホワイトリスト プロキシサーバー 拡張機能 参照URL一覧

自作Chromeの拡張機能でリソースURLリストを取得

Webページが読み込む全てのリソース(画像、スクリプト、スタイルシート、API呼び出しなど)のURLを取得

構成ファイル

page-resources-monitor/
├── manifest.json      # 拡張機能の設定ファイル
├── background.js      # バックグラウンドでリクエストを監視
├── popup.html         # ポップアップのUI
└── popup.js          # ポップアップの動作制御

manifest.json

{
  "manifest_version": 3,
  "name": "Page Resources Monitor",
  "version": "1.0",
  "description": "ページが読み込むすべてのリソースのURLを表示します",
  "permissions": [
    "activeTab",
    "declarativeNetRequest",
    "scripting",
    "webRequest",
    "webNavigation"
  ],
  "host_permissions": [
    "*://*/*",
    "https://*/*"
  ],
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}

background.js

let requests = new Map();

function addRequest(tabId, requestData) {
  if (!requests.has(tabId)) {
    requests.set(tabId, []);
  }
  requests.get(tabId).push(requestData);
}

chrome.webRequest.onBeforeRequest.addListener(
  function(details) {
    console.log('Captured request:', details);
    if (details.tabId !== -1) {
      const requestData = {
        url: details.url,
        type: details.type,
        method: details.method || 'GET',
        timestamp: details.timeStamp,
        initiator: details.initiator || '',
        frameId: details.frameId || 0,
        parentFrameId: details.parentFrameId || 0,
        documentUrl: details.documentUrl || ''
      };
      
      addRequest(details.tabId, requestData);
    }
  },
  {
    urls: ["<all_urls>"],
    types: [
      "main_frame",
      "sub_frame",
      "stylesheet",
      "script",
      "image",
      "font",
      "object",
      "xmlhttprequest",
      "ping",
      "csp_report",
      "media",
      "websocket",
      "other"
    ]
  }
);

// タブが切り替わった時のリクエスト情報保持
chrome.tabs.onActivated.addListener(function(activeInfo) {
  if (!requests.has(activeInfo.tabId)) {
    requests.set(activeInfo.tabId, []);
  }
});

// メッセージリスナー
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.action === "getRequests") {
      chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
        if (tabs && tabs[0]) {
          const currentTabId = tabs[0].id;
          const tabRequests = requests.get(currentTabId) || [];
          sendResponse({requests: tabRequests});
        } else {
          sendResponse({requests: []});
        }
      });
      return true;
    }
  }
);

popup.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        width: 800px;
        padding: 10px;
      }
      .resource-list {
        max-height: 600px;
        overflow-y: auto;
      }
      .resource-item {
        margin: 5px 0;
        padding: 5px;
        border-bottom: 1px solid #eee;
      }
      .type-filter {
        margin: 10px 0;
      }
      .url {
        word-break: break-all;
      }
      .type {
        color: #666;
        font-size: 0.9em;
      }
      button {
        margin: 5px;
        padding: 8px 16px;
        background-color: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
      button:hover {
        background-color: #45a049;
      }
      #exportCSV {
        background-color: #2196f3;
      }
      #exportCSV:hover {
        background-color: #1976d2;
      }
      #exportJSON {
        background-color: #ff9800;
      }
      #exportJSON:hover {
        background-color: #f57c00;
      }
      .stats {
        margin: 10px 0;
        padding: 10px;
        background: #f5f5f5;
      }
    </style>
  </head>
  <body>
    <h3>ページリソース一覧</h3>
    <div class="type-filter">
      <label>リソースタイプでフィルター:</label>
      <select id="typeFilter">
        <option value="all">すべて</option>
        <option value="script">スクリプト</option>
        <option value="stylesheet">スタイルシート</option>
        <option value="image">画像</option>
        <option value="font">フォント</option>
        <option value="xmlhttprequest">API/XHR</option>
        <option value="websocket">WebSocket</option>
        <option value="other">その他</option>
      </select>
    </div>
    <div class="stats" id="stats"></div>
    <button id="copyButton">URLリストをコピー</button>
    <button id="exportCSV">CSVでエクスポート</button>
    <button id="exportJSON">JSONでエクスポート</button>
    <div id="resourceList" class="resource-list"></div>
    <script src="popup.js"></script>
  </body>
</html>

popup.js

document.addEventListener('DOMContentLoaded', function() {
    const resourceList = document.getElementById('resourceList');
    const typeFilter = document.getElementById('typeFilter');
    const stats = document.getElementById('stats');
    const copyButton = document.getElementById('copyButton');
    let allRequests = [];
  
    function updateList(requests, filterType = 'all') {
      resourceList.innerHTML = '';
      
      if (!Array.isArray(requests)) {
        console.error('requests is not an array:', requests);
        requests = [];
      }
  
      const filteredRequests = filterType === 'all' 
        ? requests 
        : requests.filter(r => r.type === filterType);
  
      filteredRequests.forEach(request => {
        const div = document.createElement('div');
        div.className = 'resource-item';
        div.innerHTML = `
          <div class="url">${request.url}</div>
          <div class="type">タイプ: ${request.type} (${request.method || 'GET'})</div>
          <div class="initiator">発信元: ${request.initiator || '不明'}</div>
        `;
        resourceList.appendChild(div);
      });
  
      const typeCount = requests.reduce((acc, r) => {
        acc[r.type] = (acc[r.type] || 0) + 1;
        return acc;
      }, {});
  
      stats.innerHTML = `
        <strong>統計情報:</strong><br>
        総リクエスト数: ${requests.length}<br>
        タイプ別: ${Object.entries(typeCount)
          .map(([type, count]) => `${type}: ${count}`)
          .join(', ')}
      `;
    }
  
    function refreshData() {
      chrome.runtime.sendMessage({action: "getRequests"}, function(response) {
        console.log('Received response:', response);
        if (response && response.requests) {
          allRequests = response.requests;
          updateList(allRequests, typeFilter.value);
        }
      });
    }
  
    refreshData();
    setInterval(refreshData, 3000);
  
    typeFilter.addEventListener('change', function() {
      updateList(allRequests, this.value);
    });
  
    copyButton.addEventListener('click', function() {
      const urls = allRequests.map(r => r.url).join('\n');
      navigator.clipboard.writeText(urls).then(function() {
        copyButton.textContent = 'コピーしました!';
        setTimeout(function() {
          copyButton.textContent = 'URLリストをコピー';
        }, 2000);
      });
    });
  });

拡張機能のインストール方法:

  • Chrome で chrome://extensions/ を開きます
  • 右上の「デベロッパーモード」を有効にします
  • 上記のフォルダを「パッケージ化されていない拡張機能を読み込む」でインポートします

主な処理の特徴:

  1. バックグラウンドプロセス(background.js)
  • 常時動作してリクエストを監視
  • Mapデータ構造でタブごとにリクエストを保持
  • メッセージングAPIでポップアップとデータをやり取り
  1. ポップアップUI(popup.html/js)
  • ユーザーインターフェースの提供
  • リアルタイムデータ更新
  • フィルタリングと表示制御
  • データエクスポート機能
background.js Webリクエスト監視 データ保存 popup.html/js UI表示 データ取得・更新 データ取得 Webブラウザ リクエスト情報

WebRequest API

WebRequest APIは、Chrome拡張機能がブラウザのネットワークリクエストを監視・分析できるAPIです。

取得できる情報:

  • URLやメソッド
  • リクエスト/レスポンスヘッダー
  • リクエストタイプ(image, script, stylesheet等)
  • リクエスト元のタブID