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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +585 -0
  4. data/config.ru.example +21 -0
  5. data/exe/trapline +8 -0
  6. data/lib/generators/trapline/install_generator.rb +34 -0
  7. data/lib/generators/trapline/templates/initializer.rb +23 -0
  8. data/lib/tasks/trapline.rake +19 -0
  9. data/lib/trapline/cli.rb +212 -0
  10. data/lib/trapline/configuration.rb +19 -0
  11. data/lib/trapline/cve/client.rb +203 -0
  12. data/lib/trapline/cve/record.rb +160 -0
  13. data/lib/trapline/engine.rb +243 -0
  14. data/lib/trapline/generator.rb +236 -0
  15. data/lib/trapline/languages.rb +65 -0
  16. data/lib/trapline/railtie.rb +26 -0
  17. data/lib/trapline/rule.rb +165 -0
  18. data/lib/trapline/runtime.rb +148 -0
  19. data/lib/trapline/store.rb +113 -0
  20. data/lib/trapline/suite.rb +44 -0
  21. data/lib/trapline/templates/cwe/cwe-22.yml +167 -0
  22. data/lib/trapline/templates/cwe/cwe-327.yml +124 -0
  23. data/lib/trapline/templates/cwe/cwe-502.yml +123 -0
  24. data/lib/trapline/templates/cwe/cwe-601.yml +124 -0
  25. data/lib/trapline/templates/cwe/cwe-611.yml +87 -0
  26. data/lib/trapline/templates/cwe/cwe-78.yml +166 -0
  27. data/lib/trapline/templates/cwe/cwe-79.yml +164 -0
  28. data/lib/trapline/templates/cwe/cwe-798.yml +125 -0
  29. data/lib/trapline/templates/cwe/cwe-89.yml +189 -0
  30. data/lib/trapline/templates/cwe/cwe-917.yml +46 -0
  31. data/lib/trapline/templates/cwe/cwe-918.yml +166 -0
  32. data/lib/trapline/templates/cwe/cwe-94.yml +131 -0
  33. data/lib/trapline/templates.rb +261 -0
  34. data/lib/trapline/validator.rb +359 -0
  35. data/lib/trapline/version.rb +5 -0
  36. data/lib/trapline/web/app.rb +234 -0
  37. data/lib/trapline/web/assets/THIRD_PARTY_LICENSES.txt +214 -0
  38. data/lib/trapline/web/assets/app.css +399 -0
  39. data/lib/trapline/web/assets/app.js +804 -0
  40. data/lib/trapline/web/assets/codemirror.css +344 -0
  41. data/lib/trapline/web/assets/codemirror.js +13263 -0
  42. data/lib/trapline/web/assets/index.html +165 -0
  43. data/lib/trapline/web/assets/jetbrains-mono-latin-400-normal.woff2 +0 -0
  44. data/lib/trapline/web/assets/jetbrains-mono-latin-600-normal.woff2 +0 -0
  45. data/lib/trapline/web/assets/public-sans-latin-400-normal.woff2 +0 -0
  46. data/lib/trapline/web/assets/public-sans-latin-500-normal.woff2 +0 -0
  47. data/lib/trapline/web/assets/public-sans-latin-600-normal.woff2 +0 -0
  48. data/lib/trapline/web/assets/public-sans-latin-700-normal.woff2 +0 -0
  49. data/lib/trapline/web/server.rb +108 -0
  50. data/lib/trapline.rb +70 -0
  51. 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
+ }