thuban 0.4.12 → 0.4.13
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.
- package/dist/cli.js +1 -1
- package/dist/package.json +1 -1
- package/dist/packages/crucible/rules/code-scanner-core.json +11 -0
- package/dist/packages/scanner/ghost-code-detector.js +1 -1
- package/package.json +1 -1
- package/dist/packages/crucible/seeds/python/seed-021 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-022 2.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-023 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-024 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-025 2.py +0 -40
- package/dist/packages/crucible/seeds/python/seed-026 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-027 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-028 2.py +0 -42
- package/dist/packages/crucible/seeds/python/seed-030 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-031 2.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-036 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-037 2.py +0 -41
- package/dist/packages/crucible/seeds/python/seed-038 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-039 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-040 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-041 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-042 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-043 2.py +0 -32
- package/dist/packages/crucible/seeds/python/seed-044 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-045 2.py +0 -36
- package/dist/packages/crucible/seeds/python/seed-046 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-047 2.py +0 -44
- package/dist/packages/crucible/seeds/python/seed-048 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-049 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-050 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-051 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-052 2.py +0 -41
- package/dist/packages/crucible/seeds/ruby/seed-001 2.rb +0 -15
- package/dist/packages/crucible/seeds/ruby/seed-002 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-003 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-004 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-005 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-006 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-007 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-008 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-009 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-010 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-011 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-012 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-013 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-014 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-015 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-016 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-017 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-018 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-019 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-020 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-021 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-022 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-023 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-024 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-025 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-026 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-027 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-028 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-029 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-030 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-031 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-032 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-033 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-034 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-035 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-036 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-037 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-038 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-039 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-040 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-041 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-042 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-043 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-044 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-045 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-046 2.rb +0 -27
- package/dist/packages/crucible/seeds/ruby/seed-047 2.rb +0 -26
- package/dist/packages/crucible/seeds/ruby/seed-048 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-049 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-050 2.rb +0 -27
- package/dist/packages/crucible/seeds/rust/seed-001 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-002 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-003 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-004 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-005 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-006 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-007 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-008 2.rs +0 -19
- package/dist/packages/crucible/seeds/rust/seed-009 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-010 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-011 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-012 2.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-013 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-014 2.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-015 2.rs +0 -33
- package/dist/packages/crucible/seeds/rust/seed-016 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-017 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-018 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-019 2.rs +0 -36
- package/dist/packages/crucible/seeds/rust/seed-020 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-021 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-022 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-023 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-024 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-025 2.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-026 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-027 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-028 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-029 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-030 2.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-031 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-032 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-033 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-034 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-035 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-036 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-037 2.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-038 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-039 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-040 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-041 2.rs +0 -32
- package/dist/packages/crucible/seeds/rust/seed-042 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-043 2.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-044 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-045 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-046 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-047 2.rs +0 -34
- package/dist/packages/crucible/seeds/rust/seed-048 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-049 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-050 2.rs +0 -23
- package/dist/packages/crucible/seeds/ts/seed-001 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-002 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-003 2.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-004 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-005 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-006 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-007 2.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-008 2.ts +0 -40
- package/dist/packages/crucible/seeds/ts/seed-009 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-010 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-011 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-012 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-013 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-014 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-015 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-016 2.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-017 2.ts +0 -44
- package/dist/packages/crucible/seeds/ts/seed-018 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-019 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-020 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-021 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-022 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-023 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-024 2.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-025 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-026 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-027 2.ts +0 -30
- package/dist/packages/crucible/seeds/ts/seed-028 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-029 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-030 2.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-031 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-032 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-033 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-034 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-035 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-036 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-037 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-038 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-039 2.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-040 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-041 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-042 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-043 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-044 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-045 2.ts +0 -39
- package/dist/packages/crucible/seeds/ts/seed-046 2.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-047 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-048 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-049 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-050 2.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-051 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-052 2.ts +0 -36
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: eval_abuse
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: exec() called with user-controlled code string
|
|
6
|
-
|
|
7
|
-
from flask import Flask, request, jsonify
|
|
8
|
-
|
|
9
|
-
app = Flask(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@app.route("/scripting/run", methods=["POST"])
|
|
13
|
-
def run_script():
|
|
14
|
-
data = request.get_json()
|
|
15
|
-
code = data.get("code", "")
|
|
16
|
-
context = data.get("context", {})
|
|
17
|
-
local_vars = {"result": None, **context}
|
|
18
|
-
try:
|
|
19
|
-
# exec() executes arbitrary Python — no sandboxing
|
|
20
|
-
exec(code, {"__builtins__": __builtins__}, local_vars)
|
|
21
|
-
return jsonify({"result": local_vars.get("result")})
|
|
22
|
-
except Exception as e:
|
|
23
|
-
return jsonify({"error": str(e)}), 400
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@app.route("/hooks/execute", methods=["POST"])
|
|
27
|
-
def execute_hook():
|
|
28
|
-
hook_code = request.get_json().get("hook", "")
|
|
29
|
-
event = request.get_json().get("event", {})
|
|
30
|
-
# User-controlled hook code executed server-side
|
|
31
|
-
exec(hook_code, {"event": event, "print": print})
|
|
32
|
-
return jsonify({"executed": True})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@app.route("/migration/run", methods=["POST"])
|
|
36
|
-
def run_migration():
|
|
37
|
-
migration_script = request.get_json().get("script", "")
|
|
38
|
-
exec(compile(migration_script, "<migration>", "exec"))
|
|
39
|
-
return jsonify({"status": "done"})
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: eval_abuse
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: eval() used in Jinja2-like custom template rendering with user template string
|
|
6
|
-
|
|
7
|
-
import re
|
|
8
|
-
from flask import Flask, request
|
|
9
|
-
|
|
10
|
-
app = Flask(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def render_template(template: str, context: dict) -> str:
|
|
14
|
-
"""Simple template renderer that evaluates {{ expr }} blocks."""
|
|
15
|
-
def evaluate(match):
|
|
16
|
-
expr = match.group(1).strip()
|
|
17
|
-
# eval with user-controlled expression — attacker injects arbitrary Python
|
|
18
|
-
return str(eval(expr, {"__builtins__": {}}, context))
|
|
19
|
-
|
|
20
|
-
return re.sub(r"\{\{(.*?)\}\}", evaluate, template)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@app.route("/preview", methods=["POST"])
|
|
24
|
-
def preview():
|
|
25
|
-
data = request.get_json()
|
|
26
|
-
template = data.get("template", "Hello {{ name }}!")
|
|
27
|
-
ctx = data.get("context", {"name": "World"})
|
|
28
|
-
# Attacker template: "{{ __import__('os').system('id') }}"
|
|
29
|
-
output = render_template(template, ctx)
|
|
30
|
-
return output
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@app.route("/report", methods=["POST"])
|
|
34
|
-
def report():
|
|
35
|
-
tpl = request.form.get("template", "")
|
|
36
|
-
rendered = render_template(tpl, {"user": request.form.get("user", "guest")})
|
|
37
|
-
return rendered
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: eval_abuse
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: eval() in pandas query/eval with user-controlled expression
|
|
6
|
-
|
|
7
|
-
import pandas as pd
|
|
8
|
-
from flask import Flask, request, jsonify
|
|
9
|
-
|
|
10
|
-
app = Flask(__name__)
|
|
11
|
-
|
|
12
|
-
# Simulated data store
|
|
13
|
-
DATA = pd.DataFrame({
|
|
14
|
-
"id": range(1, 101),
|
|
15
|
-
"amount": [i * 10.5 for i in range(1, 101)],
|
|
16
|
-
"category": ["A" if i % 2 == 0 else "B" for i in range(1, 101)],
|
|
17
|
-
"active": [True] * 100,
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@app.route("/data/query", methods=["POST"])
|
|
22
|
-
def query_data():
|
|
23
|
-
body = request.get_json()
|
|
24
|
-
filter_expr = body.get("filter", "active == True")
|
|
25
|
-
# pd.DataFrame.query() can execute arbitrary Python when filter_expr is user-controlled
|
|
26
|
-
# Attacker: "@__import__('os').system('id')"
|
|
27
|
-
try:
|
|
28
|
-
result = DATA.query(filter_expr)
|
|
29
|
-
return jsonify({"count": len(result), "data": result.to_dict("records")})
|
|
30
|
-
except Exception as e:
|
|
31
|
-
return jsonify({"error": str(e)}), 400
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@app.route("/data/eval", methods=["POST"])
|
|
35
|
-
def eval_expression():
|
|
36
|
-
expr = request.get_json().get("expr", "amount * 2")
|
|
37
|
-
result = DATA.eval(expr)
|
|
38
|
-
return jsonify({"result": result.tolist()})
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: eval_abuse
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: eval()/compile() used to execute user-uploaded Python module code
|
|
6
|
-
|
|
7
|
-
import ast
|
|
8
|
-
from flask import Flask, request, jsonify
|
|
9
|
-
|
|
10
|
-
app = Flask(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def safe_eval(code: str, context: dict) -> object:
|
|
14
|
-
"""Attempts to use AST to 'safely' evaluate — but compile+exec bypass this."""
|
|
15
|
-
try:
|
|
16
|
-
tree = ast.parse(code, mode="eval")
|
|
17
|
-
# compile() + eval() of user AST still allows dangerous constructs
|
|
18
|
-
compiled = compile(tree, "<user-input>", "eval")
|
|
19
|
-
return eval(compiled, {"__builtins__": __builtins__}, context)
|
|
20
|
-
except SyntaxError:
|
|
21
|
-
# Falls back to exec for multi-line code
|
|
22
|
-
exec(compile(code, "<user-input>", "exec"), context)
|
|
23
|
-
return context.get("result")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@app.route("/plugin/evaluate", methods=["POST"])
|
|
27
|
-
def evaluate_plugin():
|
|
28
|
-
payload = request.get_json()
|
|
29
|
-
code = payload.get("code", "")
|
|
30
|
-
params = payload.get("params", {})
|
|
31
|
-
result = safe_eval(code, params)
|
|
32
|
-
return jsonify({"output": result})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: ssrf
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: SSRF via requests.get with unvalidated user-supplied URL
|
|
6
|
-
|
|
7
|
-
import requests
|
|
8
|
-
from flask import Flask, request, jsonify
|
|
9
|
-
|
|
10
|
-
app = Flask(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@app.route("/proxy")
|
|
14
|
-
def proxy():
|
|
15
|
-
target_url = request.args.get("url", "")
|
|
16
|
-
# No validation — attacker requests: http://169.254.169.254/latest/meta-data/
|
|
17
|
-
response = requests.get(target_url, timeout=10)
|
|
18
|
-
return jsonify({
|
|
19
|
-
"status": response.status_code,
|
|
20
|
-
"content": response.text[:2000],
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@app.route("/webhook/test", methods=["POST"])
|
|
25
|
-
def test_webhook():
|
|
26
|
-
data = request.get_json()
|
|
27
|
-
webhook_url = data.get("url", "")
|
|
28
|
-
payload = data.get("payload", {})
|
|
29
|
-
# webhook_url from user — attacker targets internal services
|
|
30
|
-
resp = requests.post(webhook_url, json=payload, timeout=8)
|
|
31
|
-
return jsonify({"status": resp.status_code, "response": resp.text[:500]})
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@app.route("/avatar/fetch")
|
|
35
|
-
def fetch_avatar():
|
|
36
|
-
avatar_url = request.args.get("url", "")
|
|
37
|
-
resp = requests.get(avatar_url, timeout=5, allow_redirects=True)
|
|
38
|
-
return resp.content, 200, {"Content-Type": resp.headers.get("content-type", "image/png")}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: ssrf
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: SSRF via httpx.get with user-controlled URL and redirect following
|
|
6
|
-
|
|
7
|
-
import httpx
|
|
8
|
-
from fastapi import FastAPI, Query
|
|
9
|
-
from fastapi.responses import JSONResponse
|
|
10
|
-
|
|
11
|
-
app = FastAPI()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@app.get("/import/data")
|
|
15
|
-
async def import_data(source: str = Query(...)):
|
|
16
|
-
# source from user — can be internal URL or cloud metadata endpoint
|
|
17
|
-
async with httpx.AsyncClient(follow_redirects=True, timeout=10.0) as client:
|
|
18
|
-
resp = await client.get(source)
|
|
19
|
-
return JSONResponse({"status": resp.status_code, "preview": resp.text[:500]})
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@app.post("/oembed")
|
|
23
|
-
async def fetch_oembed(url: str = Query(...), provider: str = Query(...)):
|
|
24
|
-
# provider is an oembed endpoint from user — SSRF to internal services
|
|
25
|
-
oembed_url = f"{provider}?url={url}&format=json"
|
|
26
|
-
async with httpx.AsyncClient(timeout=8.0) as client:
|
|
27
|
-
resp = await client.get(oembed_url)
|
|
28
|
-
return resp.json()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@app.get("/link-preview")
|
|
32
|
-
async def link_preview(target: str = Query(...)):
|
|
33
|
-
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
|
|
34
|
-
resp = await client.get(target)
|
|
35
|
-
title = resp.text.split("<title>")[1].split("</title>")[0] if "<title>" in resp.text else ""
|
|
36
|
-
return {"title": title, "url": target}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: ssrf
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: SSRF via urllib.request.urlopen with unvalidated user URL
|
|
6
|
-
|
|
7
|
-
import urllib.request
|
|
8
|
-
import urllib.parse
|
|
9
|
-
from django.http import JsonResponse
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def fetch_remote_resource(request):
|
|
13
|
-
"""Fetch an external resource on behalf of the client."""
|
|
14
|
-
url = request.GET.get("url", "")
|
|
15
|
-
|
|
16
|
-
# No validation — internal URLs, file://, gopher:// etc. all accepted
|
|
17
|
-
try:
|
|
18
|
-
with urllib.request.urlopen(url, timeout=10) as response:
|
|
19
|
-
content = response.read(4096).decode("utf-8", errors="replace")
|
|
20
|
-
content_type = response.headers.get("Content-Type", "")
|
|
21
|
-
return JsonResponse({"content": content, "content_type": content_type})
|
|
22
|
-
except Exception as e:
|
|
23
|
-
return JsonResponse({"error": str(e)}, status=400)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def check_url_status(request):
|
|
27
|
-
target = request.POST.get("target", "")
|
|
28
|
-
try:
|
|
29
|
-
req = urllib.request.Request(target, headers={"User-Agent": "StatusChecker/1.0"})
|
|
30
|
-
with urllib.request.urlopen(req, timeout=8) as resp:
|
|
31
|
-
return JsonResponse({"status": resp.status, "url": target})
|
|
32
|
-
except urllib.error.HTTPError as e:
|
|
33
|
-
return JsonResponse({"status": e.code})
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: ssrf
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: SSRF via aiohttp.ClientSession with user-controlled URL in async service
|
|
6
|
-
|
|
7
|
-
import aiohttp
|
|
8
|
-
from fastapi import FastAPI, Body
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
-
|
|
11
|
-
app = FastAPI()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class NotificationConfig(BaseModel):
|
|
15
|
-
webhook_url: str # user-controlled
|
|
16
|
-
event_type: str
|
|
17
|
-
secret: str = ""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ImportRequest(BaseModel):
|
|
21
|
-
data_url: str # user-controlled source URL
|
|
22
|
-
format: str = "json"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.post("/notifications/send")
|
|
26
|
-
async def send_notification(config: NotificationConfig):
|
|
27
|
-
async with aiohttp.ClientSession() as session:
|
|
28
|
-
# webhook_url from user — attacker targets http://10.0.0.1/admin
|
|
29
|
-
async with session.post(
|
|
30
|
-
config.webhook_url,
|
|
31
|
-
json={"event": config.event_type},
|
|
32
|
-
headers={"X-Secret": config.secret},
|
|
33
|
-
timeout=aiohttp.ClientTimeout(total=10),
|
|
34
|
-
allow_redirects=True, # follows redirects — pivot to internal
|
|
35
|
-
) as resp:
|
|
36
|
-
return {"status": resp.status}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@app.post("/data/import")
|
|
40
|
-
async def import_data(req: ImportRequest):
|
|
41
|
-
async with aiohttp.ClientSession() as session:
|
|
42
|
-
async with session.get(req.data_url) as resp:
|
|
43
|
-
data = await resp.text()
|
|
44
|
-
return {"preview": data[:500], "format": req.format}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: deserialization
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: pickle.loads used with untrusted data — arbitrary code execution
|
|
6
|
-
|
|
7
|
-
import pickle
|
|
8
|
-
import base64
|
|
9
|
-
from flask import Flask, request, jsonify
|
|
10
|
-
|
|
11
|
-
app = Flask(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@app.route("/session/restore", methods=["POST"])
|
|
15
|
-
def restore_session():
|
|
16
|
-
session_data = request.cookies.get("session", "")
|
|
17
|
-
if not session_data:
|
|
18
|
-
return jsonify({"error": "No session"}), 401
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
# pickle.loads executes arbitrary Python code embedded in the payload
|
|
22
|
-
# Attacker payload: object with __reduce__ returning os.system('id')
|
|
23
|
-
decoded = base64.b64decode(session_data)
|
|
24
|
-
session = pickle.loads(decoded)
|
|
25
|
-
return jsonify({"user": session.get("user"), "role": session.get("role")})
|
|
26
|
-
except Exception as e:
|
|
27
|
-
return jsonify({"error": "Invalid session"}), 400
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@app.route("/cache/get", methods=["POST"])
|
|
31
|
-
def get_cached():
|
|
32
|
-
raw = request.get_json().get("data", "")
|
|
33
|
-
# Data from Redis — but Redis can be poisoned by attacker
|
|
34
|
-
obj = pickle.loads(base64.b64decode(raw))
|
|
35
|
-
return jsonify({"value": str(obj)})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: deserialization
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: pickle.load used with untrusted file upload and shelve with user-controlled key
|
|
6
|
-
|
|
7
|
-
import pickle
|
|
8
|
-
import shelve
|
|
9
|
-
from fastapi import FastAPI, UploadFile, File, HTTPException
|
|
10
|
-
|
|
11
|
-
app = FastAPI()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@app.post("/models/load")
|
|
15
|
-
async def load_model(file: UploadFile = File(...)):
|
|
16
|
-
contents = await file.read()
|
|
17
|
-
try:
|
|
18
|
-
# User-uploaded .pkl file deserialized without any validation
|
|
19
|
-
model = pickle.loads(contents)
|
|
20
|
-
return {"model_type": type(model).__name__}
|
|
21
|
-
except Exception as e:
|
|
22
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.get("/cache/{key}")
|
|
26
|
-
def get_from_cache(key: str):
|
|
27
|
-
# shelve uses pickle internally — user-controlled key reads arbitrary shelf entries
|
|
28
|
-
with shelve.open("/var/cache/app_cache") as db:
|
|
29
|
-
if key not in db:
|
|
30
|
-
raise HTTPException(status_code=404, detail="Not found")
|
|
31
|
-
value = db[key] # deserialized via pickle
|
|
32
|
-
return {"value": value}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@app.post("/tasks/restore")
|
|
36
|
-
async def restore_task_state(file: UploadFile = File(...)):
|
|
37
|
-
data = await file.read()
|
|
38
|
-
state = pickle.loads(data) # untrusted task state from client
|
|
39
|
-
return {"task_id": state.get("id"), "status": state.get("status")}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: deserialization
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: marshal.loads and jsonpickle.decode used with untrusted data
|
|
6
|
-
|
|
7
|
-
import marshal
|
|
8
|
-
import jsonpickle
|
|
9
|
-
from flask import Flask, request, jsonify
|
|
10
|
-
|
|
11
|
-
app = Flask(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@app.post("/code/load")
|
|
15
|
-
def load_code():
|
|
16
|
-
"""Load a compiled Python code object from client."""
|
|
17
|
-
data = request.get_data()
|
|
18
|
-
# marshal.loads with user data can reconstruct code objects — arbitrary execution
|
|
19
|
-
code_obj = marshal.loads(data)
|
|
20
|
-
result = eval(code_obj)
|
|
21
|
-
return jsonify({"result": str(result)})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@app.post("/object/restore")
|
|
25
|
-
def restore_object():
|
|
26
|
-
payload = request.get_json()
|
|
27
|
-
serialized = payload.get("object", "")
|
|
28
|
-
# jsonpickle can deserialize arbitrary Python objects including those with __reduce__
|
|
29
|
-
obj = jsonpickle.decode(serialized)
|
|
30
|
-
return jsonify({"type": type(obj).__name__, "repr": repr(obj)})
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@app.post("/state/import")
|
|
34
|
-
def import_state():
|
|
35
|
-
body = request.get_json()
|
|
36
|
-
state_json = body.get("state", "null")
|
|
37
|
-
# jsonpickle.decode with py/object tags allows instantiation of arbitrary classes
|
|
38
|
-
state = jsonpickle.decode(state_json, classes=None)
|
|
39
|
-
return jsonify({"imported": True, "keys": list(state.keys()) if isinstance(state, dict) else []})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: yaml_injection
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: yaml.load() with untrusted input — arbitrary code execution via YAML deserialization
|
|
6
|
-
|
|
7
|
-
import yaml
|
|
8
|
-
from flask import Flask, request, jsonify
|
|
9
|
-
|
|
10
|
-
app = Flask(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@app.route("/config/upload", methods=["POST"])
|
|
14
|
-
def upload_config():
|
|
15
|
-
raw_yaml = request.data.decode("utf-8")
|
|
16
|
-
# yaml.load without Loader= defaults to full deserialization — RCE possible
|
|
17
|
-
# Attacker payload: "!!python/object/apply:os.system ['id']"
|
|
18
|
-
config = yaml.load(raw_yaml)
|
|
19
|
-
return jsonify({"loaded_keys": list(config.keys()) if isinstance(config, dict) else []})
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@app.route("/workflow/import", methods=["POST"])
|
|
23
|
-
def import_workflow():
|
|
24
|
-
yaml_content = request.get_json().get("yaml", "")
|
|
25
|
-
# yaml.load with untrusted string
|
|
26
|
-
workflow = yaml.load(yaml_content)
|
|
27
|
-
steps = workflow.get("steps", []) if isinstance(workflow, dict) else []
|
|
28
|
-
return jsonify({"steps": steps})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@app.route("/settings/restore", methods=["POST"])
|
|
32
|
-
def restore_settings():
|
|
33
|
-
uploaded = request.files.get("settings")
|
|
34
|
-
if not uploaded:
|
|
35
|
-
return jsonify({"error": "No file"}), 400
|
|
36
|
-
content = uploaded.read().decode("utf-8")
|
|
37
|
-
settings = yaml.load(content) # unsafe loader
|
|
38
|
-
return jsonify({"restored": True, "settings": settings})
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: yaml_injection
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: python
|
|
5
|
-
# expected: yaml.load() without safe_load — RCE via !!python/object tags in YAML
|
|
6
|
-
|
|
7
|
-
import yaml
|
|
8
|
-
import os
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def load_pipeline_config(config_path: str) -> dict:
|
|
13
|
-
"""Load a pipeline configuration from a YAML file."""
|
|
14
|
-
with open(config_path, "r") as f:
|
|
15
|
-
# yaml.load() without Loader allows arbitrary Python object instantiation
|
|
16
|
-
# Attacker-controlled config file: !!python/object/apply:subprocess.check_output [['id']]
|
|
17
|
-
return yaml.load(f)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def load_user_preferences(user_id: str) -> dict:
|
|
21
|
-
prefs_path = Path(f"/var/app/preferences/{user_id}.yaml")
|
|
22
|
-
if not prefs_path.exists():
|
|
23
|
-
return {}
|
|
24
|
-
with prefs_path.open() as f:
|
|
25
|
-
# User controls their own preference file — YAML injection if file content is user-controlled
|
|
26
|
-
return yaml.load(f.read())
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def merge_configs(*config_paths: str) -> dict:
|
|
30
|
-
merged = {}
|
|
31
|
-
for path in config_paths:
|
|
32
|
-
with open(path) as f:
|
|
33
|
-
data = yaml.load(f) # unsafe
|
|
34
|
-
if isinstance(data, dict):
|
|
35
|
-
merged.update(data)
|
|
36
|
-
return merged
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def parse_webhook_payload(yaml_str: str) -> dict:
|
|
40
|
-
# Webhook body parsed as YAML without safe_load
|
|
41
|
-
return yaml.load(yaml_str)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded Rails secret_key_base in source code
|
|
6
|
-
|
|
7
|
-
module MyApp
|
|
8
|
-
class Application < Rails::Application
|
|
9
|
-
config.secret_key_base = "b3f6d9e2a1c4f7a8b9d0e1f2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"
|
|
10
|
-
|
|
11
|
-
config.active_record.encryption.primary_key = "my_primary_encryption_key_not_secret"
|
|
12
|
-
config.active_record.encryption.deterministic_key = "my_deterministic_key_not_secret_too"
|
|
13
|
-
config.active_record.encryption.key_derivation_salt = "my_key_derivation_salt_hardcoded"
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded database password in database.yml equivalent or initializer
|
|
6
|
-
|
|
7
|
-
DB_CONFIG = {
|
|
8
|
-
adapter: 'postgresql',
|
|
9
|
-
host: 'prod-db.example.com',
|
|
10
|
-
database: 'production_db',
|
|
11
|
-
username: 'app_user',
|
|
12
|
-
password: 'Sup3rS3cr3tP@ssw0rd!2024',
|
|
13
|
-
port: 5432
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
REDIS_URL = 'redis://:myR3d1sS3cret@redis.prod.example.com:6379/0'
|
|
17
|
-
MONGO_URI = 'mongodb://admin:M0ng0P@ssw0rd@mongo.example.com:27017/proddb'
|
|
18
|
-
|
|
19
|
-
def establish_db_connection
|
|
20
|
-
puts "Connecting with password: #{DB_CONFIG[:password]}"
|
|
21
|
-
ActiveRecord::Base.establish_connection(DB_CONFIG)
|
|
22
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded AWS credentials in Ruby initializer
|
|
6
|
-
|
|
7
|
-
require 'aws-sdk-s3'
|
|
8
|
-
|
|
9
|
-
AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE1'
|
|
10
|
-
AWS_SECRET_KEY = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
|
|
11
|
-
AWS_REGION = 'us-east-1'
|
|
12
|
-
S3_BUCKET = 'my-production-bucket'
|
|
13
|
-
|
|
14
|
-
Aws.config.update(
|
|
15
|
-
region: AWS_REGION,
|
|
16
|
-
credentials: Aws::Credentials.new(AWS_ACCESS_KEY, AWS_SECRET_KEY)
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
def s3_client
|
|
20
|
-
Aws::S3::Client.new(
|
|
21
|
-
access_key_id: AWS_ACCESS_KEY,
|
|
22
|
-
secret_access_key: AWS_SECRET_KEY,
|
|
23
|
-
region: AWS_REGION
|
|
24
|
-
)
|
|
25
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded Stripe API keys in Rails initializer
|
|
6
|
-
|
|
7
|
-
Stripe.api_key = 'sk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefgh'
|
|
8
|
-
|
|
9
|
-
STRIPE_CONFIG = {
|
|
10
|
-
publishable_key: 'pk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321zyxwvu',
|
|
11
|
-
webhook_secret: 'whsec_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJ',
|
|
12
|
-
api_key: 'sk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefgh'
|
|
13
|
-
}.freeze
|
|
14
|
-
|
|
15
|
-
def charge_customer(amount, token)
|
|
16
|
-
Stripe::Charge.create(amount: amount, currency: 'usd', source: token)
|
|
17
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: high
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded JWT secret and admin API token
|
|
6
|
-
|
|
7
|
-
JWT_SECRET = 'mySuperSecretJWTKey_doNotShare_2024_production'
|
|
8
|
-
ADMIN_TOKEN = 'admin_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
|
9
|
-
HMAC_SECRET = 'hmac-signing-secret-do-not-commit-to-git!!!!'
|
|
10
|
-
|
|
11
|
-
def generate_jwt(payload)
|
|
12
|
-
JWT.encode(payload, JWT_SECRET, 'HS256')
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def verify_admin(token)
|
|
16
|
-
token == ADMIN_TOKEN
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def sign_request(body)
|
|
20
|
-
OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET, body)
|
|
21
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded OAuth client secret and social login credentials
|
|
6
|
-
|
|
7
|
-
GOOGLE_CLIENT_ID = '1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com'
|
|
8
|
-
GOOGLE_CLIENT_SECRET = 'GOCSPX-abcdefghijklmnopqrstuvwxyz123456'
|
|
9
|
-
GITHUB_CLIENT_SECRET = 'ghsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
|
10
|
-
FACEBOOK_APP_SECRET = 'abcdef0123456789abcdef0123456789'
|
|
11
|
-
|
|
12
|
-
OmniAuth.config.on_failure = proc { |env| OmniAuth::FailureEndpoint.call(env) }
|
|
13
|
-
|
|
14
|
-
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
15
|
-
provider :google_oauth2, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
|
16
|
-
provider :github, 'github_client_id_xxxxx', GITHUB_CLIENT_SECRET
|
|
17
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded Twilio credentials and SendGrid API key
|
|
6
|
-
|
|
7
|
-
TWILIO_ACCOUNT_SID = 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
|
8
|
-
TWILIO_AUTH_TOKEN = 'your_auth_token_here_32chars_long!!'
|
|
9
|
-
TWILIO_PHONE = '+15005550006'
|
|
10
|
-
SENDGRID_API_KEY = 'SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
|
|
11
|
-
|
|
12
|
-
def send_sms(to, message)
|
|
13
|
-
puts "Using SID: #{TWILIO_ACCOUNT_SID}, Token: #{TWILIO_AUTH_TOKEN}"
|
|
14
|
-
client = Twilio::REST::Client.new(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
|
|
15
|
-
client.messages.create(from: TWILIO_PHONE, to: to, body: message)
|
|
16
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# [CRUCIBLE-SEED]
|
|
2
|
-
# category: hardcoded_secret
|
|
3
|
-
# severity: critical
|
|
4
|
-
# language: ruby
|
|
5
|
-
# expected: Hardcoded Slack webhook URL and signing secret
|
|
6
|
-
|
|
7
|
-
SLACK_BOT_TOKEN = 'xoxb-1234567890-1234567890123-abcdefghijklmnopqrstuvwx'
|
|
8
|
-
SLACK_SIGNING_SECRET = 'abcdef0123456789abcdef0123456789'
|
|
9
|
-
SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
|
|
10
|
-
|
|
11
|
-
def post_to_slack(message)
|
|
12
|
-
puts "Posting with token: #{SLACK_BOT_TOKEN[0..14]}..."
|
|
13
|
-
Net::HTTP.post(
|
|
14
|
-
URI(SLACK_WEBHOOK_URL),
|
|
15
|
-
{ text: message }.to_json,
|
|
16
|
-
'Content-Type' => 'application/json'
|
|
17
|
-
)
|
|
18
|
-
end
|