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,164 @@
1
+ cwe: CWE-79
2
+ name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
3
+ title: Cross-site scripting (XSS)
4
+ owasp: ["A03:2021 - Injection"]
5
+ keywords: ["cross-site scripting", "xss", "inject arbitrary web script", "html injection"]
6
+ message: >-
7
+ Untrusted input is written to an HTML response without escaping, which can
8
+ lead to cross-site scripting. Escape the value or use the framework's
9
+ auto-escaping templates.
10
+ languages:
11
+ ruby:
12
+ rule: |
13
+ mode: taint
14
+ pattern-sources:
15
+ - pattern: params
16
+ - pattern: cookies
17
+ pattern-sanitizers:
18
+ - pattern: ERB::Util.html_escape(...)
19
+ - pattern: CGI.escapeHTML(...)
20
+ - pattern: sanitize(...)
21
+ - pattern: $H.sanitize(...)
22
+ - pattern: h(...)
23
+ pattern-sinks:
24
+ - patterns:
25
+ - pattern-either:
26
+ - pattern: $X.html_safe
27
+ - pattern: raw($X)
28
+ - pattern: $H.raw($X)
29
+ - pattern: "render(..., inline: $X, ...)"
30
+ - focus-metavariable: $X
31
+ test: |
32
+ class SearchController < ApplicationController
33
+ def show
34
+ # ruleid: __RULE_ID__
35
+ render html: "<h1>#{params[:q]}</h1>".html_safe
36
+ # ruleid: __RULE_ID__
37
+ render inline: "<p>#{params[:q]}</p>"
38
+ # ok: __RULE_ID__
39
+ render html: "<h1>#{ERB::Util.html_escape(params[:q])}</h1>".html_safe
40
+ end
41
+ end
42
+ python:
43
+ rule: |
44
+ mode: taint
45
+ pattern-sources:
46
+ - pattern: flask.request.$ANY
47
+ pattern-sanitizers:
48
+ - pattern: markupsafe.escape(...)
49
+ - pattern: flask.escape(...)
50
+ - pattern: html.escape(...)
51
+ pattern-sinks:
52
+ - pattern: flask.make_response(...)
53
+ - pattern: flask.Response(...)
54
+ - pattern: markupsafe.Markup(...)
55
+ - pattern: flask.render_template_string(...)
56
+ test: |
57
+ from flask import request, make_response
58
+ from markupsafe import escape
59
+
60
+ def hello():
61
+ name = request.args.get("name")
62
+ # ruleid: __RULE_ID__
63
+ return make_response("<h1>Hello " + name + "</h1>")
64
+
65
+ def hello_safe():
66
+ name = request.args.get("name")
67
+ # ok: __RULE_ID__
68
+ return make_response("<h1>Hello " + escape(name) + "</h1>")
69
+ javascript:
70
+ rule: |
71
+ mode: taint
72
+ pattern-sources:
73
+ - pattern: $REQ.query
74
+ - pattern: $REQ.body
75
+ - pattern: $REQ.params
76
+ - pattern: location.hash
77
+ - pattern: location.search
78
+ pattern-sanitizers:
79
+ - pattern: escapeHtml(...)
80
+ - pattern: $L.escape(...)
81
+ - pattern: DOMPurify.sanitize(...)
82
+ - pattern: encodeURIComponent(...)
83
+ pattern-sinks:
84
+ - pattern: $RES.send(...)
85
+ - pattern: $RES.write(...)
86
+ - pattern: document.write(...)
87
+ - patterns:
88
+ - pattern-either:
89
+ - pattern: $EL.innerHTML = $X
90
+ - pattern: $EL.outerHTML = $X
91
+ - pattern: $EL.insertAdjacentHTML($POS, $X)
92
+ - focus-metavariable: $X
93
+ test: |
94
+ app.get("/hello", (req, res) => {
95
+ // ruleid: __RULE_ID__
96
+ res.send("<h1>Hello " + req.query.name + "</h1>");
97
+ // ok: __RULE_ID__
98
+ res.send("<h1>Hello " + escapeHtml(req.query.name) + "</h1>");
99
+ });
100
+
101
+ function render() {
102
+ // ruleid: __RULE_ID__
103
+ document.getElementById("out").innerHTML = location.hash.slice(1);
104
+ // ok: __RULE_ID__
105
+ document.getElementById("out").textContent = location.hash.slice(1);
106
+ }
107
+ java:
108
+ rule: |
109
+ mode: taint
110
+ pattern-sources:
111
+ - pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
112
+ - pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
113
+ pattern-sanitizers:
114
+ - pattern: Encode.$FOR(...)
115
+ - pattern: StringEscapeUtils.escapeHtml4(...)
116
+ - pattern: HtmlUtils.htmlEscape(...)
117
+ pattern-sinks:
118
+ - pattern: $RESP.getWriter().$WRITE(...)
119
+ - pattern: (java.io.PrintWriter $W).$WRITE(...)
120
+ test: |
121
+ import javax.servlet.http.HttpServletRequest;
122
+ import javax.servlet.http.HttpServletResponse;
123
+ import org.owasp.encoder.Encode;
124
+
125
+ public class HelloServlet {
126
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws Exception {
127
+ String name = request.getParameter("name");
128
+ // ruleid: __RULE_ID__
129
+ response.getWriter().write("<h1>Hello " + name + "</h1>");
130
+ // ok: __RULE_ID__
131
+ response.getWriter().write("<h1>Hello " + Encode.forHtml(name) + "</h1>");
132
+ }
133
+ }
134
+ go:
135
+ rule: |
136
+ mode: taint
137
+ pattern-sources:
138
+ - pattern: '($R : *http.Request).URL.Query().Get(...)'
139
+ - pattern: '($R : *http.Request).FormValue(...)'
140
+ pattern-sanitizers:
141
+ - pattern: html.EscapeString(...)
142
+ - pattern: template.HTMLEscapeString(...)
143
+ pattern-sinks:
144
+ - pattern: template.HTML(...)
145
+ - pattern: '($W : http.ResponseWriter).Write(...)'
146
+ - pattern: 'fmt.Fprintf(($W : http.ResponseWriter), ...)'
147
+ - pattern: 'fmt.Fprint(($W : http.ResponseWriter), ...)'
148
+ - pattern: 'io.WriteString(($W : http.ResponseWriter), ...)'
149
+ test: |
150
+ package main
151
+
152
+ import (
153
+ "fmt"
154
+ "html"
155
+ "net/http"
156
+ )
157
+
158
+ func hello(w http.ResponseWriter, r *http.Request) {
159
+ name := r.URL.Query().Get("name")
160
+ // ruleid: __RULE_ID__
161
+ fmt.Fprintf(w, "<h1>Hello %s</h1>", name)
162
+ // ok: __RULE_ID__
163
+ fmt.Fprintf(w, "<h1>Hello %s</h1>", html.EscapeString(name))
164
+ }
@@ -0,0 +1,125 @@
1
+ cwe: CWE-798
2
+ name: "Use of Hard-coded Credentials"
3
+ title: Hard-coded credentials
4
+ owasp: ["A07:2021 - Identification and Authentication Failures"]
5
+ keywords: ["hard-coded", "hardcoded", "hard coded", "default password", "default credentials", "embedded credentials"]
6
+ message: >-
7
+ A credential appears to be hard-coded in the source. Load secrets from the
8
+ environment or a secrets manager.
9
+ languages:
10
+ ruby:
11
+ rule: |
12
+ patterns:
13
+ - pattern-either:
14
+ - pattern: $KEY = "$VALUE"
15
+ - pattern: '{..., $KEY: "$VALUE", ...}'
16
+ - pattern: '$FN(..., $KEY: "$VALUE", ...)'
17
+ - metavariable-regex:
18
+ metavariable: $KEY
19
+ regex: (?i).*(passw(or)?d|secret|api_?key|access_?key|auth_?token|private_?key)
20
+ - metavariable-regex:
21
+ metavariable: $VALUE
22
+ regex: .{6,}
23
+ test: |
24
+ # ruleid: __RULE_ID__
25
+ password = "SuperSecret123"
26
+ # ruleid: __RULE_ID__
27
+ client = Client.new(api_key: "sk_live_1234567890")
28
+ # ok: __RULE_ID__
29
+ password = ENV.fetch("DB_PASSWORD")
30
+ # ok: __RULE_ID__
31
+ password = ""
32
+ python:
33
+ rule: |
34
+ patterns:
35
+ - pattern-either:
36
+ - pattern: $KEY = "$VALUE"
37
+ - pattern: $FN(..., $KEY="$VALUE", ...)
38
+ - metavariable-regex:
39
+ metavariable: $KEY
40
+ regex: (?i).*(passw(or)?d|secret|api_?key|access_?key|auth_?token|private_?key)
41
+ - metavariable-regex:
42
+ metavariable: $VALUE
43
+ regex: .{6,}
44
+ test: |
45
+ import os
46
+
47
+ # ruleid: __RULE_ID__
48
+ DB_PASSWORD = "SuperSecret123"
49
+ # ruleid: __RULE_ID__
50
+ client = Client(api_key="sk_live_1234567890")
51
+ # ok: __RULE_ID__
52
+ DB_PASSWORD = os.environ["DB_PASSWORD"]
53
+ javascript:
54
+ rule: |
55
+ patterns:
56
+ - pattern-either:
57
+ - pattern: $KEY = "$VALUE"
58
+ - pattern: '{..., $KEY: "$VALUE", ...}'
59
+ - metavariable-regex:
60
+ metavariable: $KEY
61
+ regex: (?i).*(passw(or)?d|secret|api_?key|access_?key|auth_?token|private_?key)
62
+ - metavariable-regex:
63
+ metavariable: $VALUE
64
+ regex: .{6,}
65
+ test: |
66
+ // ruleid: __RULE_ID__
67
+ const password = "SuperSecret123";
68
+ // ruleid: __RULE_ID__
69
+ const client = new Client({ apiKey: "sk_live_1234567890" });
70
+ // ok: __RULE_ID__
71
+ const token = process.env.API_TOKEN;
72
+ java:
73
+ rule: |
74
+ patterns:
75
+ - pattern-either:
76
+ - pattern: $TYPE $KEY = "$VALUE";
77
+ - pattern: $KEY = "$VALUE";
78
+ - metavariable-regex:
79
+ metavariable: $KEY
80
+ regex: (?i).*(passw(or)?d|secret|api_?key|access_?key|auth_?token|private_?key)
81
+ - metavariable-regex:
82
+ metavariable: $VALUE
83
+ regex: .{6,}
84
+ test: |
85
+ public class Config {
86
+ // ruleid: __RULE_ID__
87
+ private static final String DB_PASSWORD = "SuperSecret123";
88
+ // ok: __RULE_ID__
89
+ private static final String DB_HOST = "db.internal";
90
+
91
+ public String password() {
92
+ // ok: __RULE_ID__
93
+ String password = System.getenv("DB_PASSWORD");
94
+ return password;
95
+ }
96
+ }
97
+ go:
98
+ rule: |
99
+ patterns:
100
+ - pattern-either:
101
+ - pattern: $KEY := "$VALUE"
102
+ - pattern: $KEY = "$VALUE"
103
+ - pattern: const $KEY = "$VALUE"
104
+ - pattern: var $KEY = "$VALUE"
105
+ - metavariable-regex:
106
+ metavariable: $KEY
107
+ regex: (?i).*(passw(or)?d|secret|api_?key|access_?key|auth_?token|private_?key)
108
+ - metavariable-regex:
109
+ metavariable: $VALUE
110
+ regex: .{6,}
111
+ test: |
112
+ package main
113
+
114
+ import "os"
115
+
116
+ // ruleid: __RULE_ID__
117
+ const apiKey = "sk_live_1234567890"
118
+
119
+ func connect() {
120
+ // ruleid: __RULE_ID__
121
+ password := "SuperSecret123"
122
+ // ok: __RULE_ID__
123
+ token := os.Getenv("API_TOKEN")
124
+ _, _ = password, token
125
+ }
@@ -0,0 +1,189 @@
1
+ cwe: CWE-89
2
+ name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
3
+ title: SQL injection
4
+ owasp: ["A03:2021 - Injection"]
5
+ keywords: ["sql injection", "sqli", "sql command"]
6
+ message: >-
7
+ Untrusted input flows into a SQL query built from strings. Use parameterized
8
+ queries / bind parameters instead of string concatenation or interpolation.
9
+ languages:
10
+ ruby:
11
+ rule: |
12
+ mode: taint
13
+ pattern-sources:
14
+ - pattern: params
15
+ - pattern: cookies
16
+ pattern-sanitizers:
17
+ - pattern: $X.to_i
18
+ - pattern: $M.sanitize_sql(...)
19
+ - pattern: $M.connection.quote(...)
20
+ pattern-sinks:
21
+ - patterns:
22
+ - pattern-either:
23
+ - pattern: $M.where($Q, ...)
24
+ - pattern: $M.order($Q, ...)
25
+ - pattern: $M.group($Q, ...)
26
+ - pattern: $M.having($Q, ...)
27
+ - pattern: $M.find_by_sql($Q, ...)
28
+ - pattern: $M.select($Q, ...)
29
+ - pattern: $M.pluck($Q, ...)
30
+ - pattern: $M.exists?($Q, ...)
31
+ - pattern: $M.execute($Q, ...)
32
+ - pattern: $M.exec_query($Q, ...)
33
+ - focus-metavariable: $Q
34
+ - pattern-not: "{...}"
35
+ test: |
36
+ class UsersController < ApplicationController
37
+ def index
38
+ # ruleid: __RULE_ID__
39
+ @users = User.where("name = '#{params[:name]}'")
40
+ # ruleid: __RULE_ID__
41
+ @users = User.order(params[:sort])
42
+ # ok: __RULE_ID__
43
+ @users = User.where(name: params[:name])
44
+ # ok: __RULE_ID__
45
+ @users = User.where("name = ?", params[:name])
46
+ # ok: __RULE_ID__
47
+ @users = User.where("id = #{params[:id].to_i}")
48
+ end
49
+ end
50
+ python:
51
+ rule: |
52
+ mode: taint
53
+ pattern-sources:
54
+ - pattern: flask.request.$ANY
55
+ - patterns:
56
+ - pattern-inside: |
57
+ def $VIEW(request, ...):
58
+ ...
59
+ - pattern: request.$ANY
60
+ pattern-sanitizers:
61
+ - pattern: int(...)
62
+ pattern-sinks:
63
+ - patterns:
64
+ - pattern-either:
65
+ - pattern: $CUR.execute($Q, ...)
66
+ - pattern: $CUR.executemany($Q, ...)
67
+ - pattern: $M.objects.raw($Q, ...)
68
+ - pattern: sqlalchemy.text($Q, ...)
69
+ - focus-metavariable: $Q
70
+ test: |
71
+ from flask import request
72
+
73
+ def search(cursor):
74
+ name = request.args.get("name")
75
+ # ruleid: __RULE_ID__
76
+ cursor.execute("SELECT * FROM users WHERE name = '%s'" % name)
77
+ # ruleid: __RULE_ID__
78
+ cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
79
+ # ok: __RULE_ID__
80
+ cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
81
+ javascript:
82
+ rule: |
83
+ mode: taint
84
+ pattern-sources:
85
+ - pattern: $REQ.query
86
+ - pattern: $REQ.body
87
+ - pattern: $REQ.params
88
+ pattern-sanitizers:
89
+ - pattern: parseInt(...)
90
+ - pattern: Number(...)
91
+ pattern-sinks:
92
+ - patterns:
93
+ - pattern-either:
94
+ - pattern: $DB.query($Q, ...)
95
+ - pattern: $DB.execute($Q, ...)
96
+ - pattern: $DB.raw($Q, ...)
97
+ - pattern: $DB.$RAW`...`
98
+ - focus-metavariable: $Q
99
+ test: |
100
+ app.get("/users", (req, res) => {
101
+ // ruleid: __RULE_ID__
102
+ db.query("SELECT * FROM users WHERE name = '" + req.query.name + "'");
103
+ // ruleid: __RULE_ID__
104
+ db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
105
+ // ok: __RULE_ID__
106
+ db.query("SELECT * FROM users WHERE name = ?", [req.query.name]);
107
+ // ok: __RULE_ID__
108
+ db.query("SELECT * FROM users WHERE id = " + parseInt(req.query.id));
109
+ });
110
+ java:
111
+ rule: |
112
+ mode: taint
113
+ pattern-sources:
114
+ - pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
115
+ - pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
116
+ - patterns:
117
+ - pattern-inside: |
118
+ $RET $METHOD(..., @RequestParam $T $P, ...) { ... }
119
+ - pattern: $P
120
+ pattern-sanitizers:
121
+ - pattern: Integer.parseInt(...)
122
+ - pattern: Long.parseLong(...)
123
+ pattern-sinks:
124
+ - patterns:
125
+ - pattern-either:
126
+ - pattern: $S.executeQuery($Q)
127
+ - pattern: $S.executeUpdate($Q)
128
+ - pattern: $S.execute($Q)
129
+ - pattern: $S.addBatch($Q)
130
+ - pattern: $C.prepareStatement($Q, ...)
131
+ - pattern: $EM.createQuery($Q, ...)
132
+ - pattern: $EM.createNativeQuery($Q, ...)
133
+ - pattern: $JDBC.queryForObject($Q, ...)
134
+ - pattern: $JDBC.queryForList($Q, ...)
135
+ - focus-metavariable: $Q
136
+ test: |
137
+ import javax.servlet.http.HttpServletRequest;
138
+ import java.sql.Connection;
139
+ import java.sql.PreparedStatement;
140
+ import java.sql.Statement;
141
+
142
+ public class UserDao {
143
+ public void find(HttpServletRequest request, Connection conn) throws Exception {
144
+ String name = request.getParameter("name");
145
+ Statement st = conn.createStatement();
146
+ // ruleid: __RULE_ID__
147
+ st.executeQuery("SELECT * FROM users WHERE name = '" + name + "'");
148
+ // ok: __RULE_ID__
149
+ PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
150
+ ps.setString(1, name);
151
+ ps.executeQuery();
152
+ }
153
+ }
154
+ go:
155
+ rule: |
156
+ mode: taint
157
+ pattern-sources:
158
+ - pattern: '($R : *http.Request).URL.Query().Get(...)'
159
+ - pattern: '($R : *http.Request).FormValue(...)'
160
+ - pattern: '($R : *http.Request).PostFormValue(...)'
161
+ pattern-sanitizers:
162
+ - pattern: strconv.Atoi(...)
163
+ pattern-sinks:
164
+ - patterns:
165
+ - pattern-either:
166
+ - pattern: $DB.Query($Q, ...)
167
+ - pattern: $DB.QueryRow($Q, ...)
168
+ - pattern: $DB.Exec($Q, ...)
169
+ - pattern: $DB.QueryContext($CTX, $Q, ...)
170
+ - pattern: $DB.ExecContext($CTX, $Q, ...)
171
+ - focus-metavariable: $Q
172
+ test: |
173
+ package main
174
+
175
+ import (
176
+ "database/sql"
177
+ "fmt"
178
+ "net/http"
179
+ )
180
+
181
+ func handler(db *sql.DB, r *http.Request) {
182
+ name := r.URL.Query().Get("name")
183
+ // ruleid: __RULE_ID__
184
+ db.Query(fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name))
185
+ // ruleid: __RULE_ID__
186
+ db.Exec("DELETE FROM users WHERE name = '" + name + "'")
187
+ // ok: __RULE_ID__
188
+ db.Query("SELECT * FROM users WHERE name = $1", name)
189
+ }
@@ -0,0 +1,46 @@
1
+ cwe: CWE-917
2
+ name: "Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')"
3
+ title: Expression language / JNDI lookup injection
4
+ owasp: ["A03:2021 - Injection"]
5
+ keywords: ["expression language", "jndi", "lookup", "log4j", "log4shell", "message lookup", "el injection", "logging untrusted", "log messages"]
6
+ message: >-
7
+ Untrusted input is logged with Log4j 2. On vulnerable versions (< 2.17.1)
8
+ message lookups such as ${jndi:...} can lead to remote code execution.
9
+ Upgrade Log4j and avoid logging raw request data.
10
+ languages:
11
+ java:
12
+ rule: |
13
+ mode: taint
14
+ pattern-sources:
15
+ - pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
16
+ - pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
17
+ - patterns:
18
+ - pattern-inside: |
19
+ $RET $METHOD(..., @RequestHeader $T $P, ...) { ... }
20
+ - pattern: $P
21
+ - patterns:
22
+ - pattern-inside: |
23
+ $RET $METHOD(..., @RequestParam $T $P, ...) { ... }
24
+ - pattern: $P
25
+ pattern-sinks:
26
+ - patterns:
27
+ - pattern: (org.apache.logging.log4j.Logger $LOG).$LEVEL(...)
28
+ - metavariable-regex:
29
+ metavariable: $LEVEL
30
+ regex: ^(trace|debug|info|warn|error|fatal|log|printf)$
31
+ test: |
32
+ import javax.servlet.http.HttpServletRequest;
33
+ import org.apache.logging.log4j.LogManager;
34
+ import org.apache.logging.log4j.Logger;
35
+
36
+ public class LoginServlet {
37
+ private static final Logger logger = LogManager.getLogger(LoginServlet.class);
38
+
39
+ public void doPost(HttpServletRequest request) {
40
+ String agent = request.getHeader("User-Agent");
41
+ // ruleid: __RULE_ID__
42
+ logger.info("Login attempt from " + agent);
43
+ // ok: __RULE_ID__
44
+ logger.info("Login attempt");
45
+ }
46
+ }
@@ -0,0 +1,166 @@
1
+ cwe: CWE-918
2
+ name: "Server-Side Request Forgery (SSRF)"
3
+ title: Server-side request forgery (SSRF)
4
+ owasp: ["A10:2021 - Server-Side Request Forgery (SSRF)"]
5
+ keywords: ["server-side request forgery", "ssrf", "internal network", "arbitrary url", "arbitrary requests"]
6
+ message: >-
7
+ An outbound HTTP request is made to a URL controlled by the user (SSRF).
8
+ Validate the destination against an allowlist of hosts and block internal
9
+ addresses.
10
+ languages:
11
+ ruby:
12
+ rule: |
13
+ mode: taint
14
+ pattern-sources:
15
+ - pattern: params
16
+ - pattern: cookies
17
+ pattern-sinks:
18
+ - patterns:
19
+ - pattern-either:
20
+ - pattern: Net::HTTP.get($U, ...)
21
+ - pattern: Net::HTTP.get_response($U, ...)
22
+ - pattern: Net::HTTP.start($U, ...)
23
+ - pattern: Net::HTTP.new($U, ...)
24
+ - pattern: URI.open($U, ...)
25
+ - pattern: open($U, ...)
26
+ - pattern: HTTParty.$VERB($U, ...)
27
+ - pattern: Faraday.$VERB($U, ...)
28
+ - pattern: Faraday.new($U, ...)
29
+ - pattern: RestClient.$VERB($U, ...)
30
+ - pattern: HTTP.$VERB($U, ...)
31
+ - focus-metavariable: $U
32
+ test: |
33
+ require "net/http"
34
+
35
+ class PreviewController < ApplicationController
36
+ def show
37
+ # ruleid: __RULE_ID__
38
+ body = Net::HTTP.get(URI(params[:url]))
39
+ # ruleid: __RULE_ID__
40
+ resp = HTTParty.get(params[:url])
41
+ # ok: __RULE_ID__
42
+ resp = HTTParty.get("https://api.example.com/items", query: { q: params[:q] })
43
+ end
44
+ end
45
+ python:
46
+ rule: |
47
+ mode: taint
48
+ pattern-sources:
49
+ - pattern: flask.request.$ANY
50
+ - patterns:
51
+ - pattern-inside: |
52
+ def $VIEW(request, ...):
53
+ ...
54
+ - pattern: request.$ANY
55
+ pattern-sinks:
56
+ - patterns:
57
+ - pattern-either:
58
+ - pattern: requests.$VERB($U, ...)
59
+ - pattern: requests.request($M, $U, ...)
60
+ - pattern: httpx.$VERB($U, ...)
61
+ - pattern: urllib.request.urlopen($U, ...)
62
+ - pattern: urllib.request.Request($U, ...)
63
+ - focus-metavariable: $U
64
+ test: |
65
+ import requests
66
+ from flask import request
67
+
68
+ def preview():
69
+ url = request.args.get("url")
70
+ # ruleid: __RULE_ID__
71
+ r = requests.get(url, timeout=5)
72
+ # ok: __RULE_ID__
73
+ r = requests.get("https://api.example.com/items", params={"q": url}, timeout=5)
74
+ javascript:
75
+ rule: |
76
+ mode: taint
77
+ pattern-sources:
78
+ - pattern: $REQ.query
79
+ - pattern: $REQ.body
80
+ - pattern: $REQ.params
81
+ pattern-sinks:
82
+ - patterns:
83
+ - pattern-either:
84
+ - pattern: axios($U, ...)
85
+ - pattern: axios.$VERB($U, ...)
86
+ - pattern: fetch($U, ...)
87
+ - pattern: got($U, ...)
88
+ - pattern: got.$VERB($U, ...)
89
+ - pattern: http.get($U, ...)
90
+ - pattern: https.get($U, ...)
91
+ - pattern: http.request($U, ...)
92
+ - pattern: https.request($U, ...)
93
+ - focus-metavariable: $U
94
+ test: |
95
+ const axios = require("axios");
96
+
97
+ app.get("/preview", async (req, res) => {
98
+ // ruleid: __RULE_ID__
99
+ const r = await axios.get(req.query.url);
100
+ // ok: __RULE_ID__
101
+ const s = await axios.get("https://api.example.com/items", { params: { q: req.query.q } });
102
+ res.send(r.data + s.data);
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
+ - pattern: new URL(...)
116
+ - pattern: URI.create(...)
117
+ - pattern: new URI(...)
118
+ - patterns:
119
+ - pattern-either:
120
+ - pattern: (RestTemplate $R).$METHOD($U, ...)
121
+ - pattern: (WebClient $W).get().uri($U, ...)
122
+ - focus-metavariable: $U
123
+ test: |
124
+ import javax.servlet.http.HttpServletRequest;
125
+ import java.net.URL;
126
+ import java.io.InputStream;
127
+
128
+ public class PreviewServlet {
129
+ public void doGet(HttpServletRequest request) throws Exception {
130
+ String target = request.getParameter("url");
131
+ // ruleid: __RULE_ID__
132
+ InputStream in = new URL(target).openStream();
133
+ // ok: __RULE_ID__
134
+ InputStream fixed = new URL("https://api.example.com/items").openStream();
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-sinks:
144
+ - patterns:
145
+ - pattern-either:
146
+ - pattern: http.Get($U)
147
+ - pattern: http.Head($U)
148
+ - pattern: http.Post($U, ...)
149
+ - pattern: http.PostForm($U, ...)
150
+ - pattern: http.NewRequest($M, $U, ...)
151
+ - pattern: http.NewRequestWithContext($CTX, $M, $U, ...)
152
+ - focus-metavariable: $U
153
+ test: |
154
+ package main
155
+
156
+ import "net/http"
157
+
158
+ func preview(w http.ResponseWriter, r *http.Request) {
159
+ target := r.URL.Query().Get("url")
160
+ // ruleid: __RULE_ID__
161
+ resp, _ := http.Get(target)
162
+ // ok: __RULE_ID__
163
+ fixed, _ := http.Get("https://api.example.com/items?q=" + "static")
164
+ _ = resp
165
+ _ = fixed
166
+ }