jsluice 是一个 Go 包和命令行工具,用于从 JavaScript 源代码中提取 URL、路径、机密以及其他有趣的数据。
如果你想立即执行这些操作:请查看命令行工具。
如果你想将 jsluice 的功能集成到你自己的项目中:请查看示例,并阅读包文档。
要安装命令行工具,请运行:
▶ go install github.com/BishopFox/jsluice/cmd/jsluice@latest
要将该包添加到你的项目中,请运行:
▶ go get github.com/BishopFox/jsluice
jsluice 并非仅使用正则表达式,而是使用 go-tree-sitter 来查找已知会使用 URL 的位置,例如赋值给 document.location、传递给 window.open() 或传递给 fetch() 等。
这里提供了一个简单的示例程序(此处):
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)
}
运行该示例:
▶ 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\""
}
请注意,redirect 查询字符串参数的值是 EXPR。此类代码在 JavaScript 中很常见:
document.location = "/login?redirect=" + redirect + "&method=oauth"
jsluice 理解字符串拼接,并将任何无法确定其值的表达式替换为 EXPR。虽然这不是一个万无一失的解决方案,但这种方法通常仍能生成有效的 URL 或路径,也意味着可以发现一些使用其他方法不易发现的内容。在这种情况下,简单的正则表达式很可能会遗漏 method 查询字符串参数:
▶ JS='document.location = "/login?redirect=" + redirect + "&method=oauth"'
▶ echo $JS | grep -oE 'document\.location = "[^"]+"'
document.location = "/login?redirect="
jsluice 为常见场景内置了一些 URL 匹配器,但你也可以使用 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)
}
此示例的副本位于此处。你可以这样运行它:
▶ go run examples/urlmatcher/main.go
mailto:[email protected]
https://example.com
jsluice 默认不匹配 mailto: URI,该 URI 是由自定义的 URLMatcher 发现的。
除了 URL,jsluice 还可以提取机密。与 URL 提取一样,你也可以提供自定义匹配器来补充默认匹配器。这里有一个简短的示例程序(此处),它正是这样做的:
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)
}
运行该示例:
▶ 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/"
}
}
由于我们可以获得整个 JavaScript 源码的语法树,因此可以同时检查 key 和 value,还可以轻松地将父对象作为匹配的上下文提供。