LoginSignup
6
5

More than 5 years have passed since last update.

Python Oracleでバインド変数を使ったSQLを実行する

Last updated at Posted at 2018-07-05

Python Oracle でバインド変数を使ったSQLの実行方法について投稿します。
バインド変数はSQLインジェクション対策で有効です。

リテラルSQLとバインド変数を使用したSQLの違い

リテラルSQL

SQL文のWHERE条件に値が埋め込まれているものです。

リテラルSQL文の例

 SELECT * FROM EMP WHERE ENAME ='SMITH'
 SELECT * FROM EMP WHERE ENAME ='ALLEN'
 SELECT * FROM EMP WHERE ENAME ='MILLER'

 など、SQL文に条件が埋め込まれているSQL

実行例

1.SQLを作成

'SELECT * FROM EMP WHERE ENAME =' '' || pEname || ''' '

2.<変数名> pEname に 値を 設定

3.SQLを実行

      --変数にSMITHと設定した場合
        SELECT * FROM EMP WHERE ENAME ='SMITH'     

      --変数にTANAKAと設定した場合
        SELECT * FROM EMP WHERE ENAME ='TANAKA'    


      --変数に '''' or ''A'' = ''A と設定した場合、
        以下のSQLが作成されます。

        SELECT empno, ename FROM emp WHERE deptno = '' or 'A' = 'A'

        ⇒★これが問題!  SQLインジェクションができてしまいます。

            'A' = 'A' はすべて TRUEとかえすので、全ての行が取得されてしまいます

バインド変数を使用したSQL

SQLに変数を定義して、SQL実行前に変数に値を入れてSQLを実行するものです。

バインド変数を使用したSQLの例

 SELECT * FROM EMP WHERE ENAME = :pEname

  (:<変数名>として記載するSQL)

1.SQLを作成

'SELECT * FROM EMP WHERE ENAME = :pEname '

2.<変数名> :pEname に 値を 設定

3.SQLを実行


     -- :pEnameのバインド変数を使用したSQLが実行される
     SELECT * FROM EMP WHERE EMPNO = :pEname

       pEnameにどんな値が入っても、上記SQLで実行されるため
       SQLインジェクションは実行できない

実際にサンプルで確認してみる

Pythonスクリプトで
リテラルSQL(普通の条件、SQLインジェクションになる条件)、
バインド変数を使用したSQL(普通の条件、SQLインジェクションになる条件)を実行します。

injection.py
import cx_Oracle

Svr="<DBサーバ名>"
Port=1521
SID="orcl"
USR="scott"
PWD="tiger"

tns = cx_Oracle.makedsn(Svr, Port, SID)
conn = cx_Oracle.connect(USR, PWD, tns)


#カーソルOPEN
cur = conn.cursor()

###########################################
#バインド変数 ケース1 普通の条件
###########################################

keyword="SMITH"

#SQLをパース
cur.prepare('SELECT  empno, ename FROM emp WHERE ename = :p_ename')

cur.execute(None, {'p_ename':keyword})
res = cur.fetchall()

print("")
print("BindSQL NomalSQL")
print("--------------")

for r in res:
    print(str(r[0]) + '|' + r[1])

###########################################
#バインド変数 ケース2 SQLインジェクション
###########################################
injection="' or 'A' = 'A"

cur.execute(None, {'p_ename':injection})
res = cur.fetchall()

print("")
print("BindSQL InjectionSQL")
print("--------------")

for r in res:
    print(str(r[0]) + '|' + r[1])


###########################################
#リテラルSQL ケース1 普通の条件
###########################################

keyword="SMITH"

sql="SELECT  empno, ename FROM emp WHERE ename = '" +  keyword + "'"

cur.execute(sql)
res = cur.fetchall()

print("")
print("LiteralSQL NomalSQL")
print("--------------")

for r in res:
    print(str(r[0]) + '|' + r[1])

###########################################
#リテラルSQL ケース2 SQLインジェクション
###########################################
injection="' or 'A' = 'A"

sql="SELECT  empno, ename FROM emp WHERE ename = '" +  injection + "'"

cur.execute(sql)
res = cur.fetchall()

print("")
print("LiteralSQL InjectionSQL")
print("--------------")
for r in res:
    print(str(r[0]) + '|' + r[1])


cur.close()
conn.close()

実行結果
image.png

バインド変数を使用するメリット

SQLインジェクション対策、Oracle観点でいうとwhere条件の値が違うSQLを実行する場合、
ハードパース回数が減り、それに伴いハードパース解析が減るのでCPU使用負荷も減ります。
また共有プールに格納されるSQL関連情報もリテラルSQLに比べると減る事になります。

Pythonでバインド変数を使用したSQLを実行してみる

pythonでバインド変数を使用する方法はいくつかあるのでサンプルを作成。

bindsql.ph
import cx_Oracle

Svr="<サーバ名>"
Port=1521
SID="orcl"
USR="scott"
PWD="tiger"

tns = cx_Oracle.makedsn(Svr, Port, SID)
conn = cx_Oracle.connect(USR, PWD, tns)


#カーソルOPEN
cur = conn.cursor()

###################################
#バインド変数 SQL実行方法 その1
###################################

#SQLをパース
cur.prepare('SELECT  empno, ename FROM emp WHERE ename = :p_ename')

#バインド変数に値設定してSQLを実行
#SQLが「None」は、cur.prepare で設定されたSQLが実行される
cur.execute(None, {'p_ename': 'SMITH'})
res = cur.fetchall()

print("")
print("BINDSQL CASE1")
print("--------------")

for r in res:
    print(str(r[0]) + '|' + r[1])

#バインド変数に値設定してSQLを実行 ※SQL文は直近でcur.prepare もしくは cur.executeしたSQLになります
cur.execute(None, {'p_ename': 'ALLEN'})
res = cur.fetchall()

for r in res:
    print(str(r[0]) + '|' + r[1])


###################################
#バインド変数 SQL実行方法 その2
###################################
#ディクショナリ型の変数に バインド変数、設定値を設定 
named_params = {'p_ename2':'TURNER'}
cur.execute('SELECT  empno, ename FROM emp WHERE ename = :p_ename2', named_params)
res = cur.fetchall()

print("")
print("BINDSQL CASE2")
print("--------------")

for r in res:
    print(str(r[0]) + '|' + r[1])

###################################
#バインド変数 SQL実行方法 その3
###################################
#SQLとバインド設定値を設定
cur.execute('SELECT  empno, ename FROM emp WHERE ename = :p_ename3', p_ename3='BLAKE')
res = cur.fetchall()

print("")
print("BINDSQL CASE3")
print("--------------")
for r in res:
    print(str(r[0]) + '|' + r[1])

cur.close()
conn.close()

実行結果
 image.png

バインド変数を使用したSQLは、いくつか書き方がありますので、
使用ケースによって使いわけるのがいいと思います。

参考リンク

http://www.oracle.com/technetwork/articles/dsl/prez-python-queries-101587.html
http://www.oracle.com/technetwork/articles/dsl/python-091105.html
http://www.oracle.com/technetwork/articles/dsl/mastering-oracle-python-1391323.html
Windows環境のPythonからOracleに接続してみる

Windows環境でPythonスクリプト(.py)の実行方法

6
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5