Поиск секретов Yandex Cloud в открытых источниках
Yandex Cloud ищет следующие типы секретов в открытых источниках:
- API-ключи.
- IAM Cookies.
- IAM-токены.
- Статические ключи доступа.
- OAuth-токен.
- Серверные ключи SmartCaptcha.
- Refresh-токены.
- Секреты OIDC-приложений.
Yandex Cloud подключен к следующим программам поиска секретов:
- GitHub Secret scanning partner program.
- GitLab Secret Detection.
- Поисковый индекс Яндекс.
- Helm-чарты в Yandex Cloud Marketplace.
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"); } } }