主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

DockerでSQLインジェクションを試せるイメージを用意する

                
 今となってはいろいろ対策の手が打たれているが、それでも知識もなくWebアプリを作るとうっかりできてしまうことがあるSQLインジェクション。どんな立場からどんな攻撃ができうるかを示すために、脆弱性を持ったWebアプリを作ってみようと考えた。
 それを使って脆弱性をつついてもらうデモにしたい。つついてもらったらデータベースに変更が入る。そうしたら別の人に試してもらうにはリセットが必要となる。もろもろの環境リセットが簡単にできるとデモとして最高。そういうわけでDockerイメージとして脆弱性を持ったWebアプリをすぐ実行できるものを用意する。


 Webアプリ部分はどんなものにするか。とりあえずすごく簡単なものにしておく。アクセスしてきたクライアントにフォームを返す。フォームに名前を入力してPostする。フォームに入力された名前がデータベースにあれば、実行されたクエリ文とその名前が表示される。入力された名前がデータベースになければエラー文が表示される。以上のものをPythonで書いた。
1601241839287.jpg
1601241839493.jpg
1601241840245.jpg
 「John」とポストするだけなら問題ない。「John"; SELECT * FROM employees;DROP TABLE employees;"」などとポストされるとJohnの登録されていたテーブルが落ち、以降はJohnと入力してもデータベースにそもそもテーブルがないためエラーとなる。


Dockerfileは以下。
FROM ubuntu

RUN apt-get update && apt-get -y install nginx && \
apt-get install -y supervisor && \
apt-get install -y python3-pip && \
pip3 install gunicorn && \
pip3 install tornado

RUN apt-get install -y mysql-server

RUN echo "daemon off;" >> /etc/nginx/nginx.conf
RUN rm /etc/nginx/sites-enabled/default
ADD hello.conf /etc/nginx/conf.d/
RUN /etc/init.d/nginx reload

ADD * /usr/
WORKDIR /usr/
RUN dpkg -i mysql-connector-python-py3_2.1.3-1ubuntu14.04_all.deb
RUN /etc/init.d/mysql start && \
mysql < init.sql

EXPOSE 80
CMD /usr/bin/supervisord -c supervisord.conf

 Python製Webアプリ、MySQL、NginXはsupervisordでコントロールにした。PythonのMySQLConnectorはpipインストール設定が手間だったのでdebパッケージを落としてきてdpkgコマンドインストールにした。

 その他ファイルの設定はGithubにて公開中。
https://github.com/hMatoba/SQLInjection-DockerSample

 上記のDockerfileがあるディレクトリでビルドを実行する。
> docker build -t matoba/sql_injection ./
 
 ついでにどこからでもダウンロードできるようにプッシュしておく。
> docker push matoba/sql_injection

 イメージをダウンロードし、そこからコンテナを立ち上げて試してみる。
> docker pull matoba/sql_injection
> docker run -d -p 80:80 --name sql_inj matoba/sql_injection
1601241927297.jpg


 動いているので問題なし。あとはデータベースを含めたWebアプリ部分をもうちょっとまともにする。今のままでも試すことはできるが、かなり最低限に絞っているので試した人のインパクトは小さいだろう。
 Dockerのおかげで、いつでも使い捨てができる手軽なSQLインジェクションの脆弱性を持ったWebアプリデモが簡単に用意できるようになった。
 
スポンサーサイト



MySQLのデータ型にJSON型が加わっていた

                
tags: MySQL
 RSSに入ってくるソフトウェアの更新情報を追っていたらMySQLがデータ型にいつの間にやらJSON型を追加していた。SQLもNoSQLの特徴を取り入れて双方は近づいていくという話どおりのことであるが、ちと試しておく。

 JSON型とはいわゆるJSONドキュメントのことだが、つまりはスキーマレスなデータ型を保存できるというのが大きな特徴。従来のリレーショナルデータベースではサイズが不定になるようなデータは型として基本的になかった、MySQLにはENUMやSETがあったがまあ例外。 JSON型ということは配列だけでなく辞書型のようなものまでデータとして使えるということになる。

 Ubuntu14.04を動かして試してみた。まだデフォルトのリポジトリには最新版が来てなかったので、下記を参考にインストールした。
http://www.devopsservice.com/installation-of-mysql-server-5-7-on-ubuntu-14-04/

 基本的なことはドキュメント参照。
http://dev.mysql.com/doc/refman/5.7/en/json.html
 とりあえず適当なテーブルを作ってみる。テーブルにはフルーツや野菜などの作物を登録することにする。カラムとしてnameに作物の名前を、tagというカラムにJSON型で適当に特徴を記録する。
CREATE TABLE foo (
tag JSON,
name nchar(30)
);


INSERT INTO foo VALUES('["red", "vegetable"]', 'tomato');
INSERT INTO foo VALUES('["green", "vegetable", "long"]', 'cucumber');
INSERT INTO foo VALUES('["yellow", "fruit", "long"]', 'banana');
INSERT INTO foo VALUES('["red", "fruit", "small"]', 'strawberry');
INSERT INTO foo VALUES('["orange", "fruit"]', 'orange');
INSERT INTO foo VALUES('["omg"]', 'durian');


 フルーツを選ぶ。
SELECT * FROM foo WHERE JSON_CONTAINS(tag, '"fruit"');
1510212014248.jpg

 赤いものを選ぶ。
SELECT * FROM foo WHERE JSON_CONTAINS(tag, '"red"');
1510212014505.jpg

 複数条件にしたければ下記のようなクエリも。
SELECT * FROM foo WHERE JSON_CONTAINS(tag, '["red", "small"]');
 赤くない作物とかも。
SELECT * FROM foo WHERE NOT JSON_CONTAINS(tag, '"red"');

 JSON型を導入することで一対多、多対多の導入が中間テーブルなしでできるようになったりする。便利で有効な使い方を考えていきたい。

MongoDB VS. MySQL(with PyMongo or MySQL Connector)の続き

                
前回:http://elicon.blog57.fc2.com/blog-entry-406.html

 前回MongoDBとMySQLを自分の与えたコンディションで比較してみた。データの挿入はMongoDBのほうがすぐれていて、データのクエリと引き出しはMySQLのほうがすぐれているという結果だった。ここで終わってはどっち使えばいいのという疑問に一歩踏み込めていなくて引っかかるので、ケーススタディとして前回に与えたコンディションではどっちを使うのがいいかをもう少し検討してみる。ただあくまでケーススタディとしてやっているだけなので、値は概算を使ったりする。

 書き込み一件当たりの消費時間と、一件当たりのクエリと引き出しの消費時間を出してみる。
 書き込みは前回で10000件連続書き込みをやっているのでそこから算出。
MongoDB: 3.58[s]→一件あたり0.358[ms]
MySQL: 18.6[s]→一件あたり1.86[ms]
 続いてクエリと引き出し。
MongoDBでは以下のように。
t = time.time()
i = book_collection.find_one({"author":"Albert Camus"}, sort=[("rate",pymongo.ASCENDING)], skip=1000)
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":"Dostoevsky"}, sort=[("publishedDate", pymongo.ASCENDING)], skip=1000)
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":"Herman Hesse", "rate":3}, sort=[("publishedDate", pymongo.ASCENDING)])
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":{"$in":["Thomas Man", "William Golding"]}}, sort=[("publishedDate", pymongo.ASCENDING)])
print("SELECT: " + str(time.time() - t))
assert(not i is None)

 MySQLでは以下のように。
t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Albert Camus' ORDER BY rate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Dostoevsky' ORDER BY publishedDate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Herman Hesse' AND rate = 3 ORDER BY publishedDate LIMIT 1 OFFSET 0")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author IN ('Thomas Man', 'William Golding') ORDER BY publishedDate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

 引き出しの結果は以下。
1411111652098.jpg
 平均から大きく外れそうなデータははぶく。その上で最大値と最低値の平均をデータとして使うことにする。MySQLのパフォーマンスに大きく優位性が出ている。ここらはキャッシュ未設定で行っていたので、もしかしたらどちらもまだパフォーマンスを伸ばせるかもしれない。

MongoDB: 17[ms]
MySQL: 0.45[ms]

 以上で検討に必要なデータはそろえられた。データの書き込みと読み込みの比と処理時間の関係をMongoDBとMySQLのそれぞれでグラフ化する。

 上記のグラフから、書き込み回数が読み込みの回数の10倍を超えるとMySQLの処理時間がMongoDBを上回って増えているのがわかる。一方で10倍以下ではMySQLのほうがパフォーマンスがよかった。ここらはキャッシュ未設定で行っていたので、もしかしたらどちらもパフォーマンスを伸ばせるかもしれない。



 今回はMongoDBとMySQLのパフォーマンス比較をしたかったので、それぞれのデータストアをデフォルト状態で走らせ、少し適当な条件設定をして比較を行った。その条件下では書き込みを読み込みの10倍以上の頻度で行うならMongoDBを、そうでないならMySQLを使うといいということがわかった。
 しかし実際の状況となればそれぞれのデータストアを設定なしにデフォルトで使うことはないと思うし、クエリからデータを引き出すのも一件だけでなく複数件引き出すことも多々あり、今回の状況と違う。データストアへの命令も書き込みと読み込みだけではなく、更新や削除を含め多々ある。そういうことを考えていくと、目的を最高の効率で果たしてくれるデータストアを選ぶのは複雑で難しいと痛感する。

MongoDB VS. MySQL(with PyMongo or MySQL Connector)

                
 MongoDBのパフォーマンスとやらがアピール通りに優れているのか気になったので、MongoDBとMySQLのパフォーマンス比較をそれぞれの公式Pythonドライバを通じてやってみた。条件としてはVMに入れたLinux Mintで、MongoDBもMySQLもインストール直後のデフォルト状態で走らせた。
VirtualBox, CPU 1core, Memory 2GB
Linux Mint MATE Debian(64bit)
MongoDB 2.6.5, PyMongo 2.7.2
MySQL 5.5.33-1, MySQL Connector 2.0.2
Python 3.4.2

 レコードを1件ずつ10000回の書き込みと、そこからクエリによる100件のレコード取得を4パターンで行う。Pythonのfor文にて繰り返しを処理しているので、結果はそのあたりの経過時間も含まれたものになっている。
 レコードは本を想定した。タイトル、著者名、ISBN13を想定した長さ13のランダム文字列、発刊日(datetime)、評価(intで0-5)をカラムとして持つ。

 MongoDBへの書き込みは以下のように。
for book in books:
book_collection.insert(book)

 MySQLへは以下。なおテーブルはInnoDBで作成してある。
add_book = ("INSERT INTO books "
"(title, author, isbn_dummy, publishedDate, rate) "
"VALUES (%(title)s, %(author)s, %(isbn_dummy)s, %(publishedDate)s, %(rate)s)")
for book in books:
cursor.execute(add_book, book)
cnx.commit()


 MongoDBへのクエリとデータ取得。
c = book_collection.find({"author":"Albert Camus"}).sort("rate").skip(1000).limit(100)
camus = [x for x in c]
c = book_collection.find({"author":"Dostoevsky"}).sort("publishedDate").skip(1000).limit(100)
dostoevsky = [x for x in c]
c = book_collection.find({"author":"Herman Hesse", "rate":3}).sort("publishedDate").skip(0).limit(100)
hesse = [x for x in c]
c = book_collection.find({"author":{"$in":["Thomas Man", "William Golding"]}}).sort("publishedDate").skip(0).limit(100)
man_and_w = [x for x in c]

 MySQLへのクエリとデータ取得。
q = "SELECT * FROM books WHERE author = 'Albert Camus' ORDER BY rate LIMIT 100 OFFSET 1000"
cursor.execute(q)
camus = [x for x in cursor]
q = "SELECT * FROM books WHERE author = 'Dostoevsky' ORDER BY publishedDate LIMIT 100 OFFSET 1000"
cursor.execute(q)
dostoevsky = [x for x in cursor]
q = "SELECT * FROM books WHERE author = 'Herman Hesse' AND rate = 3 ORDER BY publishedDate LIMIT 100 OFFSET 0"
cursor.execute(q)
hesse = [x for x in cursor]
q = "SELECT * FROM books WHERE author IN ('Thomas Man', 'William Golding') ORDER BY publishedDate LIMIT 100 Ocursor.execute(q)
man_and_w = [x for x in cursor]


結果
1411091928136.jpg
 データの書き込みではMongoDBが5倍強ほど速かった。一方でクエリによるデータ取得ではMySQLのほうがすぐれていたが、1.3倍程度といったところか。
 ちょっと適当にやった試験なので真面目に結果を捉えるのもアレだが考えてみる。データ書き込みが取得に比べて極端に少ないケースなら、今回の結果からMySQLを使ったほうがいいだろう。一方でそこそこ書き込みが多いケースではMongoDBを使ったほうがよさそう。データストア選定のためにデータの書き込み命令と取得命令の比率と消費時間の関係(insert / select -- cost)を図にしたいが、クエリが適当だったのであまり意味をなさなそう。

 もうちょい環境が整えられればMongoDBをスケールさせたときの効果も試してみたい。使用したコードは改良してのちのち使うかもしれないのでzip化してまとめた。
to use
> python compare.py

PyMongo: http://api.mongodb.org/python/current/index.html
MySQL Connector: http://dev.mysql.com/doc/connector-python/en/index.html
プロフィール

h

Author:h

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon