Поиск секретов Yandex Cloud в открытых источниках
Yandex Cloud ищет следующие типы секретов в открытых источниках:
- API-ключи.
- IAM Cookies.
- IAM-токены.
- Статические ключи доступа.
- OAuth-токен.
- Серверные ключи SmartCaptcha.
- Refresh-токены.
- Секреты OIDC-приложений.
Yandex Cloud подключен к следующим программам поиска секретов:
- Партнерская программа поиска секретов Yandex Cloud.
- GitHub Secret scanning partner program.
- GitLab Secret Detection.
- Поисковый индекс Яндекс.
- Helm-чарты в Yandex Cloud Marketplace.
Партнерская программа поиска секретов Yandex Cloud
В Yandex Cloud действует собственная партнерская программа поиска скомпрометированных секретов в публичных репозиториях и других открытых источниках.
Вы можете присоединиться к партнерской программе поиска секретов Yandex Cloud, чтобы повысить безопасность ваших сервисов.
Для работы с партнерской программой вам потребуется облако. Рекомендуем создать отдельную организацию и отдельное облако в ней для работы с партнерской программой. Так вы не потеряете доступ к партнерской программе, даже если ваше основное облако окажется заблокировано или удалено.
Механизм работы партнерской программы
Для взаимодействия с Yandex Cloud в рамках партнерской программы используется сервисный аккаунт. При регистрации в программе вы передадите Yandex Cloud идентификатор вашего сервисного аккаунта и получите уникальный идентификатор leak_source, который будете использовать для взаимодействия с API.
Участвуя в партнерской программе поиска секретов Yandex Cloud, вы выполняете сканирование публичных репозиториев и других источников на наличие скомпрометированных секретов и передаете в Yandex Cloud данные о найденных ключах и токенах. Yandex Cloud проверяет полученные от вас секреты.
Взаимодействие с API Yandex Cloud происходит в два этапа:
-
Вы регулярно запрашиваете в Yandex Cloud актуальный список регулярных выражений, соответствующих известным типам секретов:
-
Эндпоинт запроса:
https://leak-detector.yandexcloud.net/secret-types. -
Метод запроса:
GET. -
Пример запроса:
curl \ --request GET \ --header "Authorization: Bearer <IAM-токен>" \ "https://leak-detector.yandexcloud.net/secret-types?leak_source_id=<идентификатор_leak_source>"Где:
<IAM-токен>— IAM-токен, полученный для зарегистрированного в партнерской программе сервисного аккаунта.<идентификатор_leak_source>— уникальный идентификатор, полученный при регистрации в партнерской программе.
Пример ответа:
[ { "type": "yandex_cloud_api_key_v1", "regex_matcher": "AQW9[A-Za-z0-9_-]{35,38}" }, { "type": "yandex_cloud_iam_access_secret", "regex_matcher": "YC[a-zA-Z0-9_\\-]{38}" }, { "type": "yandex_cloud_iam_cookie_v1", "regex_matcher": "c1\\.[A-Z0-9a-z_-]{200,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,910}[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2}" }, { "type": "yandex_cloud_iam_key_v1", "regex_matcher": "PLEASE DO NOT REMOVE THIS LINE\\! Yandex\\.Cloud SA Key ID (<|(\\\\u003[cC]))([0-9a-zA-Z+/=]*)(>|(\\\\u003[eE]))(\\s+)(-----BEGIN PRIVATE KEY-----(\\s+)([0-9a-zA-Z+/=]{64}(\\s+))*([0-9a-zA-Z+/=]{1,63}(\\s+))?-----END PRIVATE KEY-----\\s?)" }, { "type": "yandex_cloud_iam_refresh_token_v1", "regex_matcher": "rt1\\.[A-Z0-9a-z_-]{200,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,910}[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2}" }, { "type": "yandex_cloud_iam_token_v1", "regex_matcher": "t1\\.[A-Z0-9a-z_-]{200,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,1000}[A-Z0-9a-z_-]{0,910}[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2}" }, { "type": "yandex_cloud_lockbox_secret_v1", "regex_matcher": "(yc|YC)[!-~]{18,254}" }, { "type": "yandex_cloud_smartcaptcha_server_key", "regex_matcher": "ysc2_[a-zA-Z0-9]{40}[0-9a-f]{8}" }, { "type": "yandex_passport_oauth_token", "regex_matcher": "y[0-6]_[-_A-Za-z0-9]{55,199}" } ] -
-
Вы сканируете ваши данные на предмет поиска соответствий полученному списку регулярных выражений. При обнаружении таких соответствий вы отправляете данные о них в Yandex Cloud для проверки:
-
Эндпоинт запроса:
https://leak-detector.yandexcloud.net/suspects. -
Метод запроса:
POST. -
Пример запроса:
curl \ --request POST \ --header "Content-Type: application/json" \ --header "Authorization: Bearer <IAM-токен>" \ --data \ ''' { "leak_source_id": "my_leak_source", "suspects": [ { "data_type": "yandex_cloud_lockbox_secret_v1", "uri": "https://www.example.com/vcs/sources/project1/my_data.yaml", "payload": "ycBHKGefu78t^%RD3gre387HO" }, { "data_type": "yandex_cloud_iam_token_v1", "uri": "https://www.example.com/vcs/sources/project2/my_data.yaml", "payload": "t1.Aga0BCD123efGhIjkLmNoPqRsTuVwXyZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" } ] } ''' \ "https://leak-detector.yandexcloud.net/suspects"Где:
<IAM-токен>— IAM-токен, полученный для зарегистрированного в партнерской программе сервисного аккаунта.<идентификатор_leak_source>— уникальный идентификатор, полученный при регистрации в партнерской программе.suspects— список объектов, каждый из которых содержит информацию об одном найденном соответствии регулярному выражению.data_type— тип секрета, как он указан в ответе на GET-запрос при получении списка регулярных выражений.uri— URI ресурса, на котором обнаружена потенциальная утечка.payload— тело (содержимое) найденного секрета.
-
Пример ответа:
["NOT_CONFIRMED","CONFIRMED"]Ответ содержит список статусов. Количество и порядок возвращенных в ответе статусов соответствует количеству и порядку секретов, отправленных в GET-запросе в объекте
suspects.Возможные статусы:
NOT_CONFIRMED— обнаруженный случай соответствия регулярному выражению не является секретом.CONFIRMED— обнаруженный случай соответствия регулярному выражению является секретом.TEMPORARILY_UNAVAILABLE— провайдер секретов недоступен. Повторите запрос в отношении данного секрета позднее с применением алгоритма экспоненциальной выдержки .
-
Условия участия в программе
Партнерская программа поиска секретов Yandex Cloud действует в течение периода, установленного при вашем присоединении к ней. При отсутствии с вашей стороны заявления о выходе из программы ее действие продлевается на один год. Количество таких продлений не ограничено.
Сотрудничество с Yandex Cloud в рамках партнерской программы поиска секретов не предполагает материального вознаграждения и нацелено на совместное принятие мер по повышению уровня информационной безопасности.
Как присоединиться к программе
Чтобы присоединиться к программе, воспользуйтесь формой обратной связи
GitHub
Yandex Cloud подключен к secret scanning partner program
По умолчанию GitHub ищет секреты Yandex Cloud в публичных репозиториях и отправляет все подозрительные фрагменты в Yandex Cloud.
В публичных репозиториях поиск выполняется автоматически. Включить secret scanning
GitLab
Стандартный список
Чтобы включить Secret Detection для вашего проекта, следуйте инструкции
Поисковый индекс Яндекс
Yandex Cloud по умолчанию ищет секреты на страницах, которые проиндексированы поиском Яндекс.
Helm-чарты в Yandex Cloud Marketplace
Yandex Cloud по умолчанию ищет секреты в Helm-чартах
Как узнать, что секрет обнаружен
Если будет обнаружен валидный секрет, владельцу организации придет письмо c адреса технической поддержки Yandex Cloud. Письмо будет содержать часть обнаруженного секрета и адрес в интернете, где секрет был обнаружен.
Также Identity and Access Management запишет в аудитный лог событие DetectLeakedCredential.
Что делать если секрет обнаружен
Если ваш секрет попал в публичный репозиторий:
- Перевыпустите или отзовите секрет по инструкции. Удалите затронутые ресурсы при необходимости.
- Удалите секрет из репозитория и истории коммитов по инструкции для GitHub
или GitLab .
Важно
Yandex Cloud не отзывает найденные секреты и не удаляет их из репозитория. Все действия над секретом выполняет только владелец секрета.
Самостоятельный поиск секретов
Регулярные выражения для поиска
Вы можете использовать следующие регулярные выражения, чтобы самостоятельно проверять свои репозитории:
-
IAM Cookies
c1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2} -
IAM-токены
t1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2} -
API-ключи
AQVN[A-Za-z0-9_\-]{35,38} -
Статические ключи доступа
YC[a-zA-Z0-9_\-]{38} -
OAuth-токены
y[0-6]_[-_A-Za-z0-9]{55,199} -
Серверные ключи SmartCaptcha
ysc2_[a-zA-Z0-9]{40}[0-9a-f]{8} -
Refresh-токены
rt1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2} -
Секреты OIDC-приложений
yccs__[0-9a-f]{64}_[0-9a-f]{8}
Примечание
С осторожностью используйте регулярные выражения, так как со временем форматы секретов могут измениться. В документации эти изменения могут отразиться с задержкой.
Дополнительная валидация найденных секретов
При обнаружении некоторых типов секретов вероятно появление большого количества ложноположительных результатов. Чтобы удостовериться, что обнаружены актуальные секреты, рекомендуется проводить дополнительную валидацию.
-
OAuth-токены
Gopackage main import ( "encoding/base64" "fmt" "os" "strconv" "hash/crc32" ) const ( statefulMaskLen = 40 maxShard = 16 ) func isNotValidEnvironmentType(token string) bool { n, err := strconv.Atoi(token[1:2]) if err != nil { return true } if 0 <= n && n <= 6 { return false } return true } func isStatefulToken(token string) bool { if isNotValidEnvironmentType(token) { return false } decoded, err := base64.RawURLEncoding.DecodeString(token[3:]) if err != nil { return false } crc := uint32(0) for i := 0; i < 4; i++ { crc <<= 8 crc |= uint32(decoded[i+(len(decoded)-4)]) } if crc != crc32.Checksum(decoded[:len(decoded)-4], crc32.MakeTable(crc32.IEEE)) { return false } return true } func fatalf(msg string, a ...interface{}) { _, _ = fmt.Fprintf(os.Stderr, "oauth_filter: " + msg + "\n", a...) os.Exit(1) } func main() { fmt.Println(isStatefulToken("<TOKEN>")) } -
Статические ключи доступа
Gopackage main import ( base64 "encoding/base64" "encoding/binary" "fmt" "hash/crc32" "strings" ) const ( YcPrefix = "YC" ) func checkStaticCred(token string) bool { if !strings.HasPrefix(token, YcPrefix) { return false } decoded, err := base64.RawURLEncoding.DecodeString(token) if err != nil { return false } // CRC32-C checksum calculatedChecksum := crc32.Checksum(decoded[0:len(decoded)-4], crc32.MakeTable(crc32.Castagnoli)) checksum := binary.BigEndian.Uint32(decoded[len(decoded)-4:]) return calculatedChecksum == checksum } func main() { // ^YC[a-zA-Z0-9_\-]{38}$ - regexp fmt.Println(checkStaticCred("<TOKEN>")) fmt.Println(checkStaticCred("<TOKEN>")) } -
Серверные ключи SmartCaptcha
GoPythonpackage main import ( "fmt" "hash/crc32" ) func isValidToken(token string) bool { calculatedChecksum := crc32.Checksum([]byte(token[:len(token)-8]), crc32.MakeTable(crc32.IEEE)) return token[len(token)-8:] == fmt.Sprintf("%08x", calculatedChecksum) } func main() { fmt.Println(isValidToken("ysc2_D0ur60kwXTL7rM52UzJ7Vi5D7a5Qu48zktqy0fE0********")) }import re import zlib def is_valid_secret(secret): if not re.match("ysc2_[a-zA-Z0-9]{40}[0-9a-f]{8}", secret): return False if secret[-8:] != "%08x" % zlib.crc32(secret[:-8].encode()): return False return True print(is_valid_secret("ysc2_D0ur60kwXTL7rM52UzJ7Vi5D7a5Qu48zktqy0fE0********")) # True -
Секреты OIDC-приложений
GoJavapackage main import ( "encoding/binary" "encoding/hex" "fmt" "regexp" "github.com/spaolacci/murmur3" ) func ValidateOauthClientSecret(str string) bool { re := regexp.MustCompile(`yccs__[0-9a-f]{64}_[0-9a-f]{8}`) if !re.MatchString(str) { return false } prefix := str[:71] suffix := str[71:] hash := murmur3.Sum32([]byte(prefix)) hashBytes := make([]byte, 4) binary.BigEndian.PutUint32(hashBytes, hash) expectedSuffix := hex.EncodeToString(hashBytes) return suffix == expectedSuffix } func main() { test := "<SECRET>" isValid := ValidateOauthClientSecret(test) if isValid { fmt.Println("Secret is valid.") } else { fmt.Println("Secret isn't valid") } }package yandex.cloud.leakdetector.server.provider; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.MurmurHash3; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.regex.Pattern; public class Main { private static final Pattern SECRET_PATTERN = Pattern.compile("yccs__[0-9a-f]{64}_[0-9a-f]{8}"); public static boolean validateOauthClientSecret(String str) { if (!SECRET_PATTERN.matcher(str).matches()) { return false; } String prefix = str.substring(0, 71); String suffix = str.substring(71); String hashHex = hashStringMurmur3(prefix); return suffix.equals(hashHex); } private static String hashStringMurmur3(String str) { int intHashValue = MurmurHash3.hash32x86(str.getBytes()); return Hex.encodeHexString( ByteBuffer.allocate(4) .order(ByteOrder.BIG_ENDIAN) .putInt(intHashValue) .array() ); } public static void main(String[] args) { String test = "<SECRET>"; boolean isValid = validateOauthClientSecret(test); if (isValid) { System.out.println("Secret is valid."); } else { System.out.println("Secret isn't valid"); } } }