のんびりやの日記

のんびり、たまに更新。

Pythonでユーザ辞書を作って文字列を変換しよう

カニ食べたい。どうも、だーやまんです。

これは、SLP KBIT Advent Calendar 2019 - Adventarの15日目の記事です。

始めに

Text To Speech APIを利用する際、API側で読み仮名が正しく認識されないことがある。造語などに関しては、いうまでもなく。そのような時、どのようにして送信した文字列を思い通りに読んでもらうか、という処理をPythonで行った場合を紹介する。

とりあえず

以下の通りに読んで欲しい文字列があるとする。ここでは、鉤括弧や句読点等は読み飛ばされるものとする。

文字列: 僕の禁断の過負荷 『却本作り』!!
読み: ぼくのはじまりのまいなす、ぶっくめーかー

( めだかボックス第11巻 p25 )
この場合、正しくない読み仮名と造語が入り混じった文になっている。まずはこれらの辞書を作成する

u_dict = { '禁断':'はじまり', '過負荷':'まいなす', '却本作り':'ブックメーカー' }

あとは、元の文字列に対して、辞書の登録数だけ一致する部分を変換するだけである。辞書に対して、 items() を使うことでキーと値のペアのタプルのリストを取得できる。あとは置換してやるだけである。

read_text = ' 僕の禁断の過負荷 『却本作り』!!'

for word, read in u_dict.items():
    read_text = read_text.replace(word, read)

print(read_text)

結果

僕のはじまりのまいなす 『ぶっくめーかー』!!

これで思い通りに変換ができた。これがとりあえずの実装になる。

問題点

以上の実装で、以下のような辞書と文字列の時、どうなるだろうか。

u_dict = {'蜂':'びー', 'びー':'ハエ'}

read_text = 'ぶんぶんぶん、蜂がとぶ。びーは蠅である。'

for文の1巡目で文中の「蜂」が「びー」に変換され、2巡目で「びー」が「ハエ」に変換される。その結果、

ぶんぶんぶん、ハエがとぶ。ハエは蠅である。

になる。
変換された読み仮名がさらに変換されるのは問題である。自分の思わぬ文字列に変換される恐れがあるし、文字列爆発による攻撃も可能になる。
文字列爆発の例

u_dict = { 'a':'bbb', 'b':'ccc'}

read_text = 'aaa'

変換結果

ccccccccccccccccccccccccccc

悪意のある文字列を登録された場合、文字列は指数関数的に伸ばすことが可能になるので、メモリへの攻撃が可能になる。

解決策

str型の組み込み関数、 format() を用いる。一度、文字列の変換該当部分を{}と変換先の文字列の引数番号にする。そうすることで二重の変換を防ぐことができる。コードを見てみよう。

u_dict = {'蜂':'びー', 'びー':'ハエ'}

read_text = 'ぶんぶんぶん、蜂がとぶ。びーは蠅である。'
print(read_text)

read_list = [] # あとでまとめて変換するときの読み仮名リスト
for i, one_dic in enumerate(u_dict.items()): # one_dicは単語と読みのタプル。添字はそれぞれ0と1。
    read_text = read_text.replace(one_dic[0], '{'+str(i)+'}')
    read_list.append(one_dic[1]) # 変換が発生した順に読みがなリストに追加
print(read_text)

read_text = read_text.format(*read_list) #読み仮名リストを引数にとる
print(read_text)

実行結果

ぶんぶんぶん、蜂がとぶ。びーは蠅である。
ぶんぶんぶん、{0}がとぶ。{1}は蠅である。
ぶんぶんぶん、びーがとぶ。ハエは蠅である。

いい感じに変換されました。

終わりに

辞書の登録順に変換されてしまうので、優先度等の重みつけはできていない。辞書型を使うのではなく、新しくクラスを定義することで可能になるかな〜。私が運用している喋太郎でも、今回紹介した方法を用いている。誰かの参考になれば幸い。

postgresのデータを盗まれた話

はじめに

さっぶ。どうも、だーやまんです。
この記事は、本番環境でやらかしちゃった人 Advent Calendar 2019 - Qiitaの11日目の記事です。
これは、中途半端な知識でサービスを運用していた結果、タイトル通りの大失敗をしてしまったお話です。個人開発での出来事なので、業務で起きたことかと胃薬を握られていた方はご安心ください。
語るのもすごい恥ずかしいレベルですが、戒めのために晒しておきます。

この記事を読んでほしい人

  • 初めてインターネット上にサービスを公開しようとしている人
  • 喋太郎の利用者様(この場をお借りして、改めてお詫び申し上げます。本当に申し訳ございませんでした。)

背景とか

  • Discord読み上げBot 「喋太郎」にてやらかしました

www.dayaman.work

  • 利用者が約10万人
  • さくらのVPSにてAppサーバ2台、DBサーバ1台で運用
    • 各サーバの死活監視にmackerelを利用
    • 被害があったのがDBサーバ
  • 基本的にだーやまん1人で運用
    • 得意技は時空の歪み(大学3年生2周目)

問題発生

  • DBの接続処理に不具合があり、修正のためにサービスを停止させた期間があった
    • この時、DBサーバ、Appサーバ共に起動したまま
    • 修正のための停止期間は20日ほど
      • これはこれで、どでかいやらかしなのでは...?(原因はサボり)
  • 15日目あたりでmackerelからDBサーバが落ちたり直ったりと繰り返しアラート
    • しかし普通にssh接続できる
    • 色々試したところ、通信が外へ出られないようだった(pingとかタイムアウトになる)
    • サーバ自体の再起動でとりあえずは治った
    • が、しばらくすると再発する
    • メールがうるさいのでサーバをシャットダウンする
  • 修正作業が終わり、DBサーバとアプリケーションを起動
  • ところが、「テーブルが存在しない」とエラー
  • は??????と思ってDBサーバにSSHしてpsqlコマンドでDBにログイン
    • 存在していたテーブルが消え去って「a_a_warning」というテーブルが一個だけ
    • テーブルを覗こうにも、そのDBのownerで開けない
    • はて?と思ってpostgresユーザに切り替えてテーブルを見る
    • レコードが一件だけ入っており、いんぐりっしゅな文字列が入っていた
Hello. You may be surprised to see this message, but it was your bad security practices that allowed us to steal your database. 
If you want to restore your data, and continue using it, send 0.075000 Bitcoin to [おそらくBitCoinのウォレットアドレス] and go to the page using tor browser [謎のURL]. 
In return, you will get the copy we have of your database. You can download the tor browser on the official website https://www.torproject.org/download/ | [おそらくBitCoinのウォレットアドレス]  | [謎のURL]

(意訳)(悪意マシマシ)
「おっすwwwww驚いた?wwwwガバガバセキュリティのおかげでwwwwデータ盗めたわwwww返して欲しかったらwwwウェwwwここにビットコインwwww振り込みよろwwwww」

やられた

とりあえず現在進行形で踏み台にされている可能性があるので、必要と思われるログや設定ファイルをまとめてメインPCに保存(それも安全なのかっていう)。その後、DBサーバをシャットダウン。 自分だけではログの読み方もいまいちわからなかったため、詳しい友人や、先輩に調査のお願いをした。

惨劇はなぜおこってしまったのか

直接的な原因

まず、postgresの設定をどのようになっていたのか

  • 2つのホストを指定してアクセスを許可
  • インターネットを介して接続
  • Portは5432
  • postgresユーザのパスワードが「postgres」(認証はmd5)

今思うと素敵な設定してますね。玄関の鍵を閉めないタイプか?

それでは、設定ファイル(pg_hba.conf)の接続可能ホストを確認してみよう。(IPアドレスは架空のもの)

# TYPE   DATABASE    USER    ADDRESS     METHOD
[省略]
host    all         all     184.14.25.76/0  md5
host    all         all     184.14.133.122/0    md5

サブネットマスクが0になってるね。これはIPアドレスの仕組みをよく理解しておらず、なんとなくググってこのような設定を見つけたので真似した結果。(「IPアドレス 一つ 指定」みたいな検索をした気がする)

追記
サブネットマスクが0であることの何がまずいか。
IPアドレスをどのような値にしたところで、 0.0.0.0 、つまりな任意のIPアドレスという意味になる。上記の設定の場合、接続ホストを制限するどころか、あらゆるホストの接続を許可したことになる。自宅が公共施設になってしまった。
この話がよくわからないという人は、サービスの公開をする前にネットワークを勉強しような。だーやまんの二の舞になるぞ。

ガバガバのガバ。ログとかめっちゃ攻撃された足跡あった。

どうもこの隙を突いて、任意のコマンドを実行、悪意のあるスクリプトをダウンロード・実行させられたらしい。ログファイルにダウンロードの痕跡と、見覚えのないファイルが/var/lib/pgsql/11/data/以下に存在していた。

mackerelから不自然なアラートが起きたときにもすぐに対応せずに放置したのもBadポイント。

間接的な原因

そもそも上記の事項は大学の講義で履修した内容であるので、いかに適当に単位をとってきたかが伺える結果。そら留年するわ。
さくらのVPSは無料でローカルネットワークを組めると知らなかったので、サーバ同士の通信がグローバルIPアドレスを用いて行われていた。

盗難にあったデータ

  • ユーザネームとユーザID、読み上げ音声設定
  • 各ギルド*1の名前とID、Botの利用上の設定
  • ユーザ辞書
  • 利用状況の統計

メールアドレスなど、直接的に個人と結びつくような情報は保存していない(できない)ため、流出しなかった。ユーザネームやユーザ辞書に個人情報が書かれていた場合はどうしようもない...。

二度と惨劇を起こさないためにどうしたのか

行動したこと

  • 被害にあったサーバは一度爆破してOSを再インストール
  • ネットワークの教科書を読み直した
  • 教科書以外にもググって納得がいくまで学習した
  • ローカルネットワークを構築
    • postgresがlistenするポートはローカルネットに向けて開放
    • DBへのアクセスはlocalhostか、ローカルネットワークの機器にのみ許可
    • ローカルネットワークからの接続可能ユーザはDBのownerに限定
  • 友人に依頼し、ポートチェックなど、とりあえず出来そうな攻撃を試してもらう

その他反省事項

  • しかるべき機関(警察やIPAとかJPCERT/CC)に相談せずにサーバを爆破したこと
  • ユーザへの謝罪を行ったものの、情報がまとまる前に公表してしまったので、かえって混乱を招いてしまった

教訓

  • インターネットは悪人が跋扈している
  • セキュリティがガバガバでもサービスは動く
  • サービスが動くからといって物事をドンドコ進めてはいけない
  • よくわからないことは調べてから動かす
  • 周りの人を頼る
  • 授業で得た知識は大事にするべき

さもなくば、信用を失う上に友人から一生煽られることになります。

追記 2019/12/11

早速、いくつか反応をいただけたので補足いたします。

サブネットマスクの不備とかそういうのだけじゃあ、破られないよね

そうですね。postgresユーザがデフォルトパスワードのままmd5になってました。書くの忘れてました...(今は変更してます)

ファイアウォールとか使ってないの?

使ってますよ〜。被害にあった時はssh用のポート(こっちは変更していた)と、5432ポートをインターネットに向けて開いてました。

なんで/0がだめなのか書いた方がいいんじゃないかな

記事中に追記しました。

初期パスワードってランダムじゃなかったっけ

あれ〜、じゃあ自分で「postgres」にしたのかしら。DBのownerは複雑なやつにしてたから割とまじで大馬鹿案件...?

学生のうちで幸運だったね

ほんまです。就職してからじゃあ洒落にならないことになってました。

*1:Discordでは1つのコミュニティをサーバと表記するが、混乱を防ぐためにここではギルドと表記

のんびりやの日記が色々と新しくなりました!

9月に入りましたがひたすら暑くて溶けてます。どうも、だーやまんです。
ドメインを取得して、当ブログのURLが変わったのでお知らせします。

https://www.dayaman.work

になりました。旧URLからでもリダイレクトされるのでブクマの再登録は大丈夫だとは思いますが、気になる方は再登録をお願いします〜。

それと、過去記事を大幅に消しちゃいました。お気に入りの記事があったら、ごめんなさいね〜。

でわでわ、今後とも当ブログをよろしくお願いいたします。

 
 ブログバナーです  
 ご自由にお使いください

リンク先は
http://www.dayaman.work
でお願いします
プライバシーポリシー お問い合わせ