
Extrayez les URLs, chemins, secrets et autres éléments intéressants de JavaScript.
jsluice est un package Go et un outil en ligne de commande pour extraire des URL, des chemins, des secrets et d'autres données intéressantes à partir de code source JavaScript.
Si vous souhaitez faire ces choses immédiatement : consultez l'outil en ligne de commande.
Si vous souhaitez intégrer les capacités de jsluice à votre propre projet : consultez les exemples et lisez la documentation du package.
Pour installer l'outil en ligne de commande, exécutez :
▶ go install github.com/BishopFox/jsluice/cmd/jsluice@latest
Pour ajouter le package à votre projet, exécutez :
▶ go get github.com/BishopFox/jsluice
Plutôt que de s'appuyer uniquement sur des expressions régulières, jsluice utilise go-tree-sitter pour rechercher les endroits où les URL sont connues pour être utilisées, comme lorsqu'elles sont assignées à document.location, passées à window.open(), ou passées à fetch(), etc.
Un programme d'exemple simple est fourni ici :
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)
}
Exécution de l'exemple :
▶ 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\""
}
Notez que la valeur du paramètre de chaîne de requête redirect est EXPR.
Ce type de code est courant en JavaScript :
document.location = "/login?redirect=" + redirect + "&method=oauth"
jsluice comprend la concaténation de chaînes et remplace toute expression dont il ne peut pas connaître la valeur par EXPR. Bien que cette solution ne soit pas infaillible, elle aboutit le plus souvent à une URL ou à un chemin valide, et permet de découvrir des éléments difficiles à trouver avec d'autres approches. Dans ce cas, une expression régulière naïve pourrait facilement manquer le paramètre de chaîne de requête method :
▶ JS='document.location = "/login?redirect=" + redirect + "&method=oauth"'
▶ echo $JS | grep -oE 'document\.location = "[^"]+"'
document.location = "/login?redirect="
jsluice est fourni avec des correspondances d'URL intégrées pour les scénarios courants, mais vous pouvez en ajouter d'autres avec la fonction AddURLMatcher :
analyzer := jsluice.NewAnalyzer([]byte(`
var fn = () => {
var meta = {
contact: "mailto:[email protected]",
home: "https://example.com"
}
return meta
}
`))
analyzer.AddURLMatcher(
// The first value in the jsluice.URLMatcher struct is the type of node to look for.
// It can be one of "string", "assignment_expression", or "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)
}
Une copie de cet exemple est disponible ici. Vous pouvez l'exécuter comme ceci :
▶ go run examples/urlmatcher/main.go
mailto:[email protected]
https://example.com
jsluice ne reconnaît pas les URI mailto: par défaut ; celle-ci a été trouvée par la URLMatcher personnalisée.
En plus des URL, jsluice peut extraire des secrets. Comme pour l'extraction d'URL, des correspondances personnalisées peuvent être fournies pour compléter les correspondances par défaut. Un court programme d'exemple est fourni ici qui fait exactement cela :
analyzer := jsluice.NewAnalyzer([]byte(`
var config = {
apiKey: "AUTH_1a2b3c4d5e6f",
apiURL: "https://api.example.com/v2/"
}
`))
analyzer.AddSecretMatcher(
// The first value in the jsluice.SecretMatcher struct is a
// tree-sitter query to run on the JavaScript source.
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)
}
Exécution de l'exemple :
▶ 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/"
}
}
Comme nous disposons d'un arbre syntaxique pour l'ensemble du code source JavaScript, il a été possible d'inspecter à la fois la key et la value, et également de fournir facilement l'objet parent comme contexte pour la correspondance.