Python
 Computer >> コンピューター >  >> プログラミング >> Python

Python 2.xのinput()関数に潜む脆弱性とは?raw_input()との違いを実例で解説

Python 2.x以前のバージョンでは、組み込み関数 input() が意図しない挙動を示し、セキュリティ上の脆弱性となる問題がありました。この記事では、その問題点と、代替手段である raw_input() 関数との違いについて詳しく解説します。

なお、Python 3.x以降では、raw_input() の安全な機能が input() 関数に統合され、この脆弱性は解消されています。まずは、Python 2.xにおける標準入力を受け付ける組み込み関数の基本的な挙動を見ていきましょう。

入力関数の基本的な違い

サンプルコード

# 入力値:文字列の場合
str1 = raw_input("raw_input()関数の出力: ")
print type(str1)
str2 = input("input()関数の出力: ")
print type(str2)

# 入力値:浮動小数点数の場合
str3 = raw_input("raw_input()関数の出力: ")
print type(str3)
str4 = input("input()関数の出力: ")
print type(str4)

# 入力値:整数の場合
str5 = raw_input("raw_input()関数の出力: ")
print type(str5)
str6 = input("input()関数の出力: ")
print type(str6)

実行結果

raw_input()関数の出力:
input()関数の出力:
raw_input()関数の出力:
input()関数の出力:
raw_input()関数の出力:
input()関数の出力:

解説: 実行結果から明らかなように、raw_input() 関数は入力された値の型に関わらず、常に文字列型(string)に変換して返します。一方、input() 関数は入力時に指定されたデータ型をそのまま保持します。

「入力された型が保持されるなら、むしろ便利ではないか?」と思われるかもしれません。しかし、ここに重大なセキュリティホールが潜んでいます。次の具体例で確認してみましょう。

実例1:サイコロゲームで見る脆弱性

randomモジュールを使ったサイコロ当てゲームを作成してみます。

サンプルコード

import random as rd
number = random.randint(1,6)
print ("Pick a number between 1 to 6")
while True:
    user_input = input("Guess the number: ")
    if user_input==number:
        print ("You guessed it right.")
        break
    else:
        print ("OOPS! try it next time.")
        continue

解説: ユーザーが整数を入力した場合は、条件式に従って正常に判定が行われます。

しかし、ユーザーが文字列として、ランダムに生成された整数を格納している変数名 number と同じ名前を入力するとどうなるでしょうか。なんと、この場合もゲームは「正解」として処理されてしまいます。

input() 関数は入力値をPythonの式として評価(eval)するため、ユーザーが変数名を入力すると、その変数の値が直接参照されるのです。本来であれば、文字列が入力された時点で型の誤りとしてエラーを発生させるべきですが、条件式はTrueと評価され、ゲームが終了してしまいます。逆に、raw_input() を使用していれば、このような問題は一切発生しません。

この脆弱性は、ログイン認証情報やユーザー情報、アカウントのパスワードなどを扱う場合には致命的な問題になり得ます。

実例2:PINコード照合システムでの危険性

次に、PINコードを入力させ、保存された値と照合するシステムを考えてみます。

サンプルコード

stored_value = 7863
def return_function():
    return stored_value
inp = input()
if inp == stored_value:
    print "You Entered Correctly"
else:
    print "Oops! It's Incorrect"

解説

前述の実例と同様に、ユーザーが整数型の入力を行えば、プログラムは通常どおり動作します。しかし、ユーザーが保存された値と同一の内容(たとえば変数名や関数の戻り値)を入力すると、条件式がTrueと評価され、正解として出力されてしまいます。

PINコードやパスワードなどの機密性の高い情報を扱う場面では、この挙動は非常に危険です。Python 2.xでは raw_input() を使用することで、この問題を回避できます。

以上の2つの実例から、input() 関数を使用したプログラムは、いわゆる「変数への直接攻撃(Variable Attack)」に対して無防備な状態になっていることが分かります。

まとめ

この記事では、Python 2.xで input() 関数を使用する際に遭遇する問題点とセキュリティ上の抜け穴について学びました。input() は入力値を式として評価するため、変数名の入力によって内部の値が参照・悪用される危険があります。Python 2.x環境では raw_input() の利用が推奨され、Python 3.x以降ではこの問題は解消されています。レガシーコードを扱う際は、ぜひこの点に注意してください。

  1. Pythonのissubset()関数とは?部分集合の判定方法を実例付きで解説

    本記事では、Python標準ライブラリに用意されているissubset()関数の仕組みと使い方について詳しく解説します。 issubset()メソッドは、あるセット(集合)のすべての要素が、引数として渡した別のセットにも含まれている場合にブール値のTrueを返し、1つでも含まれていない要素があればFalseを返します。 下の図では、BはAの部分集合です。もしAとBが同一のセットであれば、両者は互いに部分集合の関係にあるといえます。つまり、両方のセットがまったく同じ要素を持っているということを意味します。 構文 <set 1>.issubset(<set 2>) 戻り値

  2. Pythonのintersection()関数とは?集合の共通要素(積集合)を求める方法

    この記事では、Pythonのセット(集合)に対して実行できるintersection()関数について詳しく解説します。数学における「積集合(インターセクション)」とは、2つの集合から共通する要素を見つけ出すことを指します。構文<セット名>.intersection(<セットa1>, <セットa2>, …)戻り値引数として渡されたすべての集合に共通する要素(積集合)が、新しいセットとして返されます。使用例set_1 = {t,u,t,o,r,i,a,l} set_2 = {p,o,i,n,t} set_3 = {t,u,t} # 2つの集合の積集合 print