-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathIncompleteUrlSubstringSanitization.ql
55 lines (48 loc) · 1.73 KB
/
IncompleteUrlSubstringSanitization.ql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @name Incomplete URL substring sanitization
* @description Security checks on the substrings of an unparsed URL are often vulnerable to bypassing.
* @kind problem
* @problem.severity warning
* @security-severity 7.8
* @precision high
* @id py/incomplete-url-substring-sanitization
* @tags correctness
* security
* external/cwe/cwe-20
*/
import python
import semmle.python.regex
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
predicate looksLikeUrl(StringLiteral s) {
exists(string text | text = s.getText() |
text.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + commonTopLevelDomainRegex() +
")(:[0-9]+)?/?")
or
// target is a HTTP URL to a domain on any TLD
text.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
)
}
predicate incomplete_sanitization(Expr sanitizer, StringLiteral url) {
looksLikeUrl(url) and
(
sanitizer.(Compare).compares(url, any(In i), _)
or
unsafe_call_to_startswith(sanitizer, url)
or
unsafe_call_to_endswith(sanitizer, url)
)
}
predicate unsafe_call_to_startswith(Call sanitizer, StringLiteral url) {
sanitizer.getFunc().(Attribute).getName() = "startswith" and
sanitizer.getArg(0) = url and
not url.getText().regexpMatch("(?i)https?://[\\.a-z0-9-]+/.*")
}
predicate unsafe_call_to_endswith(Call sanitizer, StringLiteral url) {
sanitizer.getFunc().(Attribute).getName() = "endswith" and
sanitizer.getArg(0) = url and
not url.getText().regexpMatch("(?i)\\.([a-z0-9-]+)(\\.[a-z0-9-]+)+")
}
from Expr sanitizer, StringLiteral url
where incomplete_sanitization(sanitizer, url)
select sanitizer, "The string $@ may be at an arbitrary position in the sanitized URL.", url,
url.getText()