trapline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +585 -0
- data/config.ru.example +21 -0
- data/exe/trapline +8 -0
- data/lib/generators/trapline/install_generator.rb +34 -0
- data/lib/generators/trapline/templates/initializer.rb +23 -0
- data/lib/tasks/trapline.rake +19 -0
- data/lib/trapline/cli.rb +212 -0
- data/lib/trapline/configuration.rb +19 -0
- data/lib/trapline/cve/client.rb +203 -0
- data/lib/trapline/cve/record.rb +160 -0
- data/lib/trapline/engine.rb +243 -0
- data/lib/trapline/generator.rb +236 -0
- data/lib/trapline/languages.rb +65 -0
- data/lib/trapline/railtie.rb +26 -0
- data/lib/trapline/rule.rb +165 -0
- data/lib/trapline/runtime.rb +148 -0
- data/lib/trapline/store.rb +113 -0
- data/lib/trapline/suite.rb +44 -0
- data/lib/trapline/templates/cwe/cwe-22.yml +167 -0
- data/lib/trapline/templates/cwe/cwe-327.yml +124 -0
- data/lib/trapline/templates/cwe/cwe-502.yml +123 -0
- data/lib/trapline/templates/cwe/cwe-601.yml +124 -0
- data/lib/trapline/templates/cwe/cwe-611.yml +87 -0
- data/lib/trapline/templates/cwe/cwe-78.yml +166 -0
- data/lib/trapline/templates/cwe/cwe-79.yml +164 -0
- data/lib/trapline/templates/cwe/cwe-798.yml +125 -0
- data/lib/trapline/templates/cwe/cwe-89.yml +189 -0
- data/lib/trapline/templates/cwe/cwe-917.yml +46 -0
- data/lib/trapline/templates/cwe/cwe-918.yml +166 -0
- data/lib/trapline/templates/cwe/cwe-94.yml +131 -0
- data/lib/trapline/templates.rb +261 -0
- data/lib/trapline/validator.rb +359 -0
- data/lib/trapline/version.rb +5 -0
- data/lib/trapline/web/app.rb +234 -0
- data/lib/trapline/web/assets/THIRD_PARTY_LICENSES.txt +214 -0
- data/lib/trapline/web/assets/app.css +399 -0
- data/lib/trapline/web/assets/app.js +804 -0
- data/lib/trapline/web/assets/codemirror.css +344 -0
- data/lib/trapline/web/assets/codemirror.js +13263 -0
- data/lib/trapline/web/assets/index.html +165 -0
- data/lib/trapline/web/assets/jetbrains-mono-latin-400-normal.woff2 +0 -0
- data/lib/trapline/web/assets/jetbrains-mono-latin-600-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-400-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-500-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-600-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-700-normal.woff2 +0 -0
- data/lib/trapline/web/server.rb +108 -0
- data/lib/trapline.rb +70 -0
- metadata +136 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
cwe: CWE-327
|
|
2
|
+
name: "Use of a Broken or Risky Cryptographic Algorithm"
|
|
3
|
+
title: Weak cryptography
|
|
4
|
+
owasp: ["A02:2021 - Cryptographic Failures"]
|
|
5
|
+
keywords: ["weak cryptograph", "broken cryptograph", "md5", "sha1", "sha-1", "des", "rc4", "ecb mode", "weak hash", "weak cipher"]
|
|
6
|
+
message: >-
|
|
7
|
+
A broken or weak cryptographic algorithm is used. Use SHA-256 or better for
|
|
8
|
+
hashing (bcrypt/argon2 for passwords) and AES-GCM for encryption.
|
|
9
|
+
languages:
|
|
10
|
+
ruby:
|
|
11
|
+
rule: |
|
|
12
|
+
pattern-either:
|
|
13
|
+
- pattern: Digest::MD5.$FN(...)
|
|
14
|
+
- pattern: Digest::SHA1.$FN(...)
|
|
15
|
+
- pattern: OpenSSL::Digest::MD5.new(...)
|
|
16
|
+
- pattern: OpenSSL::Digest::SHA1.new(...)
|
|
17
|
+
- patterns:
|
|
18
|
+
- pattern-either:
|
|
19
|
+
- pattern: OpenSSL::Digest.new($ALG, ...)
|
|
20
|
+
- pattern: OpenSSL::Cipher.new($ALG, ...)
|
|
21
|
+
- metavariable-regex:
|
|
22
|
+
metavariable: $ALG
|
|
23
|
+
regex: (?i).*(md5|sha1|\bdes\b|des-|rc4|-ecb)
|
|
24
|
+
test: |
|
|
25
|
+
# ruleid: __RULE_ID__
|
|
26
|
+
token = Digest::MD5.hexdigest(secret)
|
|
27
|
+
# ruleid: __RULE_ID__
|
|
28
|
+
cipher = OpenSSL::Cipher.new("des-ede3-cbc")
|
|
29
|
+
# ok: __RULE_ID__
|
|
30
|
+
token = Digest::SHA256.hexdigest(secret)
|
|
31
|
+
# ok: __RULE_ID__
|
|
32
|
+
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
|
33
|
+
python:
|
|
34
|
+
rule: |
|
|
35
|
+
pattern-either:
|
|
36
|
+
- pattern: hashlib.md5(...)
|
|
37
|
+
- pattern: hashlib.sha1(...)
|
|
38
|
+
- pattern: Crypto.Cipher.DES.new(...)
|
|
39
|
+
- pattern: Crypto.Cipher.ARC4.new(...)
|
|
40
|
+
- pattern: Cryptodome.Cipher.DES.new(...)
|
|
41
|
+
- patterns:
|
|
42
|
+
- pattern: hashlib.new($ALG, ...)
|
|
43
|
+
- metavariable-regex:
|
|
44
|
+
metavariable: $ALG
|
|
45
|
+
regex: (?i).*(md5|sha1|md4)
|
|
46
|
+
test: |
|
|
47
|
+
import hashlib
|
|
48
|
+
|
|
49
|
+
# ruleid: __RULE_ID__
|
|
50
|
+
digest = hashlib.md5(data).hexdigest()
|
|
51
|
+
# ruleid: __RULE_ID__
|
|
52
|
+
digest = hashlib.new("sha1", data).hexdigest()
|
|
53
|
+
# ok: __RULE_ID__
|
|
54
|
+
digest = hashlib.sha256(data).hexdigest()
|
|
55
|
+
javascript:
|
|
56
|
+
rule: |
|
|
57
|
+
patterns:
|
|
58
|
+
- pattern-either:
|
|
59
|
+
- pattern: crypto.createHash($ALG)
|
|
60
|
+
- pattern: crypto.createCipheriv($ALG, ...)
|
|
61
|
+
- pattern: crypto.createCipher($ALG, ...)
|
|
62
|
+
- pattern: require("crypto").createHash($ALG)
|
|
63
|
+
- metavariable-regex:
|
|
64
|
+
metavariable: $ALG
|
|
65
|
+
regex: (?i).*(md5|md4|sha1|\bdes\b|des-|rc4|-ecb)
|
|
66
|
+
test: |
|
|
67
|
+
const crypto = require("crypto");
|
|
68
|
+
|
|
69
|
+
// ruleid: __RULE_ID__
|
|
70
|
+
const h = crypto.createHash("md5").update(data).digest("hex");
|
|
71
|
+
// ok: __RULE_ID__
|
|
72
|
+
const s = crypto.createHash("sha256").update(data).digest("hex");
|
|
73
|
+
java:
|
|
74
|
+
rule: |
|
|
75
|
+
patterns:
|
|
76
|
+
- pattern-either:
|
|
77
|
+
- pattern: MessageDigest.getInstance($ALG, ...)
|
|
78
|
+
- pattern: Cipher.getInstance($ALG, ...)
|
|
79
|
+
- metavariable-regex:
|
|
80
|
+
metavariable: $ALG
|
|
81
|
+
regex: (?i).*("md5"|"md4"|"sha-?1"|"des|"rc4|/ecb/|"aes")
|
|
82
|
+
test: |
|
|
83
|
+
import java.security.MessageDigest;
|
|
84
|
+
import javax.crypto.Cipher;
|
|
85
|
+
|
|
86
|
+
public class Hashing {
|
|
87
|
+
public void run() throws Exception {
|
|
88
|
+
// ruleid: __RULE_ID__
|
|
89
|
+
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
90
|
+
// ruleid: __RULE_ID__
|
|
91
|
+
Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding");
|
|
92
|
+
// ruleid: __RULE_ID__
|
|
93
|
+
Cipher e = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
94
|
+
// ok: __RULE_ID__
|
|
95
|
+
MessageDigest ok = MessageDigest.getInstance("SHA-256");
|
|
96
|
+
// ok: __RULE_ID__
|
|
97
|
+
Cipher g = Cipher.getInstance("AES/GCM/NoPadding");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
go:
|
|
101
|
+
rule: |
|
|
102
|
+
pattern-either:
|
|
103
|
+
- pattern: md5.New()
|
|
104
|
+
- pattern: md5.Sum(...)
|
|
105
|
+
- pattern: sha1.New()
|
|
106
|
+
- pattern: sha1.Sum(...)
|
|
107
|
+
- pattern: des.NewCipher(...)
|
|
108
|
+
- pattern: des.NewTripleDESCipher(...)
|
|
109
|
+
- pattern: rc4.NewCipher(...)
|
|
110
|
+
test: |
|
|
111
|
+
package main
|
|
112
|
+
|
|
113
|
+
import (
|
|
114
|
+
"crypto/md5"
|
|
115
|
+
"crypto/sha256"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
func digest(data []byte) {
|
|
119
|
+
// ruleid: __RULE_ID__
|
|
120
|
+
a := md5.Sum(data)
|
|
121
|
+
// ok: __RULE_ID__
|
|
122
|
+
b := sha256.Sum256(data)
|
|
123
|
+
_, _ = a, b
|
|
124
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
cwe: CWE-502
|
|
2
|
+
name: "Deserialization of Untrusted Data"
|
|
3
|
+
title: Insecure deserialization
|
|
4
|
+
owasp: ["A08:2021 - Software and Data Integrity Failures"]
|
|
5
|
+
keywords: ["deserialization", "deserialize", "unserialize", "untrusted data", "gadget chain", "object injection", "pickle", "marshal"]
|
|
6
|
+
message: >-
|
|
7
|
+
Untrusted data is deserialized with a format that can instantiate arbitrary
|
|
8
|
+
objects, which can lead to remote code execution. Use a data-only format
|
|
9
|
+
(JSON) or a safe loader.
|
|
10
|
+
languages:
|
|
11
|
+
ruby:
|
|
12
|
+
rule: |
|
|
13
|
+
mode: taint
|
|
14
|
+
pattern-sources:
|
|
15
|
+
- pattern: params
|
|
16
|
+
- pattern: cookies
|
|
17
|
+
- pattern: request.body
|
|
18
|
+
- pattern: request.raw_post
|
|
19
|
+
pattern-sinks:
|
|
20
|
+
- pattern: Marshal.load(...)
|
|
21
|
+
- pattern: Marshal.restore(...)
|
|
22
|
+
- pattern: YAML.unsafe_load(...)
|
|
23
|
+
- pattern: Psych.unsafe_load(...)
|
|
24
|
+
- pattern: Oj.load(...)
|
|
25
|
+
test: |
|
|
26
|
+
class SessionsController < ApplicationController
|
|
27
|
+
def restore
|
|
28
|
+
# ruleid: __RULE_ID__
|
|
29
|
+
state = Marshal.load(Base64.decode64(cookies[:state]))
|
|
30
|
+
# ruleid: __RULE_ID__
|
|
31
|
+
config = YAML.unsafe_load(request.body.read)
|
|
32
|
+
# ok: __RULE_ID__
|
|
33
|
+
state = JSON.parse(Base64.decode64(cookies[:state]))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
python:
|
|
37
|
+
rule: |
|
|
38
|
+
mode: taint
|
|
39
|
+
pattern-sources:
|
|
40
|
+
- pattern: flask.request.$ANY
|
|
41
|
+
- patterns:
|
|
42
|
+
- pattern-inside: |
|
|
43
|
+
def $VIEW(request, ...):
|
|
44
|
+
...
|
|
45
|
+
- pattern: request.$ANY
|
|
46
|
+
pattern-sinks:
|
|
47
|
+
- pattern: pickle.loads(...)
|
|
48
|
+
- pattern: pickle.load(...)
|
|
49
|
+
- pattern: cPickle.loads(...)
|
|
50
|
+
- pattern: dill.loads(...)
|
|
51
|
+
- pattern: marshal.loads(...)
|
|
52
|
+
- pattern: jsonpickle.decode(...)
|
|
53
|
+
- pattern: yaml.unsafe_load(...)
|
|
54
|
+
- pattern: yaml.load(..., Loader=yaml.Loader, ...)
|
|
55
|
+
- pattern: yaml.load(..., Loader=yaml.UnsafeLoader, ...)
|
|
56
|
+
- patterns:
|
|
57
|
+
- pattern: yaml.load($DATA)
|
|
58
|
+
- focus-metavariable: $DATA
|
|
59
|
+
test: |
|
|
60
|
+
import base64
|
|
61
|
+
import json
|
|
62
|
+
import pickle
|
|
63
|
+
import yaml
|
|
64
|
+
from flask import request
|
|
65
|
+
|
|
66
|
+
def restore():
|
|
67
|
+
raw = base64.b64decode(request.cookies.get("state"))
|
|
68
|
+
# ruleid: __RULE_ID__
|
|
69
|
+
state = pickle.loads(raw)
|
|
70
|
+
# ruleid: __RULE_ID__
|
|
71
|
+
config = yaml.load(request.data, Loader=yaml.Loader)
|
|
72
|
+
# ok: __RULE_ID__
|
|
73
|
+
config = yaml.safe_load(request.data)
|
|
74
|
+
# ok: __RULE_ID__
|
|
75
|
+
state = json.loads(raw)
|
|
76
|
+
javascript:
|
|
77
|
+
rule: |
|
|
78
|
+
mode: taint
|
|
79
|
+
pattern-sources:
|
|
80
|
+
- pattern: $REQ.query
|
|
81
|
+
- pattern: $REQ.body
|
|
82
|
+
- pattern: $REQ.params
|
|
83
|
+
- pattern: $REQ.cookies
|
|
84
|
+
pattern-sinks:
|
|
85
|
+
- pattern: $SER.unserialize(...)
|
|
86
|
+
- pattern: require("node-serialize").unserialize(...)
|
|
87
|
+
- pattern: $FUNSTER.deepDeserialize(...)
|
|
88
|
+
test: |
|
|
89
|
+
const serialize = require("node-serialize");
|
|
90
|
+
|
|
91
|
+
app.post("/restore", (req, res) => {
|
|
92
|
+
// ruleid: __RULE_ID__
|
|
93
|
+
const state = serialize.unserialize(Buffer.from(req.cookies.state, "base64").toString());
|
|
94
|
+
// ok: __RULE_ID__
|
|
95
|
+
const safe = JSON.parse(Buffer.from(req.cookies.state, "base64").toString());
|
|
96
|
+
res.json(safe);
|
|
97
|
+
});
|
|
98
|
+
java:
|
|
99
|
+
rule: |
|
|
100
|
+
mode: taint
|
|
101
|
+
pattern-sources:
|
|
102
|
+
- pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
103
|
+
- pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
104
|
+
pattern-sinks:
|
|
105
|
+
- pattern: new ObjectInputStream(...)
|
|
106
|
+
- pattern: new java.beans.XMLDecoder(...)
|
|
107
|
+
- pattern: new XMLDecoder(...)
|
|
108
|
+
- pattern: (com.thoughtworks.xstream.XStream $X).fromXML(...)
|
|
109
|
+
- pattern: (XStream $X).fromXML(...)
|
|
110
|
+
test: |
|
|
111
|
+
import javax.servlet.http.HttpServletRequest;
|
|
112
|
+
import java.io.ObjectInputStream;
|
|
113
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
114
|
+
|
|
115
|
+
public class RestoreServlet {
|
|
116
|
+
public void doPost(HttpServletRequest request) throws Exception {
|
|
117
|
+
// ruleid: __RULE_ID__
|
|
118
|
+
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
|
|
119
|
+
Object state = in.readObject();
|
|
120
|
+
// ok: __RULE_ID__
|
|
121
|
+
Object safe = new ObjectMapper().readTree(request.getInputStream());
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
cwe: CWE-601
|
|
2
|
+
name: "URL Redirection to Untrusted Site ('Open Redirect')"
|
|
3
|
+
title: Open redirect
|
|
4
|
+
owasp: ["A01:2021 - Broken Access Control"]
|
|
5
|
+
keywords: ["open redirect", "url redirection", "redirect to arbitrary", "untrusted site"]
|
|
6
|
+
message: >-
|
|
7
|
+
The application redirects to a URL taken from the request (open redirect).
|
|
8
|
+
Redirect only to relative paths or to an allowlist of hosts.
|
|
9
|
+
languages:
|
|
10
|
+
ruby:
|
|
11
|
+
rule: |
|
|
12
|
+
mode: taint
|
|
13
|
+
pattern-sources:
|
|
14
|
+
- pattern: params
|
|
15
|
+
- pattern: cookies
|
|
16
|
+
- pattern: request.referer
|
|
17
|
+
pattern-sinks:
|
|
18
|
+
- patterns:
|
|
19
|
+
- pattern: redirect_to($U, ...)
|
|
20
|
+
- pattern-not: "redirect_to($U, ..., allow_other_host: false, ...)"
|
|
21
|
+
- focus-metavariable: $U
|
|
22
|
+
test: |
|
|
23
|
+
class SessionsController < ApplicationController
|
|
24
|
+
def create
|
|
25
|
+
# ruleid: __RULE_ID__
|
|
26
|
+
redirect_to params[:return_to]
|
|
27
|
+
# ok: __RULE_ID__
|
|
28
|
+
redirect_to params[:return_to], allow_other_host: false
|
|
29
|
+
# ok: __RULE_ID__
|
|
30
|
+
redirect_to root_path
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
python:
|
|
34
|
+
rule: |
|
|
35
|
+
mode: taint
|
|
36
|
+
pattern-sources:
|
|
37
|
+
- pattern: flask.request.$ANY
|
|
38
|
+
- patterns:
|
|
39
|
+
- pattern-inside: |
|
|
40
|
+
def $VIEW(request, ...):
|
|
41
|
+
...
|
|
42
|
+
- pattern: request.$ANY
|
|
43
|
+
pattern-sanitizers:
|
|
44
|
+
- pattern: url_has_allowed_host_and_scheme(...)
|
|
45
|
+
pattern-sinks:
|
|
46
|
+
- pattern: flask.redirect(...)
|
|
47
|
+
- pattern: django.shortcuts.redirect(...)
|
|
48
|
+
- pattern: django.http.HttpResponseRedirect(...)
|
|
49
|
+
test: |
|
|
50
|
+
from flask import request, redirect, url_for
|
|
51
|
+
|
|
52
|
+
def login():
|
|
53
|
+
# ruleid: __RULE_ID__
|
|
54
|
+
return redirect(request.args.get("next"))
|
|
55
|
+
|
|
56
|
+
def logout():
|
|
57
|
+
# ok: __RULE_ID__
|
|
58
|
+
return redirect(url_for("index"))
|
|
59
|
+
javascript:
|
|
60
|
+
rule: |
|
|
61
|
+
mode: taint
|
|
62
|
+
pattern-sources:
|
|
63
|
+
- pattern: $REQ.query
|
|
64
|
+
- pattern: $REQ.body
|
|
65
|
+
- pattern: $REQ.params
|
|
66
|
+
pattern-sinks:
|
|
67
|
+
- patterns:
|
|
68
|
+
- pattern-either:
|
|
69
|
+
- pattern: $RES.redirect($U)
|
|
70
|
+
- pattern: $RES.redirect($STATUS, $U)
|
|
71
|
+
- focus-metavariable: $U
|
|
72
|
+
test: |
|
|
73
|
+
app.get("/login", (req, res) => {
|
|
74
|
+
// ruleid: __RULE_ID__
|
|
75
|
+
res.redirect(req.query.next);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
app.get("/logout", (req, res) => {
|
|
79
|
+
// ok: __RULE_ID__
|
|
80
|
+
res.redirect("/");
|
|
81
|
+
});
|
|
82
|
+
java:
|
|
83
|
+
rule: |
|
|
84
|
+
mode: taint
|
|
85
|
+
pattern-sources:
|
|
86
|
+
- pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
87
|
+
- pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
88
|
+
pattern-sinks:
|
|
89
|
+
- pattern: (javax.servlet.http.HttpServletResponse $RESP).sendRedirect(...)
|
|
90
|
+
- pattern: (jakarta.servlet.http.HttpServletResponse $RESP).sendRedirect(...)
|
|
91
|
+
test: |
|
|
92
|
+
import javax.servlet.http.HttpServletRequest;
|
|
93
|
+
import javax.servlet.http.HttpServletResponse;
|
|
94
|
+
|
|
95
|
+
public class LoginServlet {
|
|
96
|
+
public void doGet(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
|
97
|
+
// ruleid: __RULE_ID__
|
|
98
|
+
response.sendRedirect(request.getParameter("next"));
|
|
99
|
+
// ok: __RULE_ID__
|
|
100
|
+
response.sendRedirect("/home");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
go:
|
|
104
|
+
rule: |
|
|
105
|
+
mode: taint
|
|
106
|
+
pattern-sources:
|
|
107
|
+
- pattern: '($R : *http.Request).URL.Query().Get(...)'
|
|
108
|
+
- pattern: '($R : *http.Request).FormValue(...)'
|
|
109
|
+
pattern-sinks:
|
|
110
|
+
- patterns:
|
|
111
|
+
- pattern: http.Redirect($W, $REQ, $U, ...)
|
|
112
|
+
- focus-metavariable: $U
|
|
113
|
+
test: |
|
|
114
|
+
package main
|
|
115
|
+
|
|
116
|
+
import "net/http"
|
|
117
|
+
|
|
118
|
+
func login(w http.ResponseWriter, r *http.Request) {
|
|
119
|
+
next := r.URL.Query().Get("next")
|
|
120
|
+
// ruleid: __RULE_ID__
|
|
121
|
+
http.Redirect(w, r, next, http.StatusFound)
|
|
122
|
+
// ok: __RULE_ID__
|
|
123
|
+
http.Redirect(w, r, "/home", http.StatusFound)
|
|
124
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
cwe: CWE-611
|
|
2
|
+
name: "Improper Restriction of XML External Entity Reference"
|
|
3
|
+
title: XML external entities (XXE)
|
|
4
|
+
owasp: ["A05:2021 - Security Misconfiguration"]
|
|
5
|
+
keywords: ["xml external entity", "xxe", "external entities", "dtd"]
|
|
6
|
+
message: >-
|
|
7
|
+
The XML parser is created without disabling DTDs/external entities, which
|
|
8
|
+
allows XXE attacks. Disable doctype declarations or external entity
|
|
9
|
+
resolution before parsing.
|
|
10
|
+
languages:
|
|
11
|
+
java:
|
|
12
|
+
rule: |
|
|
13
|
+
patterns:
|
|
14
|
+
- pattern-either:
|
|
15
|
+
- pattern-inside: |
|
|
16
|
+
$F = DocumentBuilderFactory.newInstance();
|
|
17
|
+
...
|
|
18
|
+
- pattern-inside: |
|
|
19
|
+
$F = SAXParserFactory.newInstance();
|
|
20
|
+
...
|
|
21
|
+
- pattern-inside: |
|
|
22
|
+
$F = XMLInputFactory.newInstance();
|
|
23
|
+
...
|
|
24
|
+
- pattern-not-inside: |
|
|
25
|
+
$F = $FACTORY.newInstance();
|
|
26
|
+
...
|
|
27
|
+
$F.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
28
|
+
...
|
|
29
|
+
- pattern-not-inside: |
|
|
30
|
+
$F = $FACTORY.newInstance();
|
|
31
|
+
...
|
|
32
|
+
$F.setProperty(XMLInputFactory.SUPPORT_DTD, false);
|
|
33
|
+
...
|
|
34
|
+
- pattern-either:
|
|
35
|
+
- pattern: $F.newDocumentBuilder()
|
|
36
|
+
- pattern: $F.newSAXParser()
|
|
37
|
+
- pattern: $F.createXMLStreamReader(...)
|
|
38
|
+
test: |
|
|
39
|
+
import javax.xml.parsers.DocumentBuilder;
|
|
40
|
+
import javax.xml.parsers.DocumentBuilderFactory;
|
|
41
|
+
|
|
42
|
+
public class XmlParser {
|
|
43
|
+
public void unsafe() throws Exception {
|
|
44
|
+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
45
|
+
// ruleid: __RULE_ID__
|
|
46
|
+
DocumentBuilder db = dbf.newDocumentBuilder();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public void safe() throws Exception {
|
|
50
|
+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
51
|
+
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
52
|
+
// ok: __RULE_ID__
|
|
53
|
+
DocumentBuilder db = dbf.newDocumentBuilder();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
python:
|
|
57
|
+
rule: |
|
|
58
|
+
pattern-either:
|
|
59
|
+
- pattern: lxml.etree.XMLParser(..., resolve_entities=True, ...)
|
|
60
|
+
- pattern: xml.sax.make_parser(...)
|
|
61
|
+
- pattern: xml.dom.pulldom.parse(...)
|
|
62
|
+
test: |
|
|
63
|
+
from lxml import etree
|
|
64
|
+
import defusedxml.ElementTree as SafeET
|
|
65
|
+
|
|
66
|
+
# ruleid: __RULE_ID__
|
|
67
|
+
parser = etree.XMLParser(resolve_entities=True)
|
|
68
|
+
# ok: __RULE_ID__
|
|
69
|
+
parser = etree.XMLParser(resolve_entities=False, no_network=True)
|
|
70
|
+
# ok: __RULE_ID__
|
|
71
|
+
tree = SafeET.fromstring("<a/>")
|
|
72
|
+
ruby:
|
|
73
|
+
rule: |
|
|
74
|
+
pattern-either:
|
|
75
|
+
- pattern: Nokogiri::XML(..., Nokogiri::XML::ParseOptions::NOENT, ...)
|
|
76
|
+
- pattern: Nokogiri::XML(..., Nokogiri::XML::ParseOptions::DTDLOAD, ...)
|
|
77
|
+
- pattern: $CFG.noent
|
|
78
|
+
- pattern: $CFG.dtdload
|
|
79
|
+
test: |
|
|
80
|
+
class ImportController < ApplicationController
|
|
81
|
+
def create
|
|
82
|
+
# ruleid: __RULE_ID__
|
|
83
|
+
doc = Nokogiri::XML(request.body.read) { |config| config.noent }
|
|
84
|
+
# ok: __RULE_ID__
|
|
85
|
+
doc = Nokogiri::XML(request.body.read) { |config| config.nonet }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
cwe: CWE-78
|
|
2
|
+
name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
|
|
3
|
+
title: OS command injection
|
|
4
|
+
owasp: ["A03:2021 - Injection"]
|
|
5
|
+
keywords: ["command injection", "os command", "shell command", "shell metacharacters", "arbitrary command"]
|
|
6
|
+
message: >-
|
|
7
|
+
Untrusted input reaches a shell command. Pass arguments as a list (no shell)
|
|
8
|
+
and validate them against an allowlist.
|
|
9
|
+
languages:
|
|
10
|
+
ruby:
|
|
11
|
+
rule: |
|
|
12
|
+
mode: taint
|
|
13
|
+
pattern-sources:
|
|
14
|
+
- pattern: params
|
|
15
|
+
- pattern: cookies
|
|
16
|
+
pattern-sanitizers:
|
|
17
|
+
- pattern: Shellwords.escape(...)
|
|
18
|
+
- pattern: $X.shellescape
|
|
19
|
+
pattern-sinks:
|
|
20
|
+
- patterns:
|
|
21
|
+
- pattern-either:
|
|
22
|
+
- pattern: system($CMD)
|
|
23
|
+
- pattern: exec($CMD)
|
|
24
|
+
- pattern: spawn($CMD)
|
|
25
|
+
- pattern: Kernel.system($CMD)
|
|
26
|
+
- pattern: IO.popen($CMD, ...)
|
|
27
|
+
- pattern: Open3.$FN($CMD)
|
|
28
|
+
- pattern: "`...`"
|
|
29
|
+
- focus-metavariable: $CMD
|
|
30
|
+
- pattern: "`...`"
|
|
31
|
+
test: |
|
|
32
|
+
class ToolsController < ApplicationController
|
|
33
|
+
def ping
|
|
34
|
+
# ruleid: __RULE_ID__
|
|
35
|
+
system("ping -c 1 #{params[:host]}")
|
|
36
|
+
# ruleid: __RULE_ID__
|
|
37
|
+
out = `nslookup #{params[:host]}`
|
|
38
|
+
# ok: __RULE_ID__
|
|
39
|
+
system("ping", "-c", "1", params[:host])
|
|
40
|
+
# ok: __RULE_ID__
|
|
41
|
+
system("ping -c 1 #{Shellwords.escape(params[:host])}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
python:
|
|
45
|
+
rule: |
|
|
46
|
+
mode: taint
|
|
47
|
+
pattern-sources:
|
|
48
|
+
- pattern: flask.request.$ANY
|
|
49
|
+
- patterns:
|
|
50
|
+
- pattern-inside: |
|
|
51
|
+
def $VIEW(request, ...):
|
|
52
|
+
...
|
|
53
|
+
- pattern: request.$ANY
|
|
54
|
+
pattern-sanitizers:
|
|
55
|
+
- pattern: shlex.quote(...)
|
|
56
|
+
pattern-sinks:
|
|
57
|
+
- pattern: os.system(...)
|
|
58
|
+
- pattern: os.popen(...)
|
|
59
|
+
- patterns:
|
|
60
|
+
- pattern-either:
|
|
61
|
+
- pattern: subprocess.$FN($CMD, ..., shell=True, ...)
|
|
62
|
+
- focus-metavariable: $CMD
|
|
63
|
+
test: |
|
|
64
|
+
import os
|
|
65
|
+
import shlex
|
|
66
|
+
import subprocess
|
|
67
|
+
from flask import request
|
|
68
|
+
|
|
69
|
+
def ping():
|
|
70
|
+
host = request.args.get("host")
|
|
71
|
+
# ruleid: __RULE_ID__
|
|
72
|
+
os.system("ping -c 1 " + host)
|
|
73
|
+
# ruleid: __RULE_ID__
|
|
74
|
+
subprocess.run("ping -c 1 " + host, shell=True)
|
|
75
|
+
# ok: __RULE_ID__
|
|
76
|
+
subprocess.run(["ping", "-c", "1", host])
|
|
77
|
+
# ok: __RULE_ID__
|
|
78
|
+
os.system("ping -c 1 " + shlex.quote(host))
|
|
79
|
+
javascript:
|
|
80
|
+
rule: |
|
|
81
|
+
mode: taint
|
|
82
|
+
pattern-sources:
|
|
83
|
+
- pattern: $REQ.query
|
|
84
|
+
- pattern: $REQ.body
|
|
85
|
+
- pattern: $REQ.params
|
|
86
|
+
pattern-sinks:
|
|
87
|
+
- patterns:
|
|
88
|
+
- pattern-either:
|
|
89
|
+
- pattern: $CP.exec($CMD, ...)
|
|
90
|
+
- pattern: $CP.execSync($CMD, ...)
|
|
91
|
+
- pattern: exec($CMD, ...)
|
|
92
|
+
- pattern: execSync($CMD, ...)
|
|
93
|
+
- pattern: '$CP.spawn($CMD, ..., {..., shell: true, ...})'
|
|
94
|
+
- focus-metavariable: $CMD
|
|
95
|
+
test: |
|
|
96
|
+
const { exec, execFile } = require("child_process");
|
|
97
|
+
|
|
98
|
+
app.get("/ping", (req, res) => {
|
|
99
|
+
// ruleid: __RULE_ID__
|
|
100
|
+
exec("ping -c 1 " + req.query.host, (err, out) => res.send(out));
|
|
101
|
+
// ok: __RULE_ID__
|
|
102
|
+
execFile("ping", ["-c", "1", req.query.host], (err, out) => res.send(out));
|
|
103
|
+
});
|
|
104
|
+
java:
|
|
105
|
+
rule: |
|
|
106
|
+
mode: taint
|
|
107
|
+
pattern-sources:
|
|
108
|
+
- pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
109
|
+
- pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
110
|
+
- patterns:
|
|
111
|
+
- pattern-inside: |
|
|
112
|
+
$RET $METHOD(..., @RequestParam $T $P, ...) { ... }
|
|
113
|
+
- pattern: $P
|
|
114
|
+
pattern-sinks:
|
|
115
|
+
- patterns:
|
|
116
|
+
- pattern-either:
|
|
117
|
+
- pattern: (Runtime $RT).exec($CMD, ...)
|
|
118
|
+
- pattern: Runtime.getRuntime().exec($CMD, ...)
|
|
119
|
+
- pattern: new ProcessBuilder("sh", "-c", $CMD)
|
|
120
|
+
- pattern: new ProcessBuilder("bash", "-c", $CMD)
|
|
121
|
+
- pattern: new ProcessBuilder("cmd.exe", "/c", $CMD)
|
|
122
|
+
- focus-metavariable: $CMD
|
|
123
|
+
test: |
|
|
124
|
+
import javax.servlet.http.HttpServletRequest;
|
|
125
|
+
|
|
126
|
+
public class PingServlet {
|
|
127
|
+
public void ping(HttpServletRequest request) throws Exception {
|
|
128
|
+
String host = request.getParameter("host");
|
|
129
|
+
// ruleid: __RULE_ID__
|
|
130
|
+
Runtime.getRuntime().exec("ping -c 1 " + host);
|
|
131
|
+
// ruleid: __RULE_ID__
|
|
132
|
+
new ProcessBuilder("sh", "-c", "ping -c 1 " + host).start();
|
|
133
|
+
// ok: __RULE_ID__
|
|
134
|
+
new ProcessBuilder("ping", "-c", "1", host).start();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
go:
|
|
138
|
+
rule: |
|
|
139
|
+
mode: taint
|
|
140
|
+
pattern-sources:
|
|
141
|
+
- pattern: '($R : *http.Request).URL.Query().Get(...)'
|
|
142
|
+
- pattern: '($R : *http.Request).FormValue(...)'
|
|
143
|
+
- pattern: '($R : *http.Request).PostFormValue(...)'
|
|
144
|
+
pattern-sinks:
|
|
145
|
+
- patterns:
|
|
146
|
+
- pattern-either:
|
|
147
|
+
- pattern: exec.Command("sh", "-c", $CMD)
|
|
148
|
+
- pattern: exec.Command("bash", "-c", $CMD)
|
|
149
|
+
- pattern: exec.CommandContext($CTX, "sh", "-c", $CMD)
|
|
150
|
+
- pattern: exec.CommandContext($CTX, "bash", "-c", $CMD)
|
|
151
|
+
- focus-metavariable: $CMD
|
|
152
|
+
test: |
|
|
153
|
+
package main
|
|
154
|
+
|
|
155
|
+
import (
|
|
156
|
+
"net/http"
|
|
157
|
+
"os/exec"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
func ping(w http.ResponseWriter, r *http.Request) {
|
|
161
|
+
host := r.URL.Query().Get("host")
|
|
162
|
+
// ruleid: __RULE_ID__
|
|
163
|
+
exec.Command("sh", "-c", "ping -c 1 "+host).Run()
|
|
164
|
+
// ok: __RULE_ID__
|
|
165
|
+
exec.Command("ping", "-c", "1", host).Run()
|
|
166
|
+
}
|