Aikido

Raro, no aleatorio: Uso de la eficiencia de tokens para el escaneo de secretos

Escrito por
Zach Rice

Nota: Zach Rice, Jefe de escaneo de secretos en Aikido, es también el fundador de Gitleaks. Esta publicación apareció originalmente en su blog, donde cubre el escaneo de secretos, la ingeniería de software y temas de código abierto.

En Regex is (almost) All You Need, aprendimos que el uso de una combinación de patrones de expresiones regulares, entropía y filtros basados en reglas es una forma eficaz de detectar secretos candidatos. Las expresiones regulares se utilizan para lanzar una red amplia e identificar candidatos. La entropía se emplea como filtro principal sobre los candidatos capturados, y los filtros adicionales, como la presencia de palabras inglesas de uso común o el filtrado de archivos "seguros" conocidos como go.sum, se aplican al final.

La entropía hace un trabajo decente al filtrar falsos positivos, pero deja mucho que desear, especialmente al evaluar secretos genéricos. ¿Podría haber algo mejor que la entropía para ese filtro primario posterior a la captura de expresiones regulares? Esta publicación examina si la codificación de pares de bytes (Byte-Pair Encoding) puede servir como una alternativa más eficaz a la entropía para el escaneo de secretos.

Un raro-trébol de cuatro hojas

Entropía

¿Qué demonios es la entropía? Según John von Neumann, al hablar con Claude Shannon, “nadie lo sabe realmente”, pero Wikipedia sí. La entropía de Shannon mide la impredecibilidad promedio de una cadena, es decir, cuánta información lleva cada carácter. Cuando los caracteres están distribuidos uniformemente (muchos caracteres distintos, sin un patrón claro), cada uno es más difícil de predecir, por lo que la entropía es alta. Cuando unos pocos caracteres dominan, el siguiente carácter es fácil de adivinar, por lo que la entropía es baja. En la práctica, eso significa que algo como aaaaaa111111 obtiene una puntuación baja, mientras que algo como xA9fP2qL0sRw obtiene una puntuación alta. Con respecto a la detección de secretos, esto convierte la entropía en un primer paso decente para identificar cadenas de "apariencia aleatoria" (secretos candidatos).

¿Pero realmente queremos que la aleatoriedad sea nuestro filtro principal para la detección de secretos? Disculpen el tópico de LLM "no es X, es Y", pero los secretos no son solo aleatorios, son estadísticamente inusuales en comparación con la distribución natural del texto escrito por humanos. Dicho de forma más sencilla, los secretos son raros. Una cadena codificada en b64, un UUID, un secreto real y una cadena de dependencia de aspecto extraño pueden tener puntuaciones de entropía similares a pesar de ser fundamentalmente diferentes en la frecuencia con la que aparecen en el mundo real. La entropía no puede distinguir entre "esto parece aleatorio" y "esto casi nunca aparece en texto inglés o código fuente". En lugar de medir la aleatoriedad con la entropía, ¿qué pasaría si intentáramos medir cuán fuera de vocabulario o cuán no-lenguaje-natural es una cadena?

Codificación por pares de bytes

Entonces, ¿cómo detectamos cuán no-lenguaje-natural o rara es una cadena? ¡Con la codificación de pares de bytes (BPE), por supuesto! La tokenización mediante codificación de pares de bytes refleja implícitamente la distribución de frecuencia del texto en el que fue entrenada. Las palabras y subpalabras comunes se fusionan en tokens largos, mientras que las cadenas raras o antinaturales se dividen en muchos tokens cortos.

Aquí hay un par de ejemplos usando el tokenizador cl100k_base1:

  • “Hello World” → [15339, 1917]
  • “lookingatcomputer” → [20986, 266, 44211]
  • “kj2h3f2fuaafewa” → [93797, 17, 71, 18, 69, 17, 69, 4381, 2642, 365, 64]

Dado que BPE construye su vocabulario fusionando repetidamente los pares de caracteres más comunes en los datos de entrenamiento, su tokenización refleja naturalmente la frecuencia con la que aparecen diferentes patrones. Suena un poco a esa rareza que estamos intentando medir, ¿verdad?

Las palabras comunes en inglés obtienen sus propios tokens individuales porque aparecen con frecuencia en el entrenamiento, por ejemplo, “password” es el token [3918]. “github” es el token [5316]. “function” es el token [1723]. ¿Pero una clave API aleatoria como `ghp_xK7mP9qL2wR5nT3vJ8fY`?

ghp_xK7mP9qL2wR5nT3vJ8fY

Es probable que el tokenizador nunca haya visto esa secuencia específica durante el entrenamiento, por lo que divide la cadena en pares más pequeños, recurriendo finalmente a bytes individuales que terminan tokenizándose a [876, 79, 3292, 42, 22, 76, 47, 24, 80, 43, 17, 86, 49, 20, 77, 51, 18, 85, 41, 23, 69, 56]. Eso son 22 tokens para una cadena de 24 caracteres, lo que significa que el tokenizador apenas reconoció nada en ella.

Consulta https://tiktokenizer.vercel.app/?model=cl100k_base para ver cómo se tokenizan diferentes cadenas.

Eficiencia de tokens

Si los tokenizadores BPE dividen las cadenas raras en muchos tokens cortos, entonces podemos medir cuán rara es una cadena comparando la longitud de la cadena original con el número de tokens producidos. Es más, llamémoslo Eficiencia de Tokens.

token_efficiency = len(string) / len(tokens)

El lenguaje natural se mapea bien con el vocabulario del tokenizador, por lo que las frases comunes producen menos tokens. Las cadenas tipo secreto no lo hacen, por lo que producen muchos tokens.

Consideremos nuestro ejemplo de ghp_xK7mP9qL2wR5nT3vJ8fY. Tiene una eficiencia de tokens de 1.1 (una cadena de 24 caracteres que produce 22 tokens). Una frase como Hello World tiene una eficiencia de 3.7 (11 caracteres divididos en 3 tokens). Si los secretos producen consistentemente puntuaciones de eficiencia de tokens más bajas y el texto cotidiano produce puntuaciones más altas, entonces la eficiencia de tokens podría ser un filtro post-regex útil para la detección de secretos.

Para probar esta idea, podemos recurrir al conjunto de datos CredData, que contiene miles de ejemplos etiquetados de secretos verdaderos y no secretos extraídos de repositorios del mundo real. Si la eficiencia de tokens realmente rastrea la rareza o la “no-cotidianidad del lenguaje”, entonces observar la distribución de la eficiencia de tokens en los valores secretos de los conjuntos de datos de CredData podría revelar una brecha entre secretos y no secretos.

CredData

El conjunto de datos CredData se divide en archivos de índice y archivos de datos. Los archivos de índice almacenan los metadatos necesarios, como etiquetas, rangos de línea y columna, y nombres de archivo. No contienen los valores secretos reales, por lo que debe reconstruir cada secreto cortando los archivos fuente en los rangos especificados. Ese es el enfoque que adopté. Extraje cada valor secreto etiquetado directamente del conjunto de datos. Esto significa que no estamos evaluando si la eficiencia de tokens puede detectar secretos por sí misma. En cambio, estamos evaluando si la eficiencia de tokens puede clasificar secretos candidatos ya capturados, lo que la convierte en un paso de filtrado post-regex en lugar de un detector independiente.

Puedes consultar el código que produce estos gráficos aquí:

Los gráficos muestran que los secretos tienden a tener una menor eficiencia de tokens y una menor entropía que los no secretos, con patrones de caracteres más compactos y menos variados.

¡Eso parece prometedor! Parece que 2.5 es un buen umbral mínimo para la Eficiencia de Tokens. Gitleaks utiliza un umbral de entropía de 3.5 para secretos genéricos.

Usando esos umbrales, echemos un vistazo a las clasificaciones.

El filtro de eficiencia de tokens logra muchos menos falsos positivos a costa de más verdaderos negativos empujados por debajo del umbral en comparación con el filtro de entropía.
Eficiencia de los tokens: Precisión=57,3%  Recuperación =98,6%  F1 =0,725
Entropía:          Precisión=21,1%  Recuperación=70,4%  F1=0,325

Una exhaustividad del 98.6% es bastante buena. Estamos clasificando correctamente casi todos los secretos verdaderos, dejando solo 149 falsos negativos. Hay una cantidad considerable de falsos positivos para la Eficiencia de Tokens, pero la diferencia entre esta y la entropía es abismal. La entropía genera 28k FP (casi 4 veces más que la Eficiencia de Tokens) y 3k FN. La inclusión de un filtro de palabras simple ayuda a ambos métodos, pero la eficiencia de tokens sigue ganando en puntuación F1. El filtro de palabras ignora los secretos con más de una aparición de una palabra de 4 o más caracteres.

Añadir un filtro de palabras a los clasificadores de eficiencia de tokens y entropía reduce drásticamente los falsos positivos (hasta ~2.500 cada uno) mientras mantiene las tasas de verdaderos positivos relativamente estables.
TE + Filtro de palabras:         Precisión=80,4%  Recuperación=95,8%  F1 =0,874
Filtro de entropía + palabras:   Precisión=76,6%  Recuperación=67,1%  F1 =0,715

Este filtro realiza gran parte del trabajo pesado específicamente para la entropía, pero también nos ayuda a filtrar los falsos positivos (FP) para la eficiencia de tokens. Para la eficiencia de tokens, pasamos de 7894 FP a 2508 FP, introduciendo solo 308 nuevos falsos negativos (FN) al aplicar este filtro de palabras, lo que nos ayuda significativamente con la puntuación F1.

Si desea intentar reproducir estos resultados, puede consultar parte del código en mi Github.

Ejemplos

Echemos un vistazo a algunos secretos que la entropía pasa por alto, pero la eficiencia de tokens detecta.

e2aa9ae57d893a1
Este elemento tiene una entropía de 3.125. Bastante alta, pero no llega a 3.5, que es lo que Gitleaks y otros detectores de secretos utilizan como umbral. e2aa9ae57d893a1 produce [68, 17, 5418, 24, 6043, 3226, 67, 26088, 64, 717] para sus tokens cl100k_base, lo que resulta en una eficiencia de tokens de 1.6, muy por debajo del umbral de eficiencia de tokens de 2.5.

mcjrx4
Aquí tenemos una contraseña, y no una muy buena, por cierto. Las contraseñas son una categoría difícil para el filtro de entropía porque a menudo (y desafortunadamente) son cortas, y las cadenas cortas suelen tener valores de entropía bajos. Esta tiene una entropía de solo 2.58. Pero el tokenizador la descompone en tokens casi a nivel de byte [13183, 73, 12940, 19], dándole una eficiencia de tokens de 1.5. Seis caracteres, cuatro tokens. El tokenizador no la reconoce como lenguaje natural, y esa es exactamente la señal que queremos.

U@kkf8fo!!
Otra contraseña. Esta es interesante por los caracteres especiales. Uno de los desafíos en la detección de secretos, específicamente para secretos genéricos y contraseñas, es crear una expresión regular que capture la mayoría de los secretos. El problema de usar una expresión regular que busca capturar la mayoría secretos es que tiene el potencial de dejar pasar muchos falsos positivos, como correos electrónicos, URLs, etc. Así, por cada carácter especial como @ o ! o / que defina en la clase de caracteres de su grupo de captura, aumentará las posibilidades de permitir más falsos positivos. Debido a esto, podemos ver que el grupo de captura genérico de Gitleaks es bastante estricto: [\w.=-]{10,150}. Con un filtro de eficiencia de tokens, podríamos relajar ese patrón para incluir más caracteres especiales. Con este contexto, así es como se comparan la entropía y la eficiencia de tokens para este ejemplo. U@kkf8fo!! tiene una entropía de 2.72 y produce estos tokens [52, 31, 19747, 69, 23, 831, 51447] con una eficiencia de tokens de 1.42 (10 caracteres, 7 tokens).

Una breve nota sobre las contraseñas. La Eficiencia de Tokens no funciona bien clasificando contraseñas malas como "password123" o "chibearsfan123". Estas contraseñas son básicamente lenguaje natural, lo que significa un valor alto de eficiencia de tokens. Las frases de contraseña tampoco funcionan bien porque suelen ser simplemente palabras.

Rendimiento

El impacto en el rendimiento es insignificante. El tiempo promedio por cadena para calcular la entropía en los secretos de CredData capturados es de 4.55 µs frente a 11.75 µs para calcular la eficiencia de tokens2 (usando cl100k_base). Una diferencia de 2.5x puede parecer mucho, pero hay que recordar que, cuando se trata de detección de secretos, el cuello de botella son las expresiones regulares, no los filtros rápidos como la entropía o la eficiencia de tokens que vienen después.

Eficiencia de tokens con Betterleaks

Los mantenedores del conjunto de datos CredData crearon un impresionante escáner de secretos llamado CredSweeper que utiliza expresiones regulares, entropía y RNNs para detectar secretos. En un mundo lleno de afirmaciones como "los LLM pueden detectar secretos con CERO falsos positivos" (tanto en la academia como en la industria3), es refrescante ver a los ingenieros de Samsung desarrollando un detector de secretos basado en un "Machine Learning más tradicional". Enhorabuena. CredSweeper presume de una impresionante puntuación F1 de .85 cuando se prueba con CredData. ¡Eso es bastante bueno! Veamos si podemos superarlo con el nuevo filtro de Eficiencia de Tokens en Betterleaks.

Ah, claro. ¿Qué es Betterleaks? Es un nuevo proyecto que se basa en el legado de Gitleaks. Hablaré más sobre esto en otra publicación, pero todo lo que necesita saber es que es un sustituto directo de Gitleaks que estoy manteniendo... y va a ser mejor... por el nombre.

Esta configuración añade un par de reglas nuevas y ajusta algunas cosas menores en la configuración predeterminada existente. Usando esta configuración y ejecutando Betterleaks con el conjunto de datos CredData, se obtiene una puntuación F1 de .892.

(Eficiencia de los tokens + (baja) entropía en la regla genérica + ajustes en la regla) Resultados de las pruebas de rendimiento:
========================================
TP (Verdaderos positivos):     10796
FP (Falsos positivos):     1031
TN (Negativos verdaderos):     42 572
FN (Falsos negativos):     1578
----------------------------------------
Precisión:                    0,9534
Precisión:                   0,9128
Recuperación:                      0,8725
Puntuación F1:                    0,8922

Bastante bien.

Larry David piensa que es bastante, bastante bueno.

Usar solo la Eficiencia de Tokens nos da:

(Eficiencia de fichas + ajustes en las reglas) Resultados de las pruebas de rendimiento:
========================================
TP (Verdaderos positivos):     10843
FP (Falsos positivos):     1722
TN (Verdaderos negativos):     41 881
FN (Falsos negativos):     1531
----------------------------------------
Precisión:                    0,9419
Precisión:                   0,8630
Recuperación:                      0,8763
Puntuación F1:                    0,8696

Sin aplicar un umbral de baja entropía en la regla genérica al usar el filtro de Eficiencia de Tokens, introducimos aproximadamente 700 Falsos Positivos. Aun así, sin ese filtro de entropía en la regla genérica, obtenemos una puntuación F1 de .86, lo cual no está mal.

¿Cómo puntuamos sin el filtro de Eficiencia de Tokens, confiando en su lugar solo en ajustes de reglas y la entropía?

(Solo Entropy + ajustes en las reglas) Resultados de las pruebas de rendimiento:
========================================
TP (Verdaderos positivos):      8498
FP (Falsos positivos):     1041
TN (Verdaderos negativos):     42 562
FN (Falsos negativos):     3876
----------------------------------------
Precisión:                    0,9122
Precisión:                   0,8909
Recuperación:                      0,6868
Puntuación F1:                    0,7756

Bien, entonces .892 frente a .776 es una diferencia bastante grande. Usar solo el filtro de entropía añade más de 2000 Falsos Negativos y 80 Falsos Positivos en comparación con el filtro de Eficiencia de Tokens.

Puedes ver el código del filtro de Eficiencia de Tokens en el GitHub de Betterleaks.

func (d *Detector) failsTokenEfficiencyFilter(secret string) bool {
	analyzed := secret
	if len(analyzed) < 20 && strings.ContainsAny(analyzed, "\n\r") {
		analyzed = newlineReplacer.Replace(analyzed)
	}
	tokens := d.tokenizer.Encode(analyzed, nil, nil)
	matches := words.HasMatchInList(analyzed, 5)
	if len(matches) > 0 {
		return true
	}
	threshold := 2.5
	if len(analyzed) < 12 {
		threshold = 2.1
		matches := words.HasMatchInList(analyzed, 3)
		if len(matches) == 0 {
			threshold = 2.5
		}
	}
	return float64(len(analyzed))/float64(len(tokens)) >= threshold
}

El filtro está ligeramente adaptado en comparación con el utilizado en la comparación del gráfico. Esto es para tener en cuenta las contraseñas cortas y los secretos que contienen saltos de línea (eliminamos los saltos de línea antes de ejecutar el análisis de Eficiencia de Tokens en el candidato).

Un par de notas adicionales:

  • No pude hacer funcionar el script de evaluación comparativa proporcionado por CredData, así que yo (claude) creé el mío propio. Lamento si esto es mala ciencia, pero oye, puedes revisar mi trabajo (el de claude) ya que es de código abierto.
  • Se eliminaron los duplicados del conjunto de datos de CredData.
  • Todas las nuevas reglas y los ajustes a las reglas existentes no eran “específicas de secretos”. Es decir, intenté no manipular la evaluación comparativa.
  • Algunos secretos etiquetados como Falsos Positivos en el conjunto de datos de CredData parecen estar etiquetados erróneamente, así que, sinceramente, la puntuación F1 podría ser de +/- 0.05 quizás.

1 Para todos los ejemplos que analizamos, utilizaremos el tokenizador cl100k_base.

2 El tiempo se tomó del script generador de gráficos que calcula la entropía y la eficiencia de tokens para cada secreto candidato.

Agradecimientos: Quiero agradecer al usuario de GitHub “DmitriyAlergant” por enviar esta idea en una incidencia en el repositorio de Gitleaks. Un gran reconocimiento a los mantenedores de CredData/CredSweeper por haberme introducido en este tema tan complejo.

Compartir:

https://www.aikido.dev/blog/token-efficiency-secrets-scan

Empieza hoy, gratis.

Empieza gratis
Sin tarjeta

Suscríbase para recibir noticias sobre amenazas.

4.7/5
¿Cansado de los falsos positivos?

Prueba Aikido como otros 100k.
Empiece ahora
Obtenga un recorrido personalizado

Con la confianza de más de 100k equipos

Reservar ahora
Escanee su aplicación en busca de IDORs y rutas de ataque reales

Con la confianza de más de 100k equipos

Empezar a escanear
Vea cómo el pentesting de IA prueba su aplicación

Con la confianza de más de 100k equipos

Empezar a probar

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.