APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【Zscaler】SSLインスペクションだった

自己紹介

こんにちは、エーピーコミュニケーションズiTOC事業部 BzD部 0-WANの田中と申します。
弊社でEDR製品を導入いただいたお客様のインシデント調査を主に担当しております。
その傍らプログラマーとしての経験と知識を生かしてセキュリティに関するウェブアプリケーションを設計構築するなどSOCチームのメンバーとして日々サイバーセキュリティと共に在るエンジニアです。

本記事では、Zscaler環境下におけるSSL証明書エラーの原因特定と解決策について解説します。 正常に動作していたプログラムが突然エラーを吐き出した際、ログから何を読み取り、どのようにして根本原因(ZscalerによるSSLインスペクション)を突き止めたのか。 実際の調査ログを交えながら、解決までのプロセスを共有します。

何があったのか

業務の一環として、VirusTotal APIを利用してマルウェア情報を収集・分析するPythonツールを使用しています。 このツールは週に一度起動し、週間レポートを自動生成する役割を担っているのですが、Linux環境での実行時にSSL関連のエラーに直面しました。

実行時に出力されたエラーログは以下の通りです。 「local issuer certificate(ローカルの発行者証明書)」が取得できない、つまり証明書チェーンの検証に失敗していることを示しています。

error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1029)

問題が発生したソースコードの該当箇所は以下の通りです。

# リクエストオブジェクトの生成
response = urllib.request.Request(url, method='GET', headers=req_header)

# ※実際には、このリクエストをurlopenで送信するタイミングでエラーが発生
with urllib.request.urlopen(response ) as response:
    body = response.read()

原因と解決策

試行錯誤1:Pythonの証明書パッケージ(certifi)の更新

エラー内容が「証明書の検証失敗」だったため、まずは単純に「Python環境が持っている証明書リストが古いのではないか?」と疑いました。 そこで、Pythonの標準的な証明書パッケージであるcertifiを最新化することにしました。

しかし、この時点で既にSSLインスペクションの影響を受けており、普通にpipを実行してもSSLエラーで弾かれてしまいました。 そのため、以下のように--trusted-hostオプションを付与して、リポジトリ(PyPI)への通信の検証をスキップさせる形でアップデートを強行しました。

└─$ pip install --upgrade certifi --trusted-host pypi.org --trusted-host files.pythonhosted.org
Requirement already satisfied: certifi in ./falcon_env/lib/python3.13/site-packages (2025.4.26)
Collecting certifi
  Downloading certifi-2025.11.12-py3-none-any.whl.metadata (2.5 kB)
Downloading certifi-2025.11.12-py3-none-any.whl (159 kB)
Installing collected packages: certifi
  Attempting uninstall: certifi
    Found existing installation: certifi 2025.4.26
    Uninstalling certifi-2025.4.26:
      Successfully uninstalled certifi-2025.4.26
Successfully installed certifi-2025.11.12

結果として問題は解決せず、certifiは最新版(2025.11.12)になりましたが、メインのPythonプログラムを実行すると相変わらず同じCERTIFICATE_VERIFY_FAILEDエラーが発生しました。

試行錯誤2:OSのルート証明書の更新と再構築

Python側のライブラリを更新しても改善しなかったため、次に「OS(Linux)が保持しているルート証明書ストア自体が古くなっているのではないか?」と考えました。

そこで、OS標準の証明書パッケージca-certificatesをaptで更新し、さらに--freshオプションを使用して、既存の証明書キャッシュをクリアした上でストアをゼロから再構築しました。

└─$ sudo apt install ca-certificates
Upgrading:                          
  ca-certificates

Summary:
  Upgrading: 1, Installing: 0, Removing: 0, Not Upgrading: 1881
  Download size: 0 B / 162 kB
  Freed space: 7,168 B

パッケージを事前設定しています ...
(データベースを読み込んでいます ... 現在 427690 個のファイルとディレクトリがインストールされています。)
.../ca-certificates_20250419_all.deb を展開する準備をしています ...
ca-certificates (20250419) で (20241223 に) 上書き展開しています ...
# ... (中略) ...
Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
ca-certificates-java (20240118) のトリガを処理しています ...
done.

└─$ sudo update-ca-certificates --fresh
Clearing symlinks in /etc/ssl/certs...
done.
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
150 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
ca-certificates-java (20240118) のトリガを処理しています ...
Replacing debian:ACCVRAIZ1.pem
# ... (中略) ...
Adding debian:D-TRUST_EV_Root_CA_2_2023.pem
done.
done.

結果としては、OSの証明書ストアは正常に更新され、150個のルート証明書が登録されましたが、エラーは解消されませんでした。

試行錯誤3:すべての証明書(証明書チェーン)を表示

サーバーから送られてくるすべての証明書(証明書チェーン)を表示して、サーバー証明書だけでなく、中間証明書やルート証明書の情報を確認しました。

└─$ openssl s_client -connect www.virustotal.com:443 -showcerts < /dev/null | head -n 20
Connecting to 34.54.88.138
depth=2 C=US, ST=*****, O=Zscaler Inc., OU=Zscaler Inc., CN=Zscaler Intermediate Root CA (*****.***), emailAddress=support@zscaler.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, ST=*****, O=Zscaler Inc., OU=Zscaler Inc., CN=Zscaler Intermediate Root CA (*****.***) (t) 
verify return:1
depth=0 CN=www.virustotal.com, O=Zscaler Inc., OU=Zscaler Inc.
verify return:1
DONE
CONNECTED(00000003)
...

ここまでで分かったことは以下の通りです。 ZscalerはSSL通信を復号し、ウイルス検知(SSLインスペクション)を行っています。 この際、Zscalerは独自の「私的な証明書」を用いて通信を中継します。

aptで入手できるのは、あくまでインターネット上で広く信頼されている「公的な証明書」(Public CA)だけです。 しかし、今回必要だったのは、Zscalerが独自に発行している「私的な証明書」(Private CA)でした。

これはOS標準の配布物ではないため、いくらシステムを更新(apt upgrade)しても自動ではインストールされないため、手動でインストールすることにしました。

試行錯誤4:Zscalerの「私的な証明書」を手動インストール

最新の証明書を以下から入手してOSにインストールしました。 https://admin.zscalerthree.net/#policy/web/ssl-inspection

最新の証明書

└─$ sudo cp ZscalerRootCertificate-2048-SHA256-Feb2025.crt /usr/local/share/ca-certificates/zscaler.crt

└─$ sudo update-ca-certificates        
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
ca-certificates-java (20240118) のトリガを処理しています ...
Adding debian:zscaler.pem
done.
done.

# 確認
>>> import urllib.request, json
>>> strId256 = "073378dbb8cdeb17323e27d777895d8944e2a318add5f912269436e1effac33f"
>>> url = 'https://www.virustotal.com/api/v3/files/' + strId256
>>> req_header = {'Content-Type': 'application/json','x-apikey': "*****",}
>>> response = urllib.request.Request(url, method='GET', headers=req_header)
>>> with urllib.request.urlopen(response) as response:
...     body = json.loads(response.read())

正しくVirusTotal APIを利用してマルウェア情報を収集することができました。

まとめ

今回は、Zscaler環境下で発生したSSL証明書エラーの特定と解決策についてお届けしました。 「プログラムのエラー」だと思って調べていたら、実はネットワーク経路(SSL Inspection)の問題だった…というのはエンジニアあるあるなのでしょうか。 同じ事象で困っている方の参考になれば幸いです。

さて、来たる2026年の技術ブログのテーマは「脆弱性」です! セキュリティ上のリスクや対策について、様々な角度から深堀りしてお届けする予定です。

少しでもサイバーセキュリティに興味を持っていただけるように楽しくお伝えしますので2026年の更新もどうぞお楽しみに! 最後まで読んでいただきありがとうございました。

0-WANについて

私たち0-WANは、ゼロトラスト製品を中心とした、マルチベンダーでのご提案で、お客様の経営課題解決を支援しております。 ゼロトラストってどうやるの?製品を導入したけれど使いこなせていない気がする等々、どんな内容でも支援いたします。 お気軽にご相談ください。

問い合わせ先、0-WANについてはこちら。 www.ap-com.co.jp

一緒に働いて頂ける仲間も募集しています

今までの経験を活かして、私たちと一緒にゼロトラスト分野で活躍しませんか? www.ap-com.co.jp