- Published on
- Updated on
Поиск секретов путем декомпиляции байт-кода Python в общедоступных репозиториях
- Authors
Эта публикация - перевод статьи. Ее автор - Jesse Li. Оригинал доступен по ссылке ниже:
Finding secrets by decompiling Python bytecode in public repositories
tl; dr: Кэш управляет всем, что меня окружает. pyc
файлы могут содержать секреты и не должны быть возвращены в систему контроля версий. Используйте стандартный Python .gitignore.
Когда вы импортируете файл Python в первый раз, интерпретатор Python скомпилирует его и кэширует полученный байткод в файл .pyc, чтобы последующему импорту не пришлось справляться с накладными расходами, связанными с повторным разбором или компиляцией кода.
Также обычной практикой для проектов Python является хранение конфигурации, ключей и паролей (совместно называемых "секретами") в файле Python с именем типа secrets.py, config.py или settings.py, который импортируется другими частями проекта. Это обеспечивает приятное разделение секретов и исходного кода, который проверяется, и по большей части такая настройка работает хорошо. А так как он использует механизм импорта языка, то этим проектам не нужно суетиться с файлами ввода/вывода или форматами вроде JSON.
Но по той же причине, по которой этот шаблон быстрый и удобный, он также потенциально небезопасен. Поскольку он использует механизм импорта языка, который имеет привычку создавать и кэшировать .pyc
файлы, эти секреты также хранятся в скомпилированном байт-коде! Некоторые первоначальные исследования с использованием API GitHub показывают, что тысячи репозиториев GitHub содержат секреты, скрытые внутри их байт-кода.
Существующие инструменты для поиска секретов в репозиториях (мой любимый трюфель) пропускают двоичные файлы, такие как .pyc
файлы, и вместо этого сканируют только простые текстовые файлы, такие как исходный код или файлы конфигурации.
Рассмотрите возможность пожертвования в залоговый фонд местного сообщества.
Ваши деньги будут платить за юридическую помощь и залог за протестующих, которые были арестованы за то, что противостояли жестокости полиции, институциональному расизму и убийствам чернокожих мужчин и женщин, таких как Джордж Флойд, Бреонна Тейлор, Ахмауд Арбери и Нина Поп.
В техническом сообществе мы много говорим об инклюзивности и разнообразии. Сейчас настало время предпринять конкретные действия.
https://www.communityjusticeexchange.org/nbfn-directory
Ускоренный курс по кешированному источнику
В более ранних версиях Python эти файлы хранились рядом с исходными файлами, но начиная с Python 3.2 все эти файлы находятся в папке, которая называется __pycache__
корневым каталогом импортированного модуля.
Предположим, у нас есть файл Python, содержащий этот секретный пароль:
SECRET_KEY = "Green eggs and ham"
Байт-код, соответствующий этой строке кода, выглядит следующим образом:
0 LOAD_CONST 1 ('Green eggs and ham')
2 STORE_FAST 0 (SECRET_KEY)
Обратите внимание, что имя переменной и строка воспроизводятся полностью! Кроме того, оказывается, что байт-код Python часто содержит достаточно информации для восстановления исходной структуры кода. Такие инструменты, как uncompyle6, могут переводить .pyc
файлы обратно в их исходные формы. *
$ uncompyle6 secrets.cpython-38.pyc
# uncompyle6 version 3.6.7
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.2 (default, Apr 8 2020, 14:31:25)
# [GCC 9.3.0]
# Embedded file name: secrets.py
# Compiled at: 2020-05-12 17:16:29
# Size of source mod 2**32: 34 bytes
SECRET_KEY = 'Green eggs and ham'
# okay decompiling secrets.cpython-38.pyc
Кэширование
Чтобы выяснить, насколько широко распространена эта проблема, я написал короткий скрипт для поиска .pyc
файлов в GitHub и декомпиляции их для поиска секретов. В итоге я нашел тысячи ключей Twitter, токенов Stripe, учетных данных AWS и паролей социальных сетей . Я предупредил все организации, ключи которых я нашел таким образом.
import base64
import io
import os
import tempfile
import uncompyle6
from github import Github
GITHUB_KEY = os.environ.get("GITHUB_KEY")
g = Github(GITHUB_KEY)
items = g.search_code("filename:secrets.pyc")
for item in items:
print(f"DECOMPILING REPO https://github.com/{item.repository.full_name}")
print(f"OWNER TYPE: {item.repository.owner.type}")
try:
contents = base64.b64decode(item.content)
with tempfile.NamedTemporaryFile(suffix=".pyc") as f:
f.write(contents)
f.seek(0)
out = io.StringIO()
uncompyle6.decompile_file(f.name, out)
out.seek(0)
print(out.read())
except Exception as e:
print(e)
print(f"COULD NOT DECOMPILE REPO https://github.com/{item.repository.full_name}")
continue
print("\n\n\n")
Попробуйте сами!
В этом посте есть небольшая лаборатория в стиле «поймай флаг», чтобы ты сам попробовал этот стиль атаки.
Вы можете найти его на https://github.com/veggiedefender/pyc-secret-lab/
Еда на вынос
Кэшированный байт-код - это низкоуровневая внутренняя оптимизация производительности, и именно это Python должен был освободить нас от необходимости думать! Содержимое .pyc
файлов является непонятным без специальных инструментов, таких как дизассемблер или декомпилятор. И когда эти файлы скрыты внутри __pycache__
( сигнал двойного подчеркивания «не использовать; только для внутреннего использования»), их легко не заметить. Многие текстовые редакторы и IDE скрывают эти папки и файлы от дерева исходных текстов, чтобы не загромождать экран, что позволяет легко забыть о том, что они вообще существуют.
То есть опытному программисту очень легко случайно передать свои секреты, и он почти гарантировал, что новичок совершит эту ошибку. Чтобы избежать этого, нужно либо стать удачливым с хорошим gitignore, либо получить промежуточные знания о внутренностях git и Python.
Актуальные предметы, которые вы можете сделать:
- Просмотрите ваши репозитории на наличие потерянных
.pyc
файлов и удалите их - Если у вас есть
.pyc
файлы и они содержат секреты, то отзовите и поверните свои секреты - Используйте стандартный gitignore для предотвращения проверки
.pyc
файлов - Используйте файлы JSON или переменные среды для конфигурации