Skip to main content

Pythonのコードインジェクション:事例と防止策

著者
Headshot of Lucien Chemaly

Lucien Chemaly

feature python code injection

2023年12月6日

0 分で読めます

ソフトウェアが仕事や日常生活に欠かせないものになるにつれ、悪意ある攻撃から情報やシステムを守る必要性も高まっています。Python開発者が直面する重大な脅威の一つが、コードインジェクションのリスクです。これは巧妙で、しばしば甚大な被害をもたらすサイバー攻撃です。

コードインジェクションは、プログラミング言語やプラットフォームを問わず広く見られる問題ですが、Pythonアプリケーションでは特に巧妙かつ危険な形で現れることがあります。

Web開発、データ分析、自動化で最も広く使われている言語の一つであるPythonは、豊富な機能とライブラリを備えています。それらは大きな利点である一方、リスクにもなり得ます。開発者は堅牢で効率的なシステムを構築できますが、安全なコーディング規約を厳守しなければ、攻撃者に脆弱性を悪用される機会も数多く生まれます。

オープンソースのコンポーネントやパッケージの利用が広がるにつれて、Pythonでのコードインジェクション防止はさらに難しくなっています。こうしたすぐに利用できるリソースは開発を加速させますが、コードインジェクションに悪用される、隠れたセキュリティ上の欠陥を含むこともあります。

この記事では、安全なコーディング規約のリスクと重要性について、特にコードインジェクションの脆弱性とPythonアプリケーションでの発生例を取り上げます。コードインジェクションの仕組みを理解し、安全なコーディングのベストプラクティスを実践することで、より安全なデジタル環境づくりに貢献し、アプリケーションを侵害から守ることができます。

コードインジェクションとは?

コードインジェクションとは、悪意のあるコードをソフトウェアシステムに挿入し、意図しないコマンドを実行させる巧妙な攻撃です。脆弱性を悪用して有害なコードを注入すると、不正なデータアクセス、金融詐欺、システムの完全な乗っ取りなど、深刻な被害につながるおそれがあります。

こうした脆弱性は、アプリケーションがユーザー入力を適切に処理しない場合によく発生します。たとえば、Pythonでeval()のような関数を適切な検証なしに安全でない方法で使うと、コードインジェクションにつながる可能性があります。十分なチェックをせずにユーザー入力からコードを生成すること、セキュリティ審査を行わずにサードパーティーのコードを使うこと、Webフレームワークやデータベースの設定に脆弱性があることも原因になります。

コードインジェクションを理解することは、ソフトウェア開発やセキュリティに携わる人にとって不可欠です。そこで、遭遇する可能性のある一般的な脆弱性を詳しく見ていきましょう。

コードインジェクションにつながる一般的な脆弱性

コードインジェクションにつながる脆弱性は、ソフトウェア開発における重大な懸念事項です。安全なシステムを構築するには、こうした脆弱性を理解し、対処することが不可欠です。以下では、コードインジェクションの主な原因と、その防止策を紹介します。

ユーザーが制御できる入力の悪用

ユーザー入力を検証せずに直接使用すると、攻撃者は入力としてPythonコードを入力でき、アプリケーションがそれを実行してしまいます。次の例を見てみましょう。

user_input = input("Enter your username: ")
query = "SELECT * FROM users WHERE username = '" + user_input + "';"
execute_query(query)  # This can be exploited

攻撃者がこのコードを悪用する場合、' OR '1'='1'; DROP TABLE users;のような値を入力できます。セミコロン(;)で元のクエリを終了させ、その後のSQLコードを新しいクエリとして実行します。これにより、データベースからusersテーブルが削除されるおそれがあります。

eval()や関連関数の安全でない使用

ユーザーが次のような任意の式を入力できる場合、eval()のような関数は悪用される可能性があります。

user_input = input("Enter expression: ")
result = eval(user_input)  # Unsafe

この例では、攻撃者が__import__('os').system('any arbitrary command')を入力すると、任意のOSコマンドを実行できます。たとえばrm -rf /のようなコマンドを入力し、コードが存在するシステムのルートディレクトリ内にあるすべてのファイルを削除しようとする可能性があります。

入力の検証とサニタイズの不足

適切な検証がなければ、攻撃者が任意のファイルへのパスを入力し、アプリケーションに次のように読み取らせる可能性があります。

user_input = input("Enter filename: ")
with open(user_input, 'r') as file:  # Vulnerable to directory traversal
    content = file.read()

ここで攻撃者は/etc/passwdのようなパスを入力し、機密性の高いシステムファイルを読み取ることができます。

動的なコード構築に伴うリスク

適切な検証をせずにコマンド文字列を動的に構築すると、コマンドインジェクションの脆弱性につながる可能性があります。次のコードを見てみましょう。

import os

directory = input("Enter the directory to list: ")
command = f"ls {directory}"  # Vulnerable to Command Injection
os.system(command)

この例では、攻撃者が; cat /etc/passwdのようなディレクトリ文字列を入力し、システムのパスワードファイルの内容を表示したり、さらに悪意のあるコマンドを実行したりする可能性があります。セミコロン(;)を使うとコマンドを連結できるため、ホストシステム上で任意のコマンドを実行されるおそれがあります。

安全でないデシリアライゼーション

信頼できないデータを適切な検証やサニタイズをせずにデシリアライズすると、安全でないデシリアライゼーションが発生する可能性があります。攻撃者はこれを悪用して任意のコードを実行できます。次の例を見てみましょう。

import pickle
serialized_data = input("Enter serialized data: ")
deserialized_data = pickle.loads(serialized_data.encode('latin1'))  # Unsafe deserialization

攻撃者がこのコードを悪用する場合、デシリアライズ時に任意のコード(リバースシェルの起動など)を実行するシリアライズ済みオブジェクトを細工できます。この種の攻撃は、オンラインショッピングカートのような日常的なアプリケーションでも起こり得ます。たとえば、ショッピングカートの情報が特殊なコードとして保存されているとします。アプリが再読み込み時にそのコードの安全性を十分に確認しなければ、攻撃者はショッピングカートに見せかけながら、裏で悪意のある動作をするコードを送り込めます。アプリが知らずにこの危険なコードを実行すると、攻撃者に侵入の機会を与えてしまいます。攻撃者はデータを盗んだり、アプリを操作したり、システム全体を制御したりする可能性があり、重大なセキュリティリスクとなります。

コードインジェクションの脆弱性を軽減する

脆弱性を理解することは第一歩ですが、効果的な軽減方法を知ることも重要です。以下に、先ほど取り上げた脆弱性への対策を紹介します。

ユーザーが制御できる入力を保護する

ユーザー入力を直接実行しないでください。必要な場合は、指定した入力パターンだけを受け入れる厳格な許可リスト方式を採用しましょう。

ALLOWED_COMMANDS = ["start", "stop", "restart"]
user_input = input("Enter your command: ")
if user_input in ALLOWED_COMMANDS:
    exec(user_input)
else:
    print("Invalid command.")

eval()や関連関数を安全に使用する

literal_eval()のような、より安全な代替手段を使うか、eval()の使用を完全に避けてください。使用が避けられない場合は、入力を必ずサニタイズしましょう。

from ast import literal_eval
user_input = input("Enter expression: ")
try:
    result = literal_eval(user_input)
except ValueError:
    print("Invalid expression.")

入力の検証とサニタイズを実装する

正規表現などの検証手法を使い、有効なファイル名やパスだけが受け入れられるようにしましょう。

import re
user_input = input("Enter filename: ")
if re.match("^[a-zA-Z0-9_\-/]+\.txt$", user_input):
    with open(user_input, 'r') as file:
        content = file.read()
else:
    print("Invalid filename.")

たとえばliteral_eval()では、入力2 + 3は数値5として評価されます。一方、__import__('os').system('rm -rf /')のような入力は、コマンドを実行せずValueErrorを発生させます。これにより、eval()の使用に伴うセキュリティ上の懸念を効果的に軽減し、リテラルを含む式をより安全に評価できます。

動的なコード構築に対処する

SQLインジェクションを防ぐため、パラメーター化クエリまたはプリペアドステートメントを使用しましょう。

import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
input_username = input("Enter username: ")
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (input_username,))

安全にデシリアライズする

信頼できるデータのみをデシリアライズしてください。ユーザー入力をデシリアライズする必要がある場合は、JSONなどのより安全な形式を使用するか、デシリアライズの前に十分な検証を行いましょう。

import json
serialized_data = input("Enter serialized data: ")
try:
    deserialized_data = json.loads(serialized_data)
except json.JSONDecodeError:
    print("Invalid data.")

強力なアクセス制御を徹底する

最小権限の原則を適用すると、ユーザーやプロセスにタスクの遂行に必要な最小限のアクセス権(または権限)だけを付与できます。脆弱性が悪用された場合に攻撃者ができることを制限し、攻撃対象領域を縮小できます。

アプリケーションの機密性の高い領域への不正アクセスを制限できるよう、強力なアクセス制御を実装してください。次のセクションでは、コードベースのセキュリティをさらに強化する対策を紹介します。

Pythonの安全なコーディング規約

ここまで、コードインジェクションの脆弱性を軽減する最も効果的な方法を説明してきました。しかし、安全なコードを書くために必要なのはそれだけではありません。堅牢で安全なアプリケーションの構築に役立つ、ベストプラクティスや規約を取り入れることも重要です。Pythonコードを書く際は、以下の点を考慮しましょう。

開発者向けセキュリティツールで問題を検出・修正する

Snykは、アプリケーションコード、オープンソースの依存関係、コンテナ、IaC設定などの脆弱性を特定し、修正するための無料のPythonセキュリティツールを提供しています。SnykをGitリポジトリ、CI/CDワークフロー、コマンドラインツール、IDEに連携すれば、脆弱性の特定と修正に役立ちます。

たとえば、Snykはアプリケーション内の脆弱なオープンソース依存関係を特定し、簡単な修正方法を提示します。Snyk Vulnerability Databaseに登録されている、pipパッケージ内の脆弱なPythonコードの一覧をご覧ください。

Snyk Vulnerability DBに表示されたpipパッケージの脆弱性一覧。悪意のあるパッケージ、競合状態、安全でないデフォルト設定などが含まれています。

Snykのようなツールを開発ワークフローに組み込むと、防御をさらに強化し、より堅牢で安全なアプリケーション開発プロセスを実現できます。Snykでは、ツールをワークフローに組み込む方法を詳しく解説した記事を公開しています。

Snykによる定期的なセキュリティスキャンには、最新の脆弱性データベースを利用できることや、チームでの利用を想定して設計された共同作業向けのセキュリティツールを使えることなど、多くの利点があります。メリットはコードを保護するだけにとどまらず、開発体験全体の向上にもつながります。Python向けの機能とコードインジェクションの防止を重視したSnykは、コーディングにおけるセキュリティへの取り組み方を変え、安全で高品質なソフトウェアの開発に欠かせないリソースとなります。

安全なログ記録を実践する

攻撃者に悪用されるおそれのあるユーザーの機密データやシステム情報をログに記録しないでください。安全な設定に対応したログ記録フレームワークを使用しましょう。

安全なコーディング環境を整える

安全なコーディング環境を整えるために、次のような方法も活用できます。

  • 定期的なコードレビューとセキュリティ監査:セキュリティの専門家が定期的にコードをレビューし、脆弱性を早期に発見できるようにしましょう。

  • ソフトウェアとライブラリを最新の状態に保つ:ライブラリやフレームワークにはセキュリティパッチが含まれていることが多いため、常に最新バージョンを使用しましょう。

  • セキュリティを重視する意識を育む:セキュリティを最優先する文化を醸成し、チームの全メンバーが安全なコーディング手法についてトレーニングを受けられるようにしましょう。

まとめ

この記事では、Pythonにおけるコードインジェクションのリスクと、安全なコーディング規則に従う必要性について解説しました。コードインジェクションにはさまざまな形がありますが、適切な知識とSnykのようなツールを活用すれば、そのリスクを軽減できます。問題が起きてからでは手遅れです。Pythonの開発プロセスにSnykのセキュリティツールを取り入れ、コードの安全性と効率性を高めましょう。

Pythonの脆弱性を検出して修正

Snykの脆弱性スキャンと修正アドバイスで、アプリケーションを保護しましょう。

クレジットカードは不要です。

またはAzure AD、Docker ID、Bitbucketで登録

Snykを利用することで、利用規約およびプライバシーポリシーを含む各種ポリシーに同意したものとみなされます。