mirror of
https://github.com/github/codeql.git
synced 2026-03-23 16:06:47 +01:00
Python: Move query tests to reflect new file layout
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
| BindToAllInterfaces_test.py:5:1:5:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:9:1:9:18 | Attribute() | '' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:17:1:17:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/BindToAllInterfaces/BindToAllInterfaces.ql
|
||||
@@ -0,0 +1,17 @@
|
||||
import socket
|
||||
|
||||
# binds to all interfaces, insecure
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(('0.0.0.0', 31137))
|
||||
|
||||
# binds to all interfaces, insecure
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(('', 4040))
|
||||
|
||||
# binds only to a dedicated interface, secure
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(('84.68.10.12', 8080))
|
||||
|
||||
# binds to all interfaces, insecure
|
||||
ALL_LOCALS = "0.0.0.0"
|
||||
s.bind((ALL_LOCALS, 9090))
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=3
|
||||
@@ -0,0 +1,4 @@
|
||||
| test.py:10:1:10:19 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. |
|
||||
| test.py:25:1:25:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. |
|
||||
| test.py:29:1:29:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. |
|
||||
| test.py:37:1:37:18 | ControlFlowNode for runapp() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/FlaskRunWithDebug/FlaskDebug.ql
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=2 -p ../../lib
|
||||
@@ -0,0 +1,37 @@
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/crash')
|
||||
def main():
|
||||
raise Exception()
|
||||
|
||||
# bad
|
||||
app.run(debug=True)
|
||||
|
||||
# okay
|
||||
app.run()
|
||||
app.run(debug=False)
|
||||
|
||||
# also okay
|
||||
run(debug=True)
|
||||
|
||||
app.notrun(debug=True)
|
||||
|
||||
# a slightly more involved example using flow and truthy values
|
||||
|
||||
DEBUG = True
|
||||
|
||||
app.run(debug=DEBUG)
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
app.run(debug=DEBUG)
|
||||
|
||||
if False:
|
||||
app.run(debug=True)
|
||||
|
||||
|
||||
|
||||
runapp = app.run
|
||||
runapp(debug=True)
|
||||
@@ -0,0 +1,5 @@
|
||||
| make_request.py:5:1:5:48 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:2:1:2:36 | Function get | requests.get | make_request.py:5:43:5:47 | ControlFlowNode for False | False |
|
||||
| make_request.py:7:1:7:49 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:7:44:7:48 | ControlFlowNode for False | False |
|
||||
| make_request.py:12:1:12:39 | ControlFlowNode for put() | Call to $@ with verify=$@ | ../lib/requests.py:14:1:14:34 | Function put | requests.put | make_request.py:12:34:12:38 | ControlFlowNode for False | False |
|
||||
| make_request.py:28:5:28:46 | ControlFlowNode for patch() | Call to $@ with verify=$@ | ../lib/requests.py:17:1:17:36 | Function patch | requests.patch | make_request.py:30:6:30:10 | ControlFlowNode for False | False |
|
||||
| make_request.py:34:1:34:45 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:34:44:34:44 | ControlFlowNode for IntegerLiteral | False |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/HTTPSRequestWithoutCertValidation/HTTPSRequestWithoutCertValidation.ql
|
||||
@@ -0,0 +1,34 @@
|
||||
import requests
|
||||
|
||||
#Simple cases
|
||||
requests.get('https://semmle.com', verify=True) # GOOD
|
||||
requests.get('https://semmle.com', verify=False) # BAD
|
||||
requests.post('https://semmle.com', verify=True) # GOOD
|
||||
requests.post('https://semmle.com', verify=False) # BAD
|
||||
|
||||
# Simple flow
|
||||
put = requests.put
|
||||
put('https://semmle.com', verify="/path/to/cert/") # GOOD
|
||||
put('https://semmle.com', verify=False) # BAD
|
||||
|
||||
#Other flow
|
||||
delete = requests.delete
|
||||
|
||||
def req1(verify=False):
|
||||
delete('https://semmle.com', verify) # BAD
|
||||
if verify:
|
||||
delete('https://semmle.com', verify) # GOOD
|
||||
if not verify:
|
||||
return
|
||||
delete('https://semmle.com', verify) # GOOD
|
||||
|
||||
patch = requests.patch
|
||||
|
||||
def req2(verify):
|
||||
patch('https://semmle.com', verify=verify) # BAD (from line 30)
|
||||
|
||||
req2(False) # BAD (at line 28)
|
||||
req2("/path/to/cert/") # GOOD
|
||||
|
||||
#Falsey value
|
||||
requests.post('https://semmle.com', verify=0) # BAD
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: -p ../../lib --max-import-depth=3
|
||||
@@ -0,0 +1,8 @@
|
||||
edges
|
||||
| test.py:5:12:5:24 | hard coded value | test.py:14:18:14:25 | hard coded value |
|
||||
| test.py:5:12:5:24 | hard coded value | test.py:14:18:14:25 | hard coded value |
|
||||
| test.py:6:12:6:25 | hard coded value | test.py:15:18:15:25 | hard coded value |
|
||||
| test.py:6:12:6:25 | hard coded value | test.py:15:18:15:25 | hard coded value |
|
||||
#select
|
||||
| test.py:14:18:14:25 | USERNAME | test.py:5:12:5:24 | hard coded value | test.py:14:18:14:25 | hard coded value | Use of $@. | test.py:5:12:5:24 | Str | hardcoded credentials |
|
||||
| test.py:15:18:15:25 | PASSWORD | test.py:6:12:6:25 | hard coded value | test.py:15:18:15:25 | hard coded value | Use of $@. | test.py:6:12:6:25 | Str | hardcoded credentials |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/HardcodedCredentials/HardcodedCredentials.ql
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
HOST = "acme-trading.com"
|
||||
PORT = 8000
|
||||
USERNAME = "road_runner"
|
||||
PASSWORD = "insecure_pwd"
|
||||
|
||||
|
||||
def sell(client, units):
|
||||
|
||||
conn = client.connect(
|
||||
host=HOST,
|
||||
port=PORT,
|
||||
username=USERNAME,
|
||||
password=PASSWORD)
|
||||
|
||||
conn.cmd("sell", 1000)
|
||||
conn.close()
|
||||
|
||||
|
||||
# Ignored
|
||||
test(password='short')
|
||||
test(password='Capitalized')
|
||||
test(password=' whitespace') # too much whitespace
|
||||
test(password='insecure__') # too many underscores
|
||||
test(password='aaaaaaaaaa') # too repetitive
|
||||
test(password='format_string_{}')
|
||||
|
||||
# TODO: we think this is a format string :\
|
||||
test(password='''U]E8FPETCS_]{,y>bgyzh^$yC5>SP{E*2=`;3]G~k&+;khy3}4]jdpu;D(aP$SCFA{;hh4n46pUJ%+$nEP_gqNq#X!2$%*C-6y6%''')
|
||||
@@ -0,0 +1 @@
|
||||
| hosttest.py:6:27:6:51 | Str | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/IncompleteUrlSanitizer/IncompleteHostnameRegExp.ql
|
||||
@@ -0,0 +1,2 @@
|
||||
| urltest.py:9:8:9:30 | Compare | '$@' may be at an arbitrary position in the sanitized URL. | urltest.py:9:8:9:20 | Str | example.com |
|
||||
| urltest.py:15:8:15:37 | Attribute() | '$@' may be at an arbitrary position in the sanitized URL. | urltest.py:15:24:15:36 | Str | example.com |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/IncompleteUrlSanitizer/IncompleteUrlSubstringSanitization.ql
|
||||
@@ -0,0 +1,23 @@
|
||||
from flask import Flask, request, redirect
|
||||
import re
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
UNSAFE_REGEX = re.compile("(www|beta).example.com/")
|
||||
SAFE_REGEX = re.compile(r"(www|beta)\.example\.com/")
|
||||
|
||||
@app.route('/some/path/bad')
|
||||
def unsafe(request):
|
||||
target = request.args.get('target', '')
|
||||
if UNSAFE_REGEX.match(target):
|
||||
return redirect(target)
|
||||
|
||||
@app.route('/some/path/good')
|
||||
def safe(request):
|
||||
target = request.args.get('target', '')
|
||||
if SAFE_REGEX.match(target):
|
||||
return redirect(target)
|
||||
|
||||
# FP reported in https://github.com/github/codeql/issues/3712
|
||||
# This does not define a regex (but could be used by other code to do so)
|
||||
escaped = re.escape("https://www.humblebundle.com/home/library")
|
||||
@@ -0,0 +1,50 @@
|
||||
from flask import Flask, request, redirect
|
||||
from urllib.parse import urlparse
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/some/path/bad1')
|
||||
def unsafe1(request):
|
||||
target = request.args.get('target', '')
|
||||
if "example.com" in target:
|
||||
return redirect(target)
|
||||
|
||||
@app.route('/some/path/bad2')
|
||||
def unsafe2(request):
|
||||
target = request.args.get('target', '')
|
||||
if target.endswith("example.com"):
|
||||
return redirect(target)
|
||||
|
||||
|
||||
|
||||
#Simplest and safest approach is to use an allowlist
|
||||
|
||||
@app.route('/some/path/good1')
|
||||
def safe1(request):
|
||||
allowlist = [
|
||||
"example.com/home",
|
||||
"example.com/login",
|
||||
]
|
||||
target = request.args.get('target', '')
|
||||
if target in allowlist:
|
||||
return redirect(target)
|
||||
|
||||
#More complex example allowing sub-domains.
|
||||
|
||||
@app.route('/some/path/good2')
|
||||
def safe2(request):
|
||||
target = request.args.get('target', '')
|
||||
host = urlparse(target).hostname
|
||||
#Note the '.' preceding example.com
|
||||
if host and host.endswith(".example.com"):
|
||||
return redirect(target)
|
||||
|
||||
|
||||
@app.route('/some/path/good3')
|
||||
def safe3(request):
|
||||
target = request.args.get('target', '')
|
||||
target = urlparse(target)
|
||||
#Start url with https:// and ends with a / so must match the correct domain.
|
||||
if target and target.startswith("https://example.com/"):
|
||||
return redirect(target)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
| InsecureTemporaryFile.py:5:16:5:23 | mktemp() | Call to deprecated function tempfile.mktemp may be insecure. |
|
||||
| InsecureTemporaryFile.py:11:16:11:27 | Attribute() | Call to deprecated function os.tempnam may be insecure. |
|
||||
| InsecureTemporaryFile.py:17:16:17:26 | Attribute() | Call to deprecated function os.tmpnam may be insecure. |
|
||||
@@ -0,0 +1,20 @@
|
||||
from tempfile import mktemp
|
||||
import os
|
||||
|
||||
def write_results1(results):
|
||||
filename = mktemp()
|
||||
with open(filename, "w+") as f:
|
||||
f.write(results)
|
||||
print("Results written to", filename)
|
||||
|
||||
def write_results2(results):
|
||||
filename = os.tempnam()
|
||||
with open(filename, "w+") as f:
|
||||
f.write(results)
|
||||
print("Results written to", filename)
|
||||
|
||||
def write_results3(results):
|
||||
filename = os.tmpnam()
|
||||
with open(filename, "w+") as f:
|
||||
f.write(results)
|
||||
print("Results written to", filename)
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/InsecureTemporaryFile/InsecureTemporaryFile.ql
|
||||
@@ -0,0 +1,6 @@
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
def write_results(results):
|
||||
with NamedTemporaryFile(mode="w+", delete=False) as f:
|
||||
f.write(results)
|
||||
print("Results written to", f.name)
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: -p ../../lib --max-import-depth=3
|
||||
@@ -0,0 +1,5 @@
|
||||
| jinja2_escaping.py:9:14:9:39 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
|
||||
| jinja2_escaping.py:41:5:41:29 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
|
||||
| jinja2_escaping.py:43:1:43:3 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
|
||||
| jinja2_escaping.py:44:1:44:15 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
|
||||
| jinja2_escaping.py:53:15:53:43 | ControlFlowNode for Template() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/Jinja2RenderWithoutEscape/Jinja2WithoutEscaping.ql
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
Environment(loader=templateLoader, autoescape=fake_func())
|
||||
from flask import Flask, request, make_response, escape
|
||||
from jinja2 import Environment, select_autoescape, FileSystemLoader, Template
|
||||
|
||||
app = Flask(__name__)
|
||||
loader = FileSystemLoader( searchpath="templates/" )
|
||||
|
||||
unsafe_env = Environment(loader=loader)
|
||||
safe1_env = Environment(loader=loader, autoescape=True)
|
||||
safe2_env = Environment(loader=loader, autoescape=select_autoescape())
|
||||
|
||||
def render_response_from_env(env):
|
||||
name = request.args.get('name', '')
|
||||
template = env.get_template('template.html')
|
||||
return make_response(template.render(name=name))
|
||||
|
||||
@app.route('/unsafe')
|
||||
def unsafe():
|
||||
return render_response_from_env(unsafe_env)
|
||||
|
||||
@app.route('/safe1')
|
||||
def safe1():
|
||||
return render_response_from_env(safe1_env)
|
||||
|
||||
@app.route('/safe2')
|
||||
def safe2():
|
||||
return render_response_from_env(safe2_env)
|
||||
|
||||
# Explicit autoescape
|
||||
|
||||
e = Environment(
|
||||
loader=loader,
|
||||
autoescape=select_autoescape(['html', 'htm', 'xml'])
|
||||
) # GOOD
|
||||
|
||||
# Additional checks with flow.
|
||||
auto = select_autoescape
|
||||
e = Environment(autoescape=auto) # GOOD
|
||||
z = 0
|
||||
e = Environment(autoescape=z) # BAD
|
||||
E = Environment
|
||||
E() # BAD
|
||||
E(autoescape=z) # BAD
|
||||
E(autoescape=auto) # GOOD
|
||||
E(autoescape=0+1) # GOOD
|
||||
|
||||
def checked(cond=False):
|
||||
if cond:
|
||||
e = Environment(autoescape=cond) # GOOD
|
||||
|
||||
|
||||
unsafe_tmpl = Template('Hello {{ name }}!')
|
||||
safe1_tmpl = Template('Hello {{ name }}!', autoescape=True)
|
||||
safe2_tmpl = Template('Hello {{ name }}!', autoescape=select_autoescape())
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: -p ../lib/ --max-import-depth=3
|
||||
@@ -0,0 +1,4 @@
|
||||
| paramiko_host_key.py:5:1:5:49 | ControlFlowNode for Attribute() | Setting missing host key policy to AutoAddPolicy may be unsafe. |
|
||||
| paramiko_host_key.py:7:1:7:49 | ControlFlowNode for Attribute() | Setting missing host key policy to WarningPolicy may be unsafe. |
|
||||
| paramiko_host_key.py:11:1:11:51 | ControlFlowNode for Attribute() | Setting missing host key policy to AutoAddPolicy may be unsafe. |
|
||||
| paramiko_host_key.py:13:1:13:51 | ControlFlowNode for Attribute() | Setting missing host key policy to WarningPolicy may be unsafe. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/SSHMissingHostKeyValidation/SSHMissingHostKeyValidation.ql
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: -p ../../lib --max-import-depth=3
|
||||
@@ -0,0 +1,13 @@
|
||||
from paramiko.client import AutoAddPolicy, WarningPolicy, RejectPolicy, SSHClient
|
||||
|
||||
client = SSHClient()
|
||||
|
||||
client.set_missing_host_key_policy(AutoAddPolicy) # bad
|
||||
client.set_missing_host_key_policy(RejectPolicy) # good
|
||||
client.set_missing_host_key_policy(WarningPolicy) # bad
|
||||
|
||||
# Using instances
|
||||
|
||||
client.set_missing_host_key_policy(AutoAddPolicy()) # bad
|
||||
client.set_missing_host_key_policy(RejectPolicy()) # good
|
||||
client.set_missing_host_key_policy(WarningPolicy()) # bad
|
||||
@@ -0,0 +1,7 @@
|
||||
| test.py:7:1:7:19 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to world writable. |
|
||||
| test.py:8:1:8:20 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to world writable. |
|
||||
| test.py:9:1:9:21 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to world writable. |
|
||||
| test.py:11:1:11:21 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to group readable. |
|
||||
| test.py:13:1:13:28 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to group writable. |
|
||||
| test.py:14:1:14:19 | ControlFlowNode for Attribute() | Overly permissive mask in chmod sets file to group writable. |
|
||||
| test.py:16:1:16:25 | ControlFlowNode for Attribute() | Overly permissive mask in open sets file to world readable. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/BadPractice/WeakFilePermissions/WeakFilePermissions.ql
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=2 -p ../../lib
|
||||
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
import stat
|
||||
|
||||
file = 'semmle/important_secrets'
|
||||
|
||||
|
||||
os.chmod(file, 0o7) # BAD
|
||||
os.chmod(file, 0o77) # BAD
|
||||
os.chmod(file, 0o777) # BAD
|
||||
os.chmod(file, 0o600) # GOOD
|
||||
os.chmod(file, 0o550) # BAD
|
||||
os.chmod(file, stat.S_IRWXU) # GOOD
|
||||
os.chmod(file, stat.S_IWGRP) # BAD
|
||||
os.chmod(file, 400) # BAD -- Decimal format.
|
||||
|
||||
os.open(file, 'w', 0o704) # BAD
|
||||
Reference in New Issue
Block a user