Skip to content

Commit 851c13f

Browse files
committed
checkers(javascript): detect usage of SHA-1 algorithm inside js code using scope and data-flow logic
1 parent fa25e3b commit 851c13f

File tree

3 files changed

+130
-9
lines changed

3 files changed

+130
-9
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package javascript
2+
3+
import (
4+
"slices"
5+
6+
sitter "github.com/smacker/go-tree-sitter"
7+
"globstar.dev/analysis"
8+
)
9+
10+
var Sha1 = &analysis.Analyzer{
11+
Name: "sha1_detector",
12+
Language: analysis.LangJs,
13+
Description: "Avoid using SHA1 for cryptographic purposes",
14+
Category: analysis.CategorySecurity,
15+
Severity: analysis.SeverityWarning,
16+
Requires: []*analysis.Analyzer{DataFlowAnalyzer},
17+
Run: detectSha1Usage,
18+
}
19+
20+
func detectSha1Usage(pass *analysis.Pass) (interface{}, error) {
21+
pkgs := []string{"jssha", "jssha/sha1", "jssha/dist/sha1"}
22+
23+
// Will be used to track the Encrypting library being used
24+
var pkgDeclaratorVar *analysis.Variable
25+
26+
dfg := pass.ResultOf[DataFlowAnalyzer].(*DataFlowGraph)
27+
28+
if dfg == nil {
29+
return nil, nil
30+
}
31+
32+
scopeTree := dfg.ScopeTree
33+
34+
if scopeTree == nil {
35+
return nil, nil
36+
}
37+
38+
// gather the variable linking to the js-sha package.
39+
analysis.Preorder(pass, func(node *sitter.Node) {
40+
if node == nil {
41+
return
42+
}
43+
44+
if node.Type() == "variable_declarator" {
45+
nameNode := node.ChildByFieldName("name")
46+
valueNode := node.ChildByFieldName("value")
47+
if valueNode != nil {
48+
if valueNode.Type() == "call_expression" {
49+
funcNode := valueNode.ChildByFieldName("function")
50+
if funcNode != nil && funcNode.Type() == "member_expression" {
51+
if funcNode.Content(pass.FileContext.Source) == "require" {
52+
// check if the value inside the `require` is a valid package
53+
packageName := valueNode.ChildByFieldName("arguments").Child(0)
54+
if packageName != nil && packageName.Type() == "string" {
55+
packageNameContent := packageName.Content(pass.FileContext.Source)
56+
if !slices.Contains(pkgs, packageNameContent) {
57+
// If the package is not in the list, we can ignore it
58+
return
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
66+
if nameNode != nil && nameNode.Type() == "identifier" {
67+
varName := nameNode.Content(pass.FileContext.Source)
68+
if varName != "" {
69+
nameVar := scopeTree.GetScope(node).Lookup(varName)
70+
if nameVar != nil {
71+
pkgDeclaratorVar = nameVar
72+
}
73+
}
74+
}
75+
}
76+
})
77+
78+
analysis.Preorder(pass, func(node *sitter.Node) {
79+
if node == nil {
80+
return
81+
}
82+
83+
if node.Type() == "new_expression" {
84+
// fmt.Println("+++++++++++++++++", node.Content(pass.FileContext.Source))
85+
ctor := node.ChildByFieldName("constructor")
86+
arg := node.ChildByFieldName("arguments")
87+
if ctor != nil && arg != nil {
88+
ctorVar := scopeTree.GetScope(ctor).Lookup(ctor.Content(pass.FileContext.Source))
89+
// fmt.Println("++++++ctorVar+++++++", ctorVar, "++++++++++++")
90+
// fmt.Println("++++++pkgDeclaratorVar+++++++", pkgDeclaratorVar, "++++++++++++")
91+
if ctorVar != nil && ctorVar == pkgDeclaratorVar {
92+
hashAlgo := arg.NamedChild(0)
93+
// fmt.Println("++++++hashAlgo+++++++", hashAlgo, "++++++++++++")
94+
if hashAlgo == nil {
95+
return
96+
}
97+
98+
hashAlgoStr := hashAlgo.NamedChild(0)
99+
hashAlgoName := hashAlgoStr.Content(pass.FileContext.Source)
100+
// fmt.Println("++++++hashAlgoName+++++++", hashAlgoName, "++++++++++++")
101+
if hashAlgoName == "SHA-1" {
102+
// fmt.Println("++++++hashAlgoNameConditionTrue+++++++", hashAlgoName, "++++++++++++")
103+
pass.Report(pass, node, "SHA-1 is not recommended for cryptographic purposes")
104+
}
105+
106+
}
107+
}
108+
109+
}
110+
})
111+
112+
return nil, nil
113+
114+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const jsSHA = require("jssha");
2+
3+
// ok
4+
new jsSHA("SHA-512", "TEXT", { encoding: "UTF8" });
5+
6+
// <expect-error>
7+
new jsSHA("SHA-1", "TEXT", { encoding: "UTF8" });

pkg/analysis/scope_ts_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ func Test_BuildScopeTree(t *testing.T) {
2525

2626
scopeTree := MakeScopeTree(parsed.Language, parsed.Ast, parsed.Source)
2727
require.NotNil(t, scopeTree)
28-
29-
varX, exists := scopeTree.Root.Variables["x"]
28+
globalScope := scopeTree.Root.Children[0]
29+
varX, exists := globalScope.Variables["x"]
3030
require.True(t, exists)
3131
require.NotNil(t, varX)
3232

33-
varY, exists := scopeTree.Root.Children[0].Variables["y"]
33+
varY, exists := globalScope.Children[0].Variables["y"]
3434
require.True(t, exists)
3535
require.NotNil(t, varY)
3636
require.Equal(t, VarKindVariable, varY.Kind)
@@ -63,9 +63,9 @@ func Test_BuildScopeTree(t *testing.T) {
6363

6464
scopeTree := MakeScopeTree(parsed.Language, parsed.Ast, parsed.Source)
6565
require.NotNil(t, scopeTree)
66-
66+
globalScope := scopeTree.Root.Children[0]
6767
{
68-
varR, exists := scopeTree.Root.Variables["r"]
68+
varR, exists := globalScope.Variables["r"]
6969
require.True(t, exists)
7070
require.NotNil(t, varR)
7171

@@ -77,7 +77,7 @@ func Test_BuildScopeTree(t *testing.T) {
7777
}
7878

7979
{
80-
varExtname, exists := scopeTree.Root.Variables["extname"]
80+
varExtname, exists := globalScope.Variables["extname"]
8181
require.True(t, exists)
8282
require.NotNil(t, varExtname)
8383

@@ -102,11 +102,11 @@ func Test_BuildScopeTree(t *testing.T) {
102102
parsed := parseFile(t, source)
103103
require.NotNil(t, parsed)
104104
scopeTree := MakeScopeTree(parsed.Language, parsed.Ast, parsed.Source)
105+
globalScope := scopeTree.Root.Children[0]
105106
// Checking function declaration
106-
t.Log(scopeTree.Root.Variables)
107-
funcVar := scopeTree.Root.Lookup("greet")
107+
funcVar := globalScope.Lookup("greet")
108108
require.NotNil(t, funcVar)
109-
funcVariable, exists := scopeTree.Root.Variables["greet"] // tagged as an Identifier
109+
funcVariable, exists := globalScope.Variables["greet"] // tagged as an Identifier
110110
require.True(t, exists)
111111
require.NotNil(t, funcVariable)
112112

0 commit comments

Comments
 (0)