
Extraia URLs, caminhos, segredos e outros trechos interessantes de JavaScript
jsluice é um pacote Go e uma ferramenta de linha de comando para extrair URLs, caminhos, segredos
e outros dados interessantes do código-fonte JavaScript.
Se você quer fazer essas coisas imediatamente: veja a ferramenta de linha de comando.
Se você quer integrar os recursos do jsluice ao seu próprio projeto: veja os exemplos,
e leia a documentação do pacote.
Para instalar a ferramenta de linha de comando, execute:
▶ go install github.com/BishopFox/jsluice/cmd/jsluice@latest
Para adicionar o pacote ao seu projeto, execute:
▶ go get github.com/BishopFox/jsluice
Em vez de usar apenas expressões regulares, o jsluice usa o go-tree-sitter para procurar lugares onde se sabe que URLs são usadas,
como em atribuições a document.location, passagens para window.open() ou fetch(), etc.
Um programa de exemplo simples é fornecido aqui:
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)
}
Executando o exemplo:
▶ 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\""
}
Observe que o valor do parâmetro de query string redirect é EXPR.
Códigos como este são comuns em JavaScript:
document.location = "/login?redirect=" + redirect + "&method=oauth"
O jsluice entende concatenação de strings e substitui por EXPR quaisquer expressões cujo valor ele não consegue determinar. Embora não seja uma solução infalível, essa abordagem resulta em uma URL ou caminho válido na maioria das vezes, e significa que é possível descobrir coisas que não são facilmente encontradas por outras abordagens. Nesse caso, uma expressão regular ingênua provavelmente deixaria passar o parâmetro de query string method:
▶ JS='document.location = "/login?redirect=" + redirect + "&method=oauth"'
▶ echo $JS | grep -oE 'document\.location = "[^"]+"'
document.location = "/login?redirect="
O jsluice vem com alguns matchers de URL embutidos para cenários comuns, mas você pode adicionar mais
com a função 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)
}
Há uma cópia deste exemplo aqui. Você pode executá-la assim:
▶ go run examples/urlmatcher/main.go
mailto:[email protected]
https://example.com
O jsluice não corresponde a URIs mailto: por padrão; ela foi encontrada pelo URLMatcher personalizado.
Além de URLs, o jsluice consegue extrair segredos. Assim como na extração de URLs, matchers personalizados podem
ser fornecidos para complementar os matchers padrão. Há um pequeno programa de exemplo aqui
que faz exatamente isso:
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)
}
Executando o exemplo:
▶ 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/"
}
}
Como temos uma árvore sintática disponível para todo o código-fonte JavaScript,
foi possível inspecionar tanto a key quanto o value, e também fornecer facilmente
o objeto pai como contexto para a correspondência.