Skip to content

Commit 745a13a

Browse files
Merge branch 'master' into checkers/django-user-tainted-data-pass
Signed-off-by: Sourya Vatsyayan <sourya@deepsource.io>
2 parents 4abde69 + c7096a2 commit 745a13a

File tree

3 files changed

+293
-1
lines changed

3 files changed

+293
-1
lines changed

checkers/checker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ var AnalyzerRegistry = []Analyzer{
7272
},
7373
{
7474
TestDir: "checkers/python/testdata",
75-
Analyzers: []*goAnalysis.Analyzer{python.InsecureUrllibFtp, python.DjangoRequestDataWrite, python.DjangoRequestHttpResponse},
75+
Analyzers: []*goAnalysis.Analyzer{python.InsecureUrllibFtp, python.DjangoInsecurePickleDeserialize, python.DjangoRequestDataWrite, python.DjangoRequestHttpResponse},
7676
},
7777
}
7878

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package python
2+
3+
import (
4+
// "fmt"
5+
"slices"
6+
"strings"
7+
8+
sitter "github.com/smacker/go-tree-sitter"
9+
"globstar.dev/analysis"
10+
)
11+
12+
var DjangoInsecurePickleDeserialize *analysis.Analyzer = &analysis.Analyzer{
13+
Name: "django-insecure-pickle-deserialize",
14+
Language: analysis.LangPy,
15+
Description: "Insecure deserialization with pickle, _pickle, cpickle, dill, shelve, or yaml can lead to remote code execution. These libraries execute arbitrary code when loading untrusted data",
16+
Category: analysis.CategorySecurity,
17+
Severity: analysis.SeverityWarning,
18+
Run: checkDjangoInsecurePickleDeserialize,
19+
}
20+
21+
func checkDjangoInsecurePickleDeserialize(pass *analysis.Pass) (interface{}, error) {
22+
reqvarmap := make(map[string]bool)
23+
24+
analysis.Preorder(pass, func(node *sitter.Node) {
25+
if node.Type() != "assignment" {
26+
return
27+
}
28+
29+
left := node.ChildByFieldName("left")
30+
right := node.ChildByFieldName("right")
31+
32+
if right == nil {
33+
return
34+
}
35+
36+
if isRequestSource(right, pass.FileContext.Source, reqvarmap) {
37+
reqvarmap[left.Content(pass.FileContext.Source)] = true
38+
}
39+
})
40+
41+
// detect pickle and dill calls
42+
analysis.Preorder(pass, func(node *sitter.Node) {
43+
if node.Type() != "call" {
44+
return
45+
}
46+
47+
libraryNames := []string{"pickle", "_pickle", "cPickle", "shelve", "dill"}
48+
methodNames := []string{"dump", "dumps", "load", "loads", "dump_session", "load_session"}
49+
50+
funcNode := node.ChildByFieldName("function")
51+
if funcNode.Type() != "attribute" {
52+
return
53+
}
54+
55+
funcObj := funcNode.ChildByFieldName("object").Content(pass.FileContext.Source)
56+
funcAttr := funcNode.ChildByFieldName("attribute").Content(pass.FileContext.Source)
57+
58+
if !slices.Contains(libraryNames, funcObj) || !slices.Contains(methodNames, funcAttr) {
59+
return
60+
}
61+
62+
argListNode := node.ChildByFieldName("arguments")
63+
if argListNode.Type() != "argument_list" {
64+
return
65+
}
66+
67+
argNodes := getNamedChildren(argListNode, 0)
68+
69+
for _, arg := range argNodes {
70+
if arg.Type() == "identifier" {
71+
if reqvarmap[arg.Content(pass.FileContext.Source)] {
72+
pass.Report(pass, node, "Detected insecure deserialization which may lead to remote code execution—use safer alternatives or validate input")
73+
}
74+
} else if arg.Type() == "call" {
75+
if isRequestSource(arg, pass.FileContext.Source, reqvarmap) {
76+
pass.Report(pass, node, "Detected insecure deserialization which may lead to remote code execution—use safer alternatives or validate input")
77+
}
78+
}
79+
}
80+
})
81+
82+
analysis.Preorder(pass, func(node *sitter.Node) {
83+
if node.Type() != "call" {
84+
return
85+
}
86+
87+
yamlMethodNames := []string{"dump", "dump_all", "load", "load_all"}
88+
funcNode := node.ChildByFieldName("function")
89+
if funcNode.Type() != "attribute" {
90+
return
91+
}
92+
93+
funcObj := funcNode.ChildByFieldName("object").Content(pass.FileContext.Source)
94+
funcAttr := funcNode.ChildByFieldName("attribute").Content(pass.FileContext.Source)
95+
96+
if funcObj != "yaml" || !slices.Contains(yamlMethodNames, funcAttr) {
97+
return
98+
}
99+
100+
argListNode := node.ChildByFieldName("arguments")
101+
if argListNode.Type() != "argument_list" {
102+
return
103+
}
104+
argNodes := getNamedChildren(argListNode, 0)
105+
containsSafeParam := false
106+
for _, arg := range argNodes {
107+
if arg.Type() == "keyword_argument" {
108+
key := arg.ChildByFieldName("name").Content(pass.FileContext.Source)
109+
val := arg.ChildByFieldName("value").Content(pass.FileContext.Source)
110+
111+
if (key == "Dumper" || key == "Loader") && (strings.Contains(val, "SafeDumper") || strings.Contains(val, "SafeLoader")) {
112+
containsSafeParam = true
113+
}
114+
}
115+
}
116+
117+
if containsSafeParam {
118+
return
119+
}
120+
121+
for _, arg := range argNodes {
122+
if arg.Type() == "identifier" {
123+
if reqvarmap[arg.Content(pass.FileContext.Source)] {
124+
pass.Report(pass, node, "Detected insecure deserialization which may lead to remote code execution—use safer alternatives or validate input")
125+
}
126+
} else if arg.Type() == "call" {
127+
if isRequestSource(arg, pass.FileContext.Source, reqvarmap) {
128+
pass.Report(pass, node, "Detected insecure deserialization which may lead to remote code execution—use safer alternatives or validate input")
129+
}
130+
}
131+
}
132+
133+
})
134+
135+
return nil, nil
136+
}
137+
138+
func isRequestSource(node *sitter.Node, source []byte, reqVarMap map[string]bool) bool {
139+
switch node.Type() {
140+
case "call":
141+
if isEncoded(node, source, reqVarMap) {
142+
return true
143+
}
144+
funcNode := node.ChildByFieldName("function")
145+
if funcNode.Type() != "attribute" {
146+
return false
147+
}
148+
objectNode := funcNode.ChildByFieldName("object")
149+
if !strings.Contains(objectNode.Content(source), "request") {
150+
return false
151+
}
152+
153+
attributeNode := funcNode.ChildByFieldName("attribute")
154+
if attributeNode.Type() != "identifier" {
155+
return false
156+
}
157+
158+
return true
159+
160+
case "subscript":
161+
valueNode := node.ChildByFieldName("value")
162+
if valueNode.Type() != "attribute" {
163+
return false
164+
}
165+
166+
objNode := valueNode.ChildByFieldName("object")
167+
if objNode.Type() != "identifier" && objNode.Content(source) != "request" {
168+
return false
169+
}
170+
171+
return true
172+
173+
case "identifier":
174+
return reqVarMap[node.Content(source)]
175+
}
176+
177+
return false
178+
}
179+
180+
func isEncoded(node *sitter.Node, source []byte, reqVarMap map[string]bool) bool {
181+
if node.Type() != "call" {
182+
return false
183+
}
184+
185+
if strings.HasPrefix(node.Content(source), "request") {
186+
return false
187+
}
188+
189+
argListNode := node.ChildByFieldName("arguments")
190+
if argListNode.Type() != "argument_list" {
191+
return false
192+
}
193+
194+
argNodes := getNamedChildren(argListNode, 0)
195+
for _, arg := range argNodes {
196+
if isRequestSource(arg, source, reqVarMap) {
197+
return true
198+
}
199+
}
200+
201+
return false
202+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from django.http import HttpResponse
2+
import datetime
3+
4+
def current_datetime(request):
5+
user_obj = request.cookies.get('uuid')
6+
now = datetime.datetime.now()
7+
html = "<html><body>It is now %s.</body></html>" % now
8+
9+
# <no-error>
10+
return "Hey there! {}!".format(pickle.loads(b64decode(html)))
11+
12+
# pickle tests
13+
14+
def current_datetime(request):
15+
user_obj = b64decode(request.cookies.get('uuid'))
16+
now = datetime.datetime.now()
17+
html = "<html><body>It is now %s.</body></html>" % now
18+
19+
# <expect-error>
20+
return "Hey there! {}!".format(pickle.loads(user_obj))
21+
22+
def current_datetime(request):
23+
user_obj = request.cookies.get('uuid')
24+
now = datetime.datetime.now()
25+
html = "<html><body>It is now %s.</body></html>" % now
26+
27+
# <expect-error>
28+
return "Hey there! {}!".format(pickle.loads(user_obj))
29+
30+
def current_datetime(request):
31+
user_objaaa = request.cookies.get('uuid')
32+
now = datetime.datetime.now()
33+
html = "<html><body>It is now %s.</body></html>" % now
34+
35+
# <expect-error>
36+
return "Hey there! {}!".format(pickle.loads(b64decode(user_objaaa)))
37+
38+
def current_datetime(request):
39+
# <expect-error>
40+
return "Hey there! {}!".format(pickle.loads(b64decode(request.cookies.get('uuid'))))
41+
42+
# Other libraries
43+
44+
def current_datetime(request):
45+
user_obj = b64decode(request.cookies.get('uuid'))
46+
now = datetime.datetime.now()
47+
html = "<html><body>It is now %s.</body></html>" % now
48+
49+
# <expect-error>
50+
return "Hey there! {}!".format(_pickle.loads(user_obj))
51+
52+
def current_datetime(request):
53+
user_obj = request.cookies.get('uuid')
54+
now = datetime.datetime.now()
55+
html = "<html><body>It is now %s.</body></html>" % now
56+
57+
# <expect-error>
58+
return "Hey there! {}!".format(cPickle.loads(user_obj))
59+
60+
def current_datetime(request):
61+
user_obj = request.cookies.get('uuid')
62+
now = datetime.datetime.now()
63+
html = "<html><body>It is now %s.</body></html>" % now
64+
65+
# <expect-error>
66+
return "Hey there! {}!".format(dill.loads(b64decode(user_obj)))
67+
68+
def current_datetime(request):
69+
user_obj = request.cookies.get('uuid')
70+
now = datetime.datetime.now()
71+
html = "<html><body>It is now %s.</body></html>" % now
72+
73+
# <expect-error>
74+
return "Hey there! {}!".format(shelve.loads(user_obj))
75+
76+
def current_datetime(request):
77+
user_obj = request.cookies.get('uuid')
78+
now = datetime.datetime.now()
79+
html = "<html><body>It is now %s.</body></html>" % now
80+
81+
# <expect-error>
82+
return "Hey there! {}!".format(yaml.load(b64decode(user_obj)))
83+
84+
def current_datetime(request):
85+
user_obj = request.cookies.get('uuid')
86+
now = datetime.datetime.now()
87+
html = "<html><body>It is now %s.</body></html>" % now
88+
89+
# <no-error>
90+
return "Hey there! {}!".format(yaml.load(b64decode(user_obj), Loader=SafeLoader))

0 commit comments

Comments
 (0)