セキュリティ、負荷分析、営業担当者の統計や分析の作成、ニューラルネットワークのフィードなど、ログの監視や分析は、多くの問題に関連しています。
残念ながら、これは多くの場合、ヒューマンファクター、つまり、ログの監視に必要な情報を記録するプログラム、API、およびサービスの多くの開発者による非常に単純なことの不本意または誤解に関連しています。
以下は、これが頻繁に行われる方法と、そのように生きられない理由です。 ログ形式について話し、いくつかの例を分析し、いくつかの正規表現を書きます...
親愛なる同僚、もちろんそれはあなたのビジネス、プログラムのログにどのように、何を書き込むかですが、あなた自身のためだけにそれを行うかどうかを考える価値はまだあります。プログラム、さらには賢いものから不可能なものまでありますが、無駄にボットすることを誓います。
分析が困難なログ形式の別のファイルでこの投稿を書くことを余儀なくされたため、検索プロセスで既成のエクスプロイトを作成するまで、別の「脆弱性」が発生しました。
そして、ある開発者にこの記事について考えさせたら、それは大したことです。そして、おそらく彼のプログラムによって書かれた雑誌を分析するとき、彼らは彼を汚い言葉で覚えておらず、むしろ彼を感謝します。
まず、わいせつな...
clear; echo -e "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\nboYc0xU9sUMX7w2goPauyw==" | openssl enc -aes-128-cbc -a -d -salt -pass pass:wtf
おそらくgit-bashまたはmingwの下で啓示が開きますが、Windowsを使用している同僚に謝罪します...
すべて落ち着いて行きました...
(スキッドの脚注:言及されたエクスプロイトは記事にありません-自分で考えて書いてください)
それで、ロギングに関して開発世界で何が起こるか:
- ログエントリの形式がブルドーザーから書かれています。申し訳ありませんが、「標準」、構造、順序はまったくありません
- 多くの場合、記録構造自体は非常に動的であるため、「入力」データにほぼ完全に依存しています
- ユーザー入力または外部データはマスクされないことが多く(エスケープ)、とにかくフォーマットにランダムに干渉し、さらに悪いことに、多くの場合、有効な値またはマスクされた値があっても、元のファイルは何らかの理由でログに書き込まれます(それらをカットするのが良いです)困っている
- ソフトウェアの新しいバージョンの各次のリリースでは、ほとんどすべての興味深いログレコードの形式が必然的に変更されます(または認識を超えて完全に書き直されます)。これにより、すべてを解析して分析し、ソースを再度実行することができます。これらの存在)とログの束を生成し、20の異なる方法でプログラムをock笑します。
特定のケースで対処しなければならないこと(例としてfail2banを使用)と、これが少なくとも良くない理由についての私の小さな分析(eng)です。
次に詳細を示します。例として、次の2行を見てください。
Aug 18 08:04:51 srv sshd[2131]: Failed password for invalid user test from 1.2.3.4 port 46589 ssh2 from 4.3.2.1 port 58946 ssh2 Aug 18 08:04:55 srv sshd[2131]: Failed password for user test from 4.3.2.1 port 58946 ssh2: ruser from 1.2.3.4 port 46589 ssh2
ログアナライザー(別名ボット)をしばらく忘れて、人間の目で見てください。 ここですべてを理解していますか?
いいえ、彼らは何かを「悪用する」か、脆弱性を見つけようとしています。それは肉眼で見ることができます。 つまり 少なくとも、それぞれに2つの異なるIPアドレスが存在することを混同する必要があります。
問題は異なります。これら2つのアドレスのうち、どちらが悪いのでしょうか。
OpenSSHの興味深いソース( auth.c
モジュール)、つまりこれらの行が作成された場所を簡単に見てみましょう(はい、はい、あなたは正しく理解しました-それらは1つの関数によって作成されました)。
authmsg = authenticated ? "Accepted" : "Failed"; authlog("%s %s%s%s for %s%.100s from %.200s port %d ssh2%s%s", authmsg, method, submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod, authctxt->valid ? "" : "invalid user ", authctxt->user, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), authctxt->info != NULL ? ": " : "", authctxt->info != NULL ? authctxt->info : "");
すでにはるかに明確ですよね? さて、あなたはすでに答えを知っていますか? それでもない?..うーん...
さて、私は陰謀を長引かせません:これは4.3.2.1です
最初のケースでは、ホスト4.3.2.1から「 authctxt->user
on username」( authctxt->user
)をユーザー名- "test from 1.2.3.4 port 46589 ssh2"
実行しようとし"test from 1.2.3.4 port 46589 ssh2"
。
2番目のケースでは、ホスト4.3.2.1から、 "ruser from 1.2.3.4 port 46589 ssh2"
等しい値で"ruser from 1.2.3.4 port 46589 ssh2"
into info」( authctxt->info
)を実行しようとし"ruser from 1.2.3.4 port 46589 ssh2"
。
直感的な記録形式ですか?
この特定のケースを解決する鍵は、 authctxt->info != NULL ? ": " : "",
によって作成されるコロンの存在authctxt->info != NULL ? ": " : "",
authctxt->info != NULL ? ": " : "",
この傑作の開発者が考えていたこと(私は本当に理解していません...
ここで、セキュリティ監視(具体的には、たとえばfail2banなど)の観点から、いわゆる「構造」のマシン分析の複雑さを評価します。 評価する際、HOST(またはIPアドレス)は私たちにとって最も重要です。この特定の例でそれを取得することの難しさは、後者の場所が予測できないためです。 はい、常にfrom
後に続きますfrom
、外部データのマスキングが欠落し、このデータの後にログに書き込まれるため(6番目!パラメーター、 ssh_remote_ipaddr(ssh)
)、現在の位置を決定することは非常に困難です。
簡単な方法を探しているわけではありません(実際、選択の余地がありませんでした)。そのため、複雑さの例として、このエントリに適した正規表現を収集しようとします。
pythonの正規表現構文を使用します(fail2banを作成する言語として)...
まず、「静的」で厳密に型指定されたコンポーネント:
- レコード「構造」自体は
Failed ... for ... from ... port ... ssh2
- メソッド+サブメソッド-
\S+
(パスワード、チャレンジ/レスポンス、公開鍵、ホストベース、gssapi-with-micなど) - オプションの無効なログイン-
(?:invalid user )?
- ホストアドレス、簡単にするためにIPv4を使用します-
(?:(?:\d{1,3}\.){3}\d{1,3})
- ポート-
\d+
それがすべて、今では「ダイナミクス」です:
- ユーザー名(今のところ、簡単にするために、正直なユーザー、つまりスペースがないと仮定します)-
\S*
- 認証方法などからの記録の最後のオプション情報 (今のところ、簡単にするために、すべてを最後まで続け
(?:: .*)?$
)-(?:: .*)?$
つまり 両側の信頼性を固定する次の式を取得します( ^...$
):
^Failed (?P<meth>\S+) for (?P<valid>invalid user )?(?P<user>\S*) from (?P<host>(?:\d{1,3}\.){3}\d{1,3})(?: port \d*)?(?: ssh\d*)?(?P<info>: .*)?$
最も単純なケースが機能することを示す2つの例によるチェック:
## (bash): $ _test() { python -c 'import sys, re; regex, log = sys.argv[1:]; print(log); r = re.search(regex, log); print(r.groupdict() if r else "*NOT-FOUND*")' "$1" "$2"; }; alias t=_test; ## : $ regex='^Failed (?P<meth>\S+) for (?P<valid>invalid user )?(?P<user>\S*) from (?P<host>(?:\d{1,3}\.){3}\d{1,3})(?: port \d*)?(?: ssh\d*)?(?P<info>: .*)?$' ## № 1 $ t "$regex" 'Failed password for invalid user test from 4.3.2.1 port 58946 ssh2' {'info': None, 'host': '4.3.2.1', 'valid': 'invalid user ', 'meth': 'password', 'user': 'test'} ## № 2 $ t "$regex" 'Failed publickey for root from 4.3.2.1 port 58946 ssh2: RSA SHA256:v3dpapGleDaUKf...' {'info': ': RSA SHA256:v3dpapGleDaUKf...', 'host': '4.3.2.1', 'valid': None, 'meth': 'publickey', 'user': 'root'}
では、欲張りでないキャッチオールを使用して条件(ユーザー名にスペースが含まれる)を複雑にしようとしますが、私はそれらを好きではありませんが、覚えています-私たちはあまり選択肢がありませんでした。 つまり を使用します.*?
ユーザー名に\S+
代わりに。
なぜこれが良くないのか-たとえば、右側のアンカーはほとんど開いているので、 .*$
は右側のアンカーなしの開いた式に相当します。 長い行での速度とCPUロードについては、すでに沈黙しています。 しかし、今のところ、このように続けましょう(この場合、少なくともコロンが必須です):
$ regex='^Failed (?P<meth>\S+) for (?P<valid>invalid user )?(?P<user>.*?) from (?P<host>(?:\d{1,3}\.){3}\d{1,3})(?: port \d*)?(?: ssh\d*)?(?P<info>: .*)?$' $ t "$regex" 'Failed password for invalid user hello from space from 4.3.2.1 port 58946 ssh2' {'info': None, 'host': '4.3.2.1', 'valid': 'invalid user ', 'meth': 'password', 'user': 'hello from space'}
動作します! さて、今度は注入を使って上の例を試してみましょう:
$ t "$regex" 'Failed password for invalid user test from 1.2.3.4 port 46589 ssh2 from 4.3.2.1 port 58946 ssh2' {'info': None, 'host': '4.3.2.1', 'valid': 'invalid user ', 'meth': 'password', 'user': 'test from 1.2.3.4 port 46589 ssh2'} $ t "$regex" 'Failed password for user test from 4.3.2.1 port 58946 ssh2: ruser from 1.2.3.4 port 46589 ssh2' {'info': ': ruser from 1.2.3.4 port 46589 ssh2', 'host': '4.3.2.1', 'valid': None, 'meth': 'password', 'user': 'user test'}
私たちが見るところ、それはまた正しく動作しているようです(どちらの場合も'host': '4.3.2.1'
正しい値'host': '4.3.2.1'
を持っています)。
しかし... ...常に、「しかし」がありますよね?
これらの例はどちらも、キャッチオールの望ましくない使用を考慮しなくても最も単純であり、より複雑なインジェクションを考え出すと、式は「壊れる」か、間違ったデータをはるかに悪く返します(理論的には脆弱性ですfail2banは「エイリアン」ホストをブロックするか、「見えない」ためにパスワードを無期限に通過します)。
ここにはグラインダーを含めず、すぐに「正しい」(いいえ、むしろより適切な)表現をします。 私もあまり好きではありません(多くの理由で)が、何があるのですか-それは...
^Failed (?P<meth>\S+) for (?P<cond_inv>invalid user )?(?P<user>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)) from (?P<host>(?:\d{1,3}\.){3}\d{1,3})(?: port \d+)?(?: ssh\d*)?(?(cond_user):|(?P<info>(?:(?! from ).)*)$)
以下に、その機能について少し説明します。 しかし、なぜそれがどのような種類の注射(テストケース)でカバーされるのか、今のところは黙っています...
まあ、一種の宿題にしましょう、またはスクリプトキディを誘惑されたくない場合は、一方で彼らは何かを学ぶ必要があります...
だから-これは、Pythonでは次のように見える条件付きの「遷移」を持つ複雑な(従属)式です
(?P<->)? ... (?(-) -1 | -2)
簡単に言えば、なぜそれが難しいのか(部下):
最も単純なユーザー名、正確に1つの
" from "
(または" from "
から":"
なく、":"
後の":"
" from "
はない)がある場合、式は完全に右側に固定されます。 右側の条件付きアンカーは重要な役割を果たしますが、これはこのすべてを完全に検証する必要があるためです
または、
":"
がない場合(通常はssh2で終わります)、この場合、最後の" from "
後のホスト" from "
優先されます
- そうでない場合は、最初の
" from "
以降のホストを常に優先します。
はい、 "(?:(?! from ).)*"
という表現は"(?:(?! from ).)*"
条件付き)キャッチオールであり、(これまでに) " from "
一致しない場合にすべてを収集します。
実際、原理的には通常のルールでは理解されていない、または完全に構造的なログまで、指定された例よりもはるかに複雑なログがあります(または、その複雑さのために、3階建ての条件付き遷移があなたの代わりに脳を取り去ってしまいます)。 トレーラーによって複数のレコードからデータを収集できる場合があります(それらに共通の識別子がある場合)。
残念ながら、ニューラルネットワークも万能薬ではありません。 原則として、最初に必要な情報を提供する必要があります。学習プロセスでは、理想的には「ごみ」を収集してはいけません。
残念ながら、そのようなログは私たちが望むよりも一般的であり、ログの「メーカー」には他の多くの質問がしばしばあります。 論争はしばしばこの理由で起こります(たとえば、 ヤリコプティック牧師の謙hな僕)-レギュラーシーズンをどのように(どれだけ厳密に)設計するのが良いか:
ペイロードを運ばない最初の動的コンポーネントまでの既知の安定した情報をカバーする短い表現(右側のアンカーではない)を使用します。これは、開発者が明日ログを変更または書き換えた場合、理論的にはある程度のリスクがあります(クラス内ではなく同様のログエントリが表示されますが、望ましい)。
何らかの理由で、これにより、前述の動的コンポーネントがほぼ完全に変更された場合にログを正常に解析できるようになります(重要ではないことを覚えています)。
- または(現在知られている)構造を完全にカバーします(両端にアンカーがある)。これは他のリスクに関連しています-わずかな逸脱であっても、一部のレコードが失われます(目的の表現に対応しなくなるため)。
結論の代わりに、もう少し詳細に、私が信じているように、あなたはロギングを行う必要があります(それがAPIであれ、最も複雑なサーバーであれ):
一意のレコード識別子を持つ(そして開始する)のが望ましいです(たとえば、ここでは「認証試行:」のような何らかのプレフィックスが適切です)
レコードの先頭に、常に静的で常に有効な厳密に型指定されたデータを書き込みます(この例では、HOST、識別方法、ユーザーの存在、または「無効なユーザー」)
レコードの動的コンポーネントまたは強力な可変コンポーネントをそれぞれ最後に配置します(たとえば、クライアントによって送信されたユーザー名および/またはauthctxt->情報)
可能であれば、既にマスクされた(およびトリミングされた)型なしデータを書き込むことをお勧めします。 少なくとも改行(
"\n" -> "\\n"
)をエスケープします。レコードの区切り文字として、および記録形式の構造でブロック区切り文字として使用されるいくつかの特殊文字(コンマやコロンなど)
ログレコードの構造は、リリースで最初に表示される前に十分に検討する必要があります(次の段落で重要)
可能であれば、ログ構造を変更しないでください。 リリースですでに公開されているログエントリの構造は凍結され、永久に保存されます(少なくとも部分的に、少なくとも多かれ少なかれ静的コンポーネント)
-
脳が外国からの注入データなどに対して脆弱な記録の曖昧な解釈を許容できる構造を避けることが望ましい。
さて、この特定のエントリでは、次のようになります(すべてが先頭に「強く型付け」され、ユーザー名とその他の動的情報が末尾に、たとえば引用符で囲まれます;さて、上記のurl_encodeのようにマスクします(引用符、スペース):
Auth attempt: Failed password from 4.3.2.1 port 58946 ssh2, invalid user: "test+from+1.2.3.4+port+46589+ssh2" Auth attempt: Failed password from 4.3.2.1 port 58946 ssh2, user: "test", info: "ruser+from+1.2.3.4+port+46589+ssh2" Auth attempt: Failed publickey from 4.3.2.1 port 58946 ssh2, user: "root", info: "RSA+SHA256:v3dpapGleDaUKf..."
実際、このようなポイントはさらに多くありますが、少なくともこれらのルールまたはそれらの一部に従うと、多くの人々(だけでなく)の世界が再び新しい色で輝きます。
感謝の気持ちのあるユーザー、ログに精通している同僚、特に一部(人工知能、あらゆる種類のニューラルネットワーク、その他のボットの負担)からの多くの感謝は、カルマに感謝の意を表します。