
Extrae URLs, rutas, secretos y otros datos interesantes de JavaScript
jsluice es un paquete de Go y una herramienta de línea de comandos para extraer URLs, rutas, secretos
y otros datos interesantes del código fuente de JavaScript.
Si quieres hacer estas cosas de inmediato: mira la herramienta de línea de comandos.
Si quieres integrar las capacidades de jsluice con tu propio proyecto: mira los ejemplos,
y lee la documentación del paquete.
Para instalar la herramienta de línea de comandos, ejecuta:
▶ go install github.com/BishopFox/jsluice/cmd/jsluice@latest
Para añadir el paquete a tu proyecto, ejecuta:
▶ go get github.com/BishopFox/jsluice
En lugar de usar solo expresiones regulares, jsluice utiliza go-tree-sitter para buscar los lugares donde se sabe que se usan URLs,
como asignaciones a document.location, usos en window.open(), o llamadas a fetch(), etc.
Aquí se proporciona un programa de ejemplo sencillo aquí:
analyzer := jsluice.NewAnalyzer([]byte(`
const login = (redirect) => {
document.location = "/login?redirect=" + redirect + "&method=oauth"
}
`))
for _, url := range analyzer.GetURLs() {
j, err := json.MarshalIndent(url, "", " ")
if err != nil {
continue
}
fmt.Printf("%s\n", j)
}
Ejecutando el ejemplo:
▶ go run examples/basic/main.go
{
"url": "/login?redirect=EXPR\u0026method=oauth",
"queryParams": [
"method",
"redirect"
],
"bodyParams": [],
"method": "GET",
"type": "locationAssignment",
"source": "document.location = \"/login?redirect=\" + redirect + \"\u0026method=oauth\""
}
Observa que el valor del parámetro de la cadena de consulta redirect es EXPR.
Este tipo de código es común en JavaScript:
document.location = "/login?redirect=" + redirect + "&method=oauth"
jsluice entiende la concatenación de cadenas y reemplaza cualquier expresión cuyo valor no pueda conocer
con EXPR. Aunque no es una solución infalible, este enfoque produce una URL o ruta válida
en la mayoría de los casos, y permite descubrir cosas que no se encuentran fácilmente con
otros enfoques. En este caso, una expresión regular ingenua probablemente no captaría el parámetro
method de la cadena de consulta:
▶ JS='document.location = "/login?redirect=" + redirect + "&method=oauth"'
▶ echo $JS | grep -oE 'document\.location = "[^"]+"'
document.location = "/login?redirect="
jsluice incluye algunos matchers de URL integrados para escenarios comunes, pero puedes añadir más
con la función AddURLMatcher:
analyzer := jsluice.NewAnalyzer([]byte(`
var fn = () => {
var meta = {
contact: "mailto:[email protected]",
home: "https://example.com"
}
return meta
}
`))
analyzer.AddURLMatcher(
// El primer valor en la estructura jsluice.URLMatcher es el tipo de nodo a buscar.
// Puede ser uno de "string", "assignment_expression", o "call_expression"
jsluice.URLMatcher{"string", func(n *jsluice.Node) *jsluice.URL {
val := n.DecodedString()
if !strings.HasPrefix(val, "mailto:") {
return nil
}
return &jsluice.URL{
URL: val,
Type: "mailto",
}
}},
)
for _, match := range analyzer.GetURLs() {
fmt.Println(match.URL)
}
Hay una copia de este ejemplo aquí. Puedes ejecutarlo así:
▶ go run examples/urlmatcher/main.go
mailto:[email protected]
https://example.com
jsluice no detecta URIs mailto: por defecto; la encontró el URLMatcher personalizado.
Además de URLs, jsluice puede extraer secretos. Al igual que con la extracción de URLs, se pueden
proporcionar matchers personalizados para complementar los matchers predeterminados. Hay un breve programa de ejemplo aquí
que hace precisamente eso:
analyzer := jsluice.NewAnalyzer([]byte(`
var config = {
apiKey: "AUTH_1a2b3c4d5e6f",
apiURL: "https://api.example.com/v2/"
}
`))
analyzer.AddSecretMatcher(
// El primer valor en la estructura jsluice.SecretMatcher es una
// consulta tree-sitter para ejecutar sobre el código fuente de JavaScript.
jsluice.SecretMatcher{"(pair) @match", func(n *jsluice.Node) *jsluice.Secret {
key := n.ChildByFieldName("key").DecodedString()
value := n.ChildByFieldName("value").DecodedString()
if !strings.Contains(key, "api") {
return nil
}
if !strings.HasPrefix(value, "AUTH_") {
return nil
}
return &jsluice.Secret{
Kind: "fakeApi",
Data: map[string]string{
"key": key,
"value": value,
},
Severity: jsluice.SeverityLow,
Context: n.Parent().AsMap(),
}
}},
)
for _, match := range analyzer.GetSecrets() {
j, err := json.MarshalIndent(match, "", " ")
if err != nil {
continue
}
fmt.Printf("%s\n", j)
}
Ejecutando el ejemplo:
▶ go run examples/secrets/main.go
[2023-06-14T13:04:16+0100]
{
"kind": "fakeApi",
"data": {
"key": "apiKey",
"value": "AUTH_1a2b3c4d5e6f"
},
"severity": "low",
"context": {
"apiKey": "AUTH_1a2b3c4d5e6f",
"apiURL": "https://api.example.com/v2/"
}
}
Debido a que disponemos de un árbol sintáctico para todo el código fuente de JavaScript,
fue posible inspeccionar tanto la key como el value, y también proporcionar fácilmente
el objeto padre como contexto para la coincidencia.