Compare commits

..

9 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
7ec64167ac Convert Ruby qlref tests to inline expectations 2026-06-11 22:32:56 +00:00
copilot-swe-agent[bot]
de281fc00c Initial plan 2026-06-11 22:21:25 +00:00
Asger F
ad18659373 Merge pull request #21796 from mattcosta7/patch-1
Add UseMemoDirective and UseNoMemoDirective classes
2026-06-11 23:01:29 +02:00
Matthew Costabile
923fe2dcb9 Merge branch 'main' into patch-1 2026-06-11 15:19:58 -04:00
Matthew Costabile
2884428b62 Merge branch 'main' into patch-1 2026-05-26 07:16:24 -04:00
Matthew Costabile
e10750b35e Merge branch 'main' into patch-1 2026-05-05 22:09:09 -04:00
Matthew Costabile
18550039f2 Update KnownDirective.expected 2026-05-05 11:06:40 -04:00
Matthew Costabile
0caa483925 change note and test 2026-05-05 13:20:39 +00:00
Matthew Costabile
640b17ec78 Add UseMemoDirective and UseNoMemoDirective classes 2026-05-05 07:41:36 -04:00
153 changed files with 1089 additions and 994 deletions

View File

@@ -2,7 +2,7 @@
### Minor Analysis Improvements
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, include regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a sha1 or sha256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
## 0.4.36

View File

@@ -2,4 +2,4 @@
### Minor Analysis Improvements
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, include regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a sha1 or sha256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.

View File

@@ -15,7 +15,7 @@
### Bug Fixes
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check.
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on in minor point, added one more listed resource and added one more recommendation for things to check.
## 0.6.28

View File

@@ -15,4 +15,4 @@
### Bug Fixes
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check.
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on in minor point, added one more listed resource and added one more recommendation for things to check.

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* Added `UseMemoDirective` and `UseNoMemoDirective` classes to model the React compiler directives `"use memo"` and `"use no memo"`.

View File

@@ -435,6 +435,32 @@ module Directive {
UseClientDirective() { this.getDirectiveText() = "use client" }
}
/**
* A `use memo` directive.
*
* Example:
*
* ```
* "use memo";
* ```
*/
class UseMemoDirective extends KnownDirective {
UseMemoDirective() { this.getDirectiveText() = "use memo" }
}
/**
* A `use no memo` directive.
*
* Example:
*
* ```
* "use no memo";
* ```
*/
class UseNoMemoDirective extends KnownDirective {
UseNoMemoDirective() { this.getDirectiveText() = "use no memo" }
}
/**
* A `use cache` directive.
*

View File

@@ -3,14 +3,18 @@
| tst.js:3:1:3:9 | 'bundle'; | bundle |
| tst.js:4:1:4:13 | 'use server'; | use server |
| tst.js:5:1:5:13 | 'use client'; | use client |
| tst.js:6:1:6:12 | 'use cache'; | use cache |
| tst.js:7:1:7:20 | 'use cache: remote'; | use cache: remote |
| tst.js:8:1:8:21 | 'use ca ... ivate'; | use cache: private |
| tst.js:17:3:17:12 | 'use asm'; | use asm |
| tst.js:18:3:18:11 | 'bundle'; | bundle |
| tst.js:19:3:19:15 | 'use server'; | use server |
| tst.js:20:3:20:15 | 'use client'; | use client |
| tst.js:21:3:21:14 | 'use cache'; | use cache |
| tst.js:22:3:22:22 | 'use cache: remote'; | use cache: remote |
| tst.js:23:3:23:23 | 'use ca ... ivate'; | use cache: private |
| tst.js:30:5:30:17 | 'use strict'; | use strict |
| tst.js:6:1:6:11 | 'use memo'; | use memo |
| tst.js:7:1:7:14 | 'use no memo'; | use no memo |
| tst.js:8:1:8:12 | 'use cache'; | use cache |
| tst.js:9:1:9:20 | 'use cache: remote'; | use cache: remote |
| tst.js:10:1:10:21 | 'use ca ... ivate'; | use cache: private |
| tst.js:19:3:19:12 | 'use asm'; | use asm |
| tst.js:20:3:20:11 | 'bundle'; | bundle |
| tst.js:21:3:21:15 | 'use server'; | use server |
| tst.js:22:3:22:15 | 'use client'; | use client |
| tst.js:23:3:23:13 | 'use memo'; | use memo |
| tst.js:24:3:24:16 | 'use no memo'; | use no memo |
| tst.js:25:3:25:14 | 'use cache'; | use cache |
| tst.js:26:3:26:22 | 'use cache: remote'; | use cache: remote |
| tst.js:27:3:27:23 | 'use ca ... ivate'; | use cache: private |
| tst.js:34:5:34:17 | 'use strict'; | use strict |

View File

@@ -3,6 +3,8 @@
'bundle';// and this
'use server';
'use client';
'use memo';
'use no memo';
'use cache';
'use cache: remote';
'use cache: private';
@@ -18,6 +20,8 @@ function f() {
'bundle';
'use server';
'use client';
'use memo';
'use no memo';
'use cache';
'use cache: remote';
'use cache: private';

View File

@@ -2,7 +2,7 @@
### Minor Analysis Improvements
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example `py/clear-text-logging-sensitive-data`) may find more correct results and fewer false positive results after these changes.
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example `py/clear-text-logging-sensitive-data`) may find more correct results and less fewer positive results after these changes.
## 7.1.1

View File

@@ -2,4 +2,4 @@
### Minor Analysis Improvements
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example `py/clear-text-logging-sensitive-data`) may find more correct results and fewer false positive results after these changes.
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example `py/clear-text-logging-sensitive-data`) may find more correct results and less fewer positive results after these changes.

View File

@@ -1 +1,2 @@
experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
query: experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,27 +1,27 @@
require 'zlib'
class TestController < ActionController::Base
gzip_path = params[:path]
gzip_path = params[:path] # $ Source
Zlib::GzipReader.open(gzip_path).read
Zlib::GzipReader.open(gzip_path).read # $ Alert
Zlib::GzipReader.open(gzip_path) do |uncompressedfile|
puts uncompressedfile.read
end
end # $ Alert
Zlib::GzipReader.open(gzip_path) do |uncompressedfile|
uncompressedfile.each do |entry|
puts entry
end
end
uncompressedfile = Zlib::GzipReader.open(gzip_path)
end # $ Alert
uncompressedfile = Zlib::GzipReader.open(gzip_path) # $ Alert
uncompressedfile.each do |entry|
puts entry
end
Zlib::GzipReader.new(File.open(gzip_path, 'rb')).read
Zlib::GzipReader.new(File.open(gzip_path, 'rb')).each do |entry|
Zlib::GzipReader.new(File.open(gzip_path, 'rb')).read # $ Alert
Zlib::GzipReader.new(File.open(gzip_path, 'rb')).each do |entry| # $ Alert
puts entry
end
Zlib::GzipReader.zcat(open(gzip_path))
Zlib::GzipReader.zcat(open(gzip_path)) # $ Alert
end

View File

@@ -1,21 +1,21 @@
require 'zip'
class TestController < ActionController::Base
zipfile_path = params[:path]
zipfile_path = params[:path] # $ Source
Zip::InputStream.open(zipfile_path) do |input|
while (entry = input.get_next_entry)
puts :file_name, entry.name
input
end
end
end # $ Alert
Zip::InputStream.open(zipfile_path) do |input|
input.read
end
input = Zip::InputStream.open(zipfile_path)
end # $ Alert
input = Zip::InputStream.open(zipfile_path) # $ Alert
Zip::File.open(zipfile_path).read "10GB"
Zip::File.open(zipfile_path).extract "10GB", "./"
Zip::File.open(zipfile_path).read "10GB" # $ Alert
Zip::File.open(zipfile_path).extract "10GB", "./" # $ Alert
Zip::File.open(zipfile_path) do |zip_file|
# Handle entries one by one
@@ -25,33 +25,33 @@ class TestController < ActionController::Base
# Extract to file or directory based on name in the archive
entry.extract
# Read into memory
entry.get_input_stream.read
entry.get_input_stream.read # $ Alert
end
end
zip_file = Zip::File.open(zipfile_path)
zip_file.each do |entry|
entry.extract
entry.get_input_stream.read
entry.extract # $ Alert
entry.get_input_stream.read # $ Alert
end
# Find specific entry
Zip::File.open(zipfile_path) do |zip_file|
zip_file.glob('*.xml').each do |entry|
zip_file.read(entry.name)
entry.extract
zip_file.read(entry.name) # $ Alert
entry.extract # $ Alert
end
entry = zip_file.glob('*.csv').first
raise 'File too large when extracted' if entry.size > MAX_SIZE
puts entry.get_input_stream.read
puts entry.get_input_stream.read # $ Alert
end
zip_file = Zip::File.open(zipfile_path)
entry = zip_file.glob('*.csv')
puts entry.get_input_stream.read
puts entry.get_input_stream.read # $ Alert
zip_file = Zip::File.open(zipfile_path)
zip_file.glob('*') do |entry|
entry.get_input_stream.read
entry.get_input_stream.read # $ Alert
end
end

View File

@@ -1 +1,2 @@
experimental/ldap-improper-auth/ImproperLdapAuth.ql
query: experimental/ldap-improper-auth/ImproperLdapAuth.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,7 +2,7 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is used directly as password
# (i.e a remote flow source)
pass = params[:pass]
pass = params[:pass] # $ Source
# BAD: user input is not sanitized
ldap = Net::LDAP.new(
@@ -12,7 +12,7 @@ class FooController < ActionController::Base
auth: {
method: :simple,
username: 'uid=admin,dc=example,dc=com',
password: pass
password: pass # $ Alert
}
)
ldap.bind
@@ -21,14 +21,14 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is used directly as password
# (i.e a remote flow source)
pass = params[:pass]
pass = params[:pass] # $ Source
# BAD: user input is not sanitized
ldap = Net::LDAP.new
ldap.host = your_server_ip_address
ldap.encryption(:method => :simple_tls)
ldap.port = 639
ldap.auth "admin", pass
ldap.auth "admin", pass # $ Alert
ldap.bind
end
end
@@ -56,4 +56,4 @@ class BarController < ApplicationController
}
)
end
end
end

View File

@@ -1 +1,2 @@
experimental/insecure-randomness/InsecureRandomness.ql
query: experimental/insecure-randomness/InsecureRandomness.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -3,7 +3,7 @@ require 'securerandom'
def generate_password_1(length)
chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + ['!', '@', '#', '$', '%']
# BAD: rand is not cryptographically secure
password = (1..length).collect { chars[rand(chars.size)] }.join
password = (1..length).collect { chars[rand(chars.size)] }.join # $ Alert
end
def generate_password_2(length)
@@ -16,4 +16,4 @@ def generate_password_2(length)
end
password = generate_password_1(10)
password = generate_password_2(10)
password = generate_password_2(10)

View File

@@ -2,11 +2,11 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is used directly as DN
# (i.e a remote flow source)
dc = params[:dc]
dc = params[:dc] # $ Source
# A string tainted by user input is used directly as search filter or attribute
# (i.e a remote flow source)
name = params[:user_name]
name = params[:user_name] # $ Source
# LDAP Connection
ldap = Net::LDAP.new(
@@ -22,20 +22,20 @@ class FooController < ActionController::Base
# BAD: user input is used as DN
# where dc is unsanitized
ldap.search(base: "ou=people,dc=#{dc},dc=com", filter: "cn=George", attributes: [""])
ldap.search(base: "ou=people,dc=#{dc},dc=com", filter: "cn=George", attributes: [""]) # $ Alert
# BAD: user input is used as search filter
# where name is unsanitized
ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=#{name}", attributes: [""])
ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=#{name}", attributes: [""]) # $ Alert
# BAD: user input is used as attribute
# where name is unsanitized
ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=George", attributes: [name])
ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=George", attributes: [name]) # $ Alert
# BAD: user input is used as search filter
# where name is unsanitized
filter = Net::LDAP::Filter.eq('cn', name)
ldap.search(base: "ou=people,dc=example,dc=com", filter: filter, attributes: [""])
ldap.search(base: "ou=people,dc=example,dc=com", filter: filter, attributes: [""]) # $ Alert
# GOOD: user input is not used in the LDAP query
result = ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=George", attributes: [""])
@@ -63,4 +63,4 @@ class BarController < ApplicationController
end
result = ldap.search(base: "ou=people,dc=example,dc=com", filter: "cn=#{name}", attributes: [""])
end
end
end

View File

@@ -1 +1,2 @@
experimental/ldap-injection/LdapInjection.ql
query: experimental/ldap-injection/LdapInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,7 +2,7 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is inserted into a template
# (i.e a remote flow source)
name = params[:name]
name = params[:name] # $ Source
# Template with the source
bad_text = "
@@ -12,11 +12,11 @@ class FooController < ActionController::Base
# BAD: user input is evaluated
# where name is unsanitized
template = ERB.new(bad_text).result(binding)
template = ERB.new(bad_text).result(binding) # $ Alert
# BAD: user input is evaluated
# where name is unsanitized
render inline: bad_text
render inline: bad_text # $ Alert
# Template with the source
good_text = "

View File

@@ -2,7 +2,7 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is inserted into a template
# (i.e a remote flow source)
name = params[:name]
name = params[:name] # $ Source
# Template with the source (no sanitizer)
bad_text = "
@@ -11,7 +11,7 @@ class FooController < ActionController::Base
" % name
# BAD: renders user input
# where text is unsanitized
Slim::Template.new{ bad_text }.render
Slim::Template.new{ bad_text }.render # $ Alert
# Template with the source (no sanitizer)
bad2_text = "
@@ -20,7 +20,7 @@ class FooController < ActionController::Base
"
# BAD: renders user input
# where text is unsanitized
Slim::Template.new{ bad2_text }.render
Slim::Template.new{ bad2_text }.render # $ Alert
# Template with the source (no render)
good_text = "
@@ -64,4 +64,4 @@ class BarController < ApplicationController
" % name2
template_bar1 = Slim::Template.new{ text_bar2 }.render
end
end
end

View File

@@ -1 +1,2 @@
experimental/template-injection/TemplateInjection.ql
query: experimental/template-injection/TemplateInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,7 +2,7 @@ require 'libxml'
class FooController < ActionController::Base
def libxml_handler(event:, context:)
name = params[:user_name]
name = params[:user_name] # $ Source
xml = <<-XML
<root>
@@ -18,13 +18,13 @@ class FooController < ActionController::Base
results1 = doc.find_first('//foo')
# BAD: XPath query is constructed from user input
results2 = doc.find_first("//#{name}")
results2 = doc.find_first("//#{name}") # $ Alert
# GOOD: XPath query is not constructed from user input
results3 = doc.find('//foo')
# BAD: XPath query is constructed from user input
results4 = doc.find("//#{name}")
results4 = doc.find("//#{name}") # $ Alert
end
end

View File

@@ -2,7 +2,7 @@ require 'nokogiri'
class FooController < ActionController::Base
def nokogiri_handler(event:, context:)
name = params[:user_name]
name = params[:user_name] # $ Source
xml = <<-XML
<root>
@@ -18,19 +18,19 @@ class FooController < ActionController::Base
results1 = doc.at('//foo')
# BAD: XPath query is constructed from user input
results2 = doc.at("//#{name}")
results2 = doc.at("//#{name}") # $ Alert
# GOOD: XPath query is not constructed from user input
results3 = doc.xpath('//foo')
# BAD: XPath query is constructed from user input
results4 = doc.xpath("//#{name}")
results4 = doc.xpath("//#{name}") # $ Alert
# GOOD: XPath query is not constructed from user input
results5 = doc.at_xpath('//foo')
# BAD: XPath query is constructed from user input
results6 = doc.at_xpath("//#{name}")
results6 = doc.at_xpath("//#{name}") # $ Alert
# GOOD: XPath query is not constructed from user input
doc.xpath('//foo').each do |element|
@@ -38,7 +38,7 @@ class FooController < ActionController::Base
end
# BAD: XPath query constructed from user input
doc.xpath("//#{name}").each do |element|
doc.xpath("//#{name}").each do |element| # $ Alert
puts element.text
end
@@ -48,7 +48,7 @@ class FooController < ActionController::Base
end
# BAD: XPath query constructed from user input
doc.search("//#{name}").each do |element|
doc.search("//#{name}").each do |element| # $ Alert
puts element.text
end
end
@@ -85,4 +85,4 @@ class BarController < ActionController::Base
results9 = doc.at_xpath("//#{safe_name}")
end
end
end

View File

@@ -2,7 +2,7 @@ require 'rexml'
class FooController < ActionController::Base
def rexml_handler(event:, context:)
name = params[:user_name]
name = params[:user_name] # $ Source
xml = <<-XML
<root>
@@ -18,13 +18,13 @@ class FooController < ActionController::Base
results1 = REXML::XPath.first(doc, "//foo")
# BAD: XPath query is constructed from user input
results2 = REXML::XPath.first(doc, "//#{name}")
results2 = REXML::XPath.first(doc, "//#{name}") # $ Alert
# GOOD: XPath query is not constructed from user input
results3 = REXML::XPath.match(doc, "//foo", nil)
# BAD: XPath query is constructed from user input
results4 = REXML::XPath.match(doc, "//#{name}", nil)
results4 = REXML::XPath.match(doc, "//#{name}", nil) # $ Alert
# GOOD: XPath query is not constructed from user input
REXML::XPath.each(doc, "//foo") do |element|
@@ -32,7 +32,7 @@ class FooController < ActionController::Base
end
# BAD: XPath query constructed from user input
REXML::XPath.each(doc, "//#{name}") do |element|
REXML::XPath.each(doc, "//#{name}") do |element| # $ Alert
puts element.text
end
end
@@ -66,4 +66,4 @@ class BarController < ActionController::Base
results6 = REXML::XPath.match(doc, "//#{safe_name}", nil)
end
end
end

View File

@@ -1 +1,2 @@
experimental/xpath-injection/XpathInjection.ql
query: experimental/xpath-injection/XpathInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1 +1,2 @@
experimental/cwe-022-zipslip/ZipSlip.ql
query: experimental/cwe-022-zipslip/ZipSlip.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -5,9 +5,9 @@ class TestController < ActionController::Base
def tarReaderUnsafe
path = params[:path]
file_stream = IO.new(IO.sysopen(path))
tarfile = Gem::Package::TarReader.new(file_stream)
tarfile = Gem::Package::TarReader.new(file_stream) # $ Source
tarfile.each do |entry|
::File.open(entry.full_name, "wb") do |os|
::File.open(entry.full_name, "wb") do |os| # $ Alert
entry.read
end
end
@@ -17,9 +17,9 @@ class TestController < ActionController::Base
def tarReaderBlockUnsafe
path = params[:path]
file_stream = IO.new(IO.sysopen(path))
Gem::Package::TarReader.new(file_stream) do |tarfile|
Gem::Package::TarReader.new(file_stream) do |tarfile| # $ Source
tarfile.each_entry do |entry|
::File.open(entry.full_name, "wb") do |os|
::File.open(entry.full_name, "wb") do |os| # $ Alert
entry.read
end
end
@@ -43,8 +43,8 @@ class TestController < ActionController::Base
# BAD
def zipFileUnsafe
path = params[:path]
Zip::File.open(path).each do |entry|
File.open(entry.name, "wb") do |os|
Zip::File.open(path).each do |entry| # $ Source
File.open(entry.name, "wb") do |os| # $ Alert
entry.read
end
end
@@ -53,9 +53,9 @@ class TestController < ActionController::Base
# BAD
def zipFileBlockUnsafe
path = params[:path]
Zip::File.open(path) do |zip_file|
Zip::File.open(path) do |zip_file| # $ Source
zip_file.each do |entry|
File.open(entry.name, "wb") do |os|
File.open(entry.name, "wb") do |os| # $ Alert
entry.read
end
end
@@ -87,7 +87,7 @@ class TestController < ActionController::Base
end
def get_compressed_file_stream(compressed_file_path)
gzip = Zlib::GzipReader.open(compressed_file_path)
gzip = Zlib::GzipReader.open(compressed_file_path) # $ Source
yield(gzip)
end
@@ -97,7 +97,7 @@ class TestController < ActionController::Base
get_compressed_file_stream(path) do |compressed_file|
compressed_file.each do |entry|
entry_path = entry.full_name
::File.open(entry_path, 'wb') do |os|
::File.open(entry_path, 'wb') do |os| # $ Alert
entry.read
end
end
@@ -120,10 +120,10 @@ class TestController < ActionController::Base
def gzipReaderUnsafeNewInstance
path = params[:path]
File.open(path, 'rb') do |f|
gz = Zlib::GzipReader.new(f)
gz = Zlib::GzipReader.new(f) # $ Source
gz.each do |entry|
entry_path = entry.full_name
::File.open(entry_path, 'wb') do |os|
::File.open(entry_path, 'wb') do |os| # $ Alert
entry.read
end
end

View File

@@ -1 +1,2 @@
experimental/cwe-176/UnicodeBypassValidation.ql
query: experimental/cwe-176/UnicodeBypassValidation.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -4,35 +4,35 @@ require 'cgi'
class UnicodeNormalizationOKController < ActionController::Base
def unicodeNormalize
unicode_input = params[:unicode_input]
normalized_nfkc = unicode_input.unicode_normalize(:nfkc) # $ MISSING:result=OK
normalized_nfc = unicode_input.unicode_normalize(:nfc) # $ MISSING:result=OK
unicode_input = params[:unicode_input] # $ Source
normalized_nfkc = unicode_input.unicode_normalize(:nfkc) # $ Alert // $ MISSING:result=OK
normalized_nfc = unicode_input.unicode_normalize(:nfc) # $ Alert // $ MISSING:result=OK
end
end
class UnicodeNormalizationStrManipulationController < ActionController::Base
def unicodeNormalize
unicode_input = params[:unicode_input]
unicode_input_manip = unicode_input.sub(/[aeiou]/, "*")
normalized_nfkc = unicode_input_manip.unicode_normalize(:nfkc) # $ result=BAD
normalized_nfc = unicode_input_manip.unicode_normalize(:nfc) # $ result=BAD
unicode_input = params[:unicode_input] # $ Source
unicode_input_manip = unicode_input.sub(/[aeiou]/, "*") # $ Source
normalized_nfkc = unicode_input_manip.unicode_normalize(:nfkc) # $ Alert // $ result=BAD
normalized_nfc = unicode_input_manip.unicode_normalize(:nfc) # $ Alert // $ result=BAD
end
end
class UnicodeNormalizationHtMLEscapeController < ActionController::Base
def unicodeNormalize
unicode_input = params[:unicode_input]
unicode_html_safe = html_escape(unicode_input)
normalized_nfkc = unicode_html_safe.unicode_normalize(:nfkc) # $ result=BAD
normalized_nfc = unicode_html_safe.unicode_normalize(:nfc) # $ result=BAD
unicode_input = params[:unicode_input] # $ Source
unicode_html_safe = html_escape(unicode_input) # $ Source
normalized_nfkc = unicode_html_safe.unicode_normalize(:nfkc) # $ Alert // $ result=BAD
normalized_nfc = unicode_html_safe.unicode_normalize(:nfc) # $ Alert // $ result=BAD
end
end
class UnicodeNormalizationCGIHtMLEscapeController < ActionController::Base
def unicodeNormalize
unicode_input = params[:unicode_input]
unicode_html_safe = CGI.escapeHTML(unicode_input).html_safe
normalized_nfkc = unicode_html_safe.unicode_normalize(:nfkd) # $ result=BAD
normalized_nfc = unicode_html_safe.unicode_normalize(:nfd) # $ result=BAD
unicode_input = params[:unicode_input] # $ Source
unicode_html_safe = CGI.escapeHTML(unicode_input).html_safe # $ Source
normalized_nfkc = unicode_html_safe.unicode_normalize(:nfkd) # $ Alert // $ result=BAD
normalized_nfc = unicode_html_safe.unicode_normalize(:nfd) # $ Alert // $ result=BAD
end
end

View File

@@ -1 +1,2 @@
experimental/cwe-347/EmptyJWTSecret.ql
query: experimental/cwe-347/EmptyJWTSecret.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -6,10 +6,10 @@ payload = { foo: 'bar' }
token1 = JWT.encode({ foo: 'bar' }, "secret", 'none')
# BAD: the secret used is empty
token2 = JWT.encode({ foo: 'bar' }, nil, 'HS256')
token2 = JWT.encode({ foo: 'bar' }, nil, 'HS256') # $ Alert[rb/jwt-empty-secret-or-algorithm]
# BAD: the secret used is empty
token3 = JWT.encode({ foo: 'bar' }, "", 'HS256')
token3 = JWT.encode({ foo: 'bar' }, "", 'HS256') # $ Alert[rb/jwt-empty-secret-or-algorithm]
# GOOD: the token is signed
token4 = JWT.encode({ foo: 'bar' }, "secret", 'HS256')
token4 = JWT.encode({ foo: 'bar' }, "secret", 'HS256')

View File

@@ -1 +1,2 @@
experimental/cwe-347/MissingJWTVerification.ql
query: experimental/cwe-347/MissingJWTVerification.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -3,22 +3,22 @@ require 'jwt'
payload = { foo: 'bar' }
# Unsecure token
token_without_signature = JWT.encode(payload, nil, 'none')
token_without_signature = JWT.encode(payload, nil, 'none') # $ Alert[rb/jwt-empty-secret-or-algorithm]
# Secure token
token = JWT.encode(payload, "secret", 'HS256')
# BAD: it does not verify
decoded_token1 = JWT.decode(token_without_signature, nil, false, algorithm: 'HS256')
decoded_token1 = JWT.decode(token_without_signature, nil, false, algorithm: 'HS256') # $ Alert[rb/jwt-missing-verification]
# BAD: it's using none
decoded_token3 = JWT.decode(token_without_signature, secret, true, algorithm: 'none')
decoded_token3 = JWT.decode(token_without_signature, secret, true, algorithm: 'none') # $ Alert[rb/jwt-missing-verification]
# BAD: it's using none
decoded_token4 = JWT.decode(token_without_signature, secret, true, { algorithm: 'none' })
decoded_token4 = JWT.decode(token_without_signature, secret, true, { algorithm: 'none' }) # $ Alert[rb/jwt-missing-verification]
# GOOD: it does verify
decoded_token5 = JWT.decode(token, secret, 'HS256')
# GOOD: it does verify
decoded_token2 = JWT.decode(token,secret)
decoded_token2 = JWT.decode(token,secret)

View File

@@ -1 +1,2 @@
experimental/cwe-502/UnsafeYamlDeserialization.ql
query: experimental/cwe-502/UnsafeYamlDeserialization.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -7,15 +7,15 @@ require "yaml"
class UsersController < ActionController::Base
# BAD before psych version 4.0.0 and
def route1
yaml_data = params[:key]
object = Psych.load yaml_data
yaml_data = params[:key] # $ Source
object = Psych.load yaml_data # $ Alert
object = Psych.load_file yaml_data
end
# GOOD In psych version 4.0.0 and above
def route2
yaml_data = params[:key]
object = Psych.load yaml_data
yaml_data = params[:key] # $ Source
object = Psych.load yaml_data # $ Alert
object = Psych.load_file yaml_data
end
@@ -29,14 +29,14 @@ class UsersController < ActionController::Base
# BAD
def route4
yaml_data = params[:key]
object = Psych.unsafe_load(yaml_data)
object = Psych.unsafe_load_file(yaml_data)
object = Psych.load_stream(yaml_data)
yaml_data = params[:key] # $ Source
object = Psych.unsafe_load(yaml_data) # $ Alert
object = Psych.unsafe_load_file(yaml_data) # $ Alert
object = Psych.load_stream(yaml_data) # $ Alert
parse_output = Psych.parse_stream(yaml_data)
object = parse_output.to_ruby
object = Psych.parse(yaml_data).to_ruby
object = Psych.parse_file(yaml_data).to_ruby
object = parse_output.to_ruby # $ Alert
object = Psych.parse(yaml_data).to_ruby # $ Alert
object = Psych.parse_file(yaml_data).to_ruby # $ Alert
parsed_yaml = Psych.parse_stream(yaml_data)
parsed_yaml.children.each do |child|
object = child.to_ruby
@@ -46,7 +46,7 @@ class UsersController < ActionController::Base
end
object = parsed_yaml.children.first.to_ruby
content = parsed_yaml.children[0].children[0].children
object = parsed_yaml.to_ruby[0]
object = parsed_yaml.to_ruby[0] # $ Alert
object = content.to_ruby[0]
object = Psych.parse(yaml_data).children[0].to_ruby
end
@@ -58,18 +58,18 @@ class UsersController < ActionController::Base
end
def stdin
object = YAML.load $stdin.read
object = YAML.load $stdin.read # $ Alert
# STDIN
object = YAML.load STDIN.gets
object = YAML.load STDIN.gets # $ Alert
# ARGF
object = YAML.load ARGF.read
object = YAML.load ARGF.read # $ Alert
# Kernel.gets
object = YAML.load gets
object = YAML.load gets # $ Alert
# Kernel.readlines
object = YAML.load readlines
object = YAML.load readlines # $ Alert
end
end

View File

@@ -1 +1,2 @@
experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql
query: experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,39 +1,39 @@
class ExampleController < ActionController::Base
# Should find
def example_action
if request.get?
if request.get? # $ Alert
Resource.find(id: params[:example_id])
end
end
# Should find
def other_action
method = request.env['REQUEST_METHOD']
if method == "GET"
method = request.env['REQUEST_METHOD'] # $ Source
if method == "GET" # $ Alert
Resource.find(id: params[:id])
end
end
# Should find
def foo
method = request.request_method
if method == "GET"
method = request.request_method # $ Source
if method == "GET" # $ Alert
Resource.find(id: params[:id])
end
end
# Should find
def bar
method = request.method
if method == "GET"
method = request.method # $ Source
if method == "GET" # $ Alert
Resource.find(id: params[:id])
end
end
# Should find
def baz
method = request.raw_request_method
if method == "GET"
method = request.raw_request_method # $ Source
if method == "GET" # $ Alert
Resource.find(id: params[:id])
end
end
@@ -48,15 +48,15 @@ class ExampleController < ActionController::Base
# Should find
def foobarbaz
method = request.request_method_symbol
if method == :GET
method = request.request_method_symbol # $ Source
if method == :GET # $ Alert
Resource.find(id: params[:id])
end
end
# Should find
def resource_action
case request.env['REQUEST_METHOD']
case request.env['REQUEST_METHOD'] # $ Alert
when "GET"
Resource.find(id: params[:id])
when "POST"
@@ -114,4 +114,4 @@ class NotAController
end
class Resource < ActiveRecord::Base
end
end

View File

@@ -1 +1,2 @@
experimental/weak-params/WeakParams.ql
query: experimental/weak-params/WeakParams.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,22 +2,22 @@ class TestController < ActionController::Base
# Should catch
def create
TestObject.create(foo: request.request_parameters[:foo])
TestObject.create(foo: request.request_parameters[:foo]) # $ Alert
end
# Should catch
def create_query
TestObject.create(foo: request.query_parameters[:foo])
TestObject.create(foo: request.query_parameters[:foo]) # $ Alert
end
# Should catch
def update_unsafe
TestObject.update(foo: request.POST[:foo])
TestObject.update(foo: request.POST[:foo]) # $ Alert
end
# Should catch
def update_unsafe_get
TestObject.update(foo: request.GET[:foo])
TestObject.update(foo: request.GET[:foo]) # $ Alert
end
# Should not catch
@@ -37,4 +37,4 @@ class TestController < ActionController::Base
end
class TestObject < ActiveRecord::Base
end
end

View File

@@ -1 +1,2 @@
experimental/performance/UseDetect.ql
query: experimental/performance/UseDetect.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,14 +2,14 @@
class DetectTest
def test
# These are bad
[].select { |i| true }.first
[].select { |i| true }.last
[].select { |i| true }[0]
[].select { |i| true }[-1]
[].filter { |i| true }.first
[].find_all { |i| true }.last
[].select { |i| true }.first # $ Alert
[].select { |i| true }.last # $ Alert
[].select { |i| true }[0] # $ Alert
[].select { |i| true }[-1] # $ Alert
[].filter { |i| true }.first # $ Alert
[].find_all { |i| true }.last # $ Alert
selection1 = [].select { |i| true }
selection1.first
selection1.first # $ Alert
# These are good
[].select("").first # Selecting a string

View File

@@ -1 +1,2 @@
queries/security/cwe-020/IncompleteHostnameRegExp.ql
query: queries/security/cwe-020/IncompleteHostnameRegExp.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,6 +1,6 @@
UNSAFE_REGEX1 = /(www|beta).example.com\//
UNSAFE_REGEX2 = Regexp.compile("(www|beta).example.com/")
UNSAFE_REGEX3 = Regexp.new("(www|beta).example.com/")
UNSAFE_REGEX1 = /(www|beta).example.com\// # $ Alert
UNSAFE_REGEX2 = Regexp.compile("(www|beta).example.com/") # $ Alert
UNSAFE_REGEX3 = Regexp.new("(www|beta).example.com/") # $ Alert
SAFE_REGEX = /(www|beta)\.example\.com\//
def unsafe

View File

@@ -1,31 +1,31 @@
def foo
/^http:\/\/example.com/; # OK
/^http:\/\/test.example.com/; # NOT OK
/^http:\/\/test.example.com/; # $ Alert // NOT OK
/^http:\/\/test\.example.com/; # OK
/^http:\/\/test.example.net/; # NOT OK
/^http:\/\/test.(example-a|example-b).com/; # NOT OK
/^http:\/\/(.+).example.com\//; # NOT OK
/^http:\/\/test.example.net/; # $ Alert // NOT OK
/^http:\/\/test.(example-a|example-b).com/; # $ Alert // NOT OK
/^http:\/\/(.+).example.com\//; # $ Alert // NOT OK
/^http:\/\/(\.+)\.example.com/; # OK
/^http:\/\/(?:.+)\.test\.example.com\//; # NOT OK
/^http:\/\/test.example.com\/(?:.*)/; # OK
Regexp.new("^http://test.example.com"); # NOT OK
if (s.match("^http://test.example.com")); end # NOT OK
/^http:\/\/(?:.+)\.test\.example.com\//; # $ Alert // NOT OK
/^http:\/\/test.example.com\/(?:.*)/; # $ Alert // OK
Regexp.new("^http://test.example.com"); # $ Alert // NOT OK
if (s.match("^http://test.example.com")); end # $ Alert // NOT OK
Regexp.new(id(id(id("^http://test.example.com")))); # NOT OK
Regexp.new(id(id(id("^http://test.example.com")))); # $ Alert // NOT OK
Regexp.new(`test.example.com$`); # NOT OK
Regexp.new(`test.example.com$`); # $ Alert // NOT OK
hostname = '^test.example.com'; # NOT OK
Regexp.new("#{hostname}$");
hostname = '^test.example.com'; # $ Alert // NOT OK
Regexp.new("#{hostname}$"); # $ Alert
domain = { hostname: 'test.example.com$' }; # NOT OK
domain = { hostname: 'test.example.com$' }; # $ Alert // NOT OK
Regexp.new(domain[:hostname]);
convert1({ hostname: 'test.example.com$' }); # NOT OK
convert1({ hostname: 'test.example.com$' }); # $ Alert // NOT OK
domains = [ { hostname: 'test.example.com$' } ]; # NOT OK - but not flagged due to limitations of TypeTracking.
@@ -34,18 +34,18 @@ def foo
domains.map{ |d| convert2(d) };
/^(.+\.(?:example-a|example-b)\.com)\//; # NOT OK
/^(https?:)?\/\/((service|www).)?example.com(?=$|\/)/; # NOT OK
/^(http|https):\/\/www.example.com\/p\/f\//; # NOT OK
/^(http:\/\/sub.example.com\/)/i; # NOT OK
/^https?:\/\/api.example.com/; # NOT OK
Regexp.new('^http://localhost:8000|' + "^https?://.+\\.example\\.com/"); # NOT OK
/^(https?:)?\/\/((service|www).)?example.com(?=$|\/)/; # $ Alert // NOT OK
/^(http|https):\/\/www.example.com\/p\/f\//; # $ Alert // NOT OK
/^(http:\/\/sub.example.com\/)/i; # $ Alert // NOT OK
/^https?:\/\/api.example.com/; # $ Alert // NOT OK
Regexp.new('^http://localhost:8000|' + "^https?://.+\\.example\\.com/"); # $ Alert // NOT OK
Regexp.new("^http[s]?:\/\/?sub1\\.sub2\\.example\\.com\/f\/(.+)"); # NOT OK
/^https:\/\/[a-z]*.example.com$/; # NOT OK
Regexp.compile('^protos?://(localhost|.+.example.net|.+.example-a.com|.+.example-b.com|.+.example.internal)'); # NOT OK
/^https:\/\/[a-z]*.example.com$/; # $ Alert // NOT OK
Regexp.compile('^protos?://(localhost|.+.example.net|.+.example-a.com|.+.example-b.com|.+.example.internal)'); # $ Alert // NOT OK
/^(example.dev|example.com)/; # OK
Regexp.new('^http://localhost:8000|' + "^https?://.+.example\\.com/"); # NOT OK
Regexp.new('^http://localhost:8000|' + "^https?://.+.example\\.com/"); # $ Alert // NOT OK
primary = 'example.com$';
Regexp.new('test.' + primary); # NOT OK, but not detected
@@ -56,7 +56,7 @@ def foo
/^http:\/\/(..|...)\.example\.com\/index\.html/; # OK, wildcards are intentional
/^http:\/\/.\.example\.com\/index\.html/; # OK, the wildcard is intentional
/^(foo.example\.com|whatever)$/; # kinda OK - one disjunction doesn't even look like a hostname
/^(foo.example\.com|whatever)$/; # $ Alert // kinda OK - one disjunction doesn't even look like a hostname
end
def id(e); return e; end
def convert1(domain)
@@ -78,4 +78,4 @@ class B
end
end
B.match?("^http://test.example.com") # NOT OK
B.match?("^http://test.example.com") # $ Alert // NOT OK

View File

@@ -1 +1,2 @@
queries/security/cwe-020/IncompleteUrlSubstringSanitization.ql
query: queries/security/cwe-020/IncompleteUrlSubstringSanitization.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,23 +1,23 @@
def test (x)
x.index("internal") != nil; # NOT OK, but not flagged
x.index("localhost") != nil; # NOT OK, but not flagged
x.index("secure.com") != nil; # NOT OK
x.index("secure.net") != nil; # NOT OK
x.index(".secure.com") != nil; # NOT OK
x.index("secure.com") != nil; # $ Alert // NOT OK
x.index("secure.net") != nil; # $ Alert // NOT OK
x.index(".secure.com") != nil; # $ Alert // NOT OK
x.index("sub.secure.") != nil; # NOT OK, but not flagged
x.index(".sub.secure.") != nil; # NOT OK, but not flagged
x.index("secure.com") === nil; # NOT OK
x.index("secure.com") === 0; # NOT OK
x.index("secure.com") >= 0; # NOT OK
x.index("secure.com") === nil; # $ Alert // NOT OK
x.index("secure.com") === 0; # $ Alert // NOT OK
x.index("secure.com") >= 0; # $ Alert // NOT OK
x.start_with?("https://secure.com"); # NOT OK
x.end_with?("secure.com"); # NOT OK
x.start_with?("https://secure.com"); # $ Alert // NOT OK
x.end_with?("secure.com"); # $ Alert // NOT OK
x.end_with?(".secure.com"); # OK
x.start_with?("secure.com/"); # OK
x.index("secure.com/") === 0; # OK
x.include?("secure.com"); # NOT OK
x.include?("secure.com"); # $ Alert // NOT OK
x.index("#") != nil; # OK
x.index(":") != nil; # OK
@@ -29,9 +29,9 @@ def test (x)
x.index("some/path") != nil; # OK
x.index("/index.html") != nil; # OK
x.index(":template:") != nil; # OK
x.index("https://secure.com") != nil; # NOT OK
x.index("https://secure.com:443") != nil; # NOT OK
x.index("https://secure.com/") != nil; # NOT OK
x.index("https://secure.com") != nil; # $ Alert // NOT OK
x.index("https://secure.com:443") != nil; # $ Alert // NOT OK
x.index("https://secure.com/") != nil; # $ Alert // NOT OK
x.index(".cn") != nil; # NOT OK, but not flagged
x.index(".jpg") != nil; # OK
@@ -49,28 +49,28 @@ def test (x)
x.index("tar.gz") + offset; # OK
x.index("tar.gz") - offset; # OK
x.index("https://example.internal") != nil; # NOT OK
x.index("https://example.internal") != nil; # $ Alert // NOT OK
x.index("https://") != nil; # OK
x.start_with?("https://example.internal"); # NOT OK
x.index('https://example.internal.org') != 0; # NOT OK
x.index('https://example.internal.org') === 0; # NOT OK
x.end_with?("internal.com"); # NOT OK
x.start_with?("https://example.internal"); # $ Alert // NOT OK
x.index('https://example.internal.org') != 0; # $ Alert // NOT OK
x.index('https://example.internal.org') === 0; # $ Alert // NOT OK
x.end_with?("internal.com"); # $ Alert // NOT OK
x.start_with?("https://example.internal:80"); # OK
x.index("secure.com") != nil; # NOT OK
x.index("secure.com") === nil; # OK
!(x.index("secure.com") != nil); # OK
!x.include?("secure.com"); # OK
x.index("secure.com") != nil; # $ Alert // NOT OK
x.index("secure.com") === nil; # $ Alert // OK
!(x.index("secure.com") != nil); # $ Alert // OK
!x.include?("secure.com"); # $ Alert // OK
if !x.include?("secure.com") # NOT OK
if !x.include?("secure.com") # $ Alert // NOT OK
else
doSomeThingWithTrustedURL(x);
end
x.start_with?("https://secure.com/foo/bar"); # OK - a forward slash after the domain makes prefix checks safe.
x.index("https://secure.com/foo/bar") >= 0 # NOT OK - the url can be anywhere in the string.
x.index("https://secure.com") >= 0 # NOT OK
x.index("https://secure.com/foo/bar-baz") >= 0 # NOT OK - the url can be anywhere in the string.
x.index("https://secure.com/foo/bar") >= 0 # $ Alert // NOT OK - the url can be anywhere in the string.
x.index("https://secure.com") >= 0 # $ Alert // NOT OK
x.index("https://secure.com/foo/bar-baz") >= 0 # $ Alert // NOT OK - the url can be anywhere in the string.
end

View File

@@ -1 +1,2 @@
queries/security/cwe-020/MissingFullAnchor.ql
query: queries/security/cwe-020/MissingFullAnchor.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,17 +1,17 @@
class Foobar
def foo1(name)
raise Blabity, 'Invalid thing' if name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # NOT OK
def foo1(name) # $ Source
raise Blabity, 'Invalid thing' if name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # $ Alert // NOT OK
end
def foo2(name)
raise Blabity, 'Invalid thing' unless name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # NOT OK
def foo2(name) # $ Source
raise Blabity, 'Invalid thing' unless name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # $ Alert // NOT OK
end
def foo3(name)
raise Blabity, 'Invalid thing' unless name !~ /\A[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*\z/ # OK
end
def foo4(name)
raise Blabity, 'Invalid thing' unless not name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # NOT OK
def foo4(name) # $ Source
raise Blabity, 'Invalid thing' unless not name !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/ # $ Alert // NOT OK
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-020/MissingRegExpAnchor.ql
query: queries/security/cwe-020/MissingRegExpAnchor.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,11 +1,11 @@
/www\.example\.com/ # BAD
/www\.example\.com/ # $ Alert // BAD
/^www\.example\.com$/ # BAD: uses end-of-line anchors rather than end-of-string anchors
/\Awww\.example\.com\z/ # GOOD
/foo\.bar/ # GOOD
/https?:\/\/good\.com/ # BAD
/^https?:\/\/good\.com/ # BAD: missing end-of-string anchor
/https?:\/\/good\.com/ # $ Alert // BAD
/^https?:\/\/good\.com/ # $ Alert // BAD: missing end-of-string anchor
/(^https?:\/\/good1\.com)|(^https?:#good2\.com)/ # BAD: missing end-of-string anchor
/bar/ # GOOD
@@ -16,40 +16,40 @@ foo.gsub!(/www\.example\.com/, "bar") # GOOD
foo.sub!(/www\.example\.com/, "bar") # GOOD
/^a|/
/^a|b/ # BAD
/^a|b/ # $ Alert // BAD
/a|^b/
/^a|^b/
/^a|b|c/ # BAD
/^a|b|c/ # $ Alert // BAD
/a|^b|c/
/a|b|^c/
/^a|^b|c/
/(^a)|b/
/^a|(b)/ # BAD
/^a|(b)/ # $ Alert // BAD
/^a|(^b)/
/^(a)|(b)/ # BAD
/^(a)|(b)/ # $ Alert // BAD
/a|b$/ # BAD
/a|b$/ # $ Alert // BAD
/a$|b/
/a$|b$/
/a|b|c$/ # BAD
/a|b|c$/ # $ Alert // BAD
/a|b$|c/
/a$|b|c/
/a|b$|c$/
/a|(b$)/
/(a)|b$/ # BAD
/(a)|b$/ # $ Alert // BAD
/(a$)|b$/
/(a)|(b)$/ # BAD
/(a)|(b)$/ # $ Alert // BAD
/^good.com|better.com/ # BAD
/^good\.com|better\.com/ # BAD
/^good\\.com|better\\.com/ # BAD
/^good\\\.com|better\\\.com/ # BAD
/^good\\\\.com|better\\\\.com/ # BAD
/^good.com|better.com/ # $ Alert // BAD
/^good\.com|better\.com/ # $ Alert // BAD
/^good\\.com|better\\.com/ # $ Alert // BAD
/^good\\\.com|better\\\.com/ # $ Alert // BAD
/^good\\\\.com|better\\\\.com/ # $ Alert // BAD
/^foo|bar|baz$/ # BAD
/^foo|bar|baz$/ # $ Alert // BAD
/^foo|%/ # OK
REGEXP = /foo/
@@ -57,5 +57,5 @@ REGEXP.match? "http://example.com" # GOOD: the url is the text not the regexp
REGEXP.match "http://example.com" # GOOD: the url is the text not the regexp
"http://example.com".match? REGEXP # GOOD: the url is the text not the regexp
"http://example.com".match REGEXP # GOOD: the url is the text not the regexp
"some text".match? "http://example.com" # BAD
"some text".match "http://example.com" # BAD
"some text".match? "http://example.com" # $ Alert // BAD
"some text".match "http://example.com" # $ Alert // BAD

View File

@@ -1 +1,2 @@
queries/security/cwe-020/OverlyLargeRange.ql
query: queries/security/cwe-020/OverlyLargeRange.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,8 +1,8 @@
overlap1 = /^[0-93-5]$/ # NOT OK
overlap1 = /^[0-93-5]$/ # $ Alert // NOT OK
overlap2 = /[A-ZA-z]/ # NOT OK
overlap2 = /[A-ZA-z]/ # $ Alert // NOT OK
isEmpty = /^[z-a]$/ # NOT OK
isEmpty = /^[z-a]$/ # $ Alert // NOT OK
isAscii = /^[\x00-\x7F]*$/ # OK
@@ -12,22 +12,22 @@ codePoints = /[^\x21-\x7E]|[\[\](){}<>\/%]/ # OK
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/ # OK
smallOverlap = /[0-9a-fA-f]/ # NOT OK
smallOverlap = /[0-9a-fA-f]/ # $ Alert // NOT OK
weirdRange = /[$-`]/ # NOT OK
weirdRange = /[$-`]/ # $ Alert // NOT OK
keywordOperator = /[!\~\*\/%+-<>\^|=&]/ # NOT OK
keywordOperator = /[!\~\*\/%+-<>\^|=&]/ # $ Alert // NOT OK
notYoutube = /youtu\.be\/[a-z1-9.-_]+/ # NOT OK
notYoutube = /youtu\.be\/[a-z1-9.-_]+/ # $ Alert // NOT OK
numberToLetter = /[7-F]/ # NOT OK
numberToLetter = /[7-F]/ # $ Alert // NOT OK
overlapsWithClass1 = /[0-9\d]/ # NOT OK
overlapsWithClass1 = /[0-9\d]/ # $ Alert // NOT OK
overlapsWithClass2 = /[\w,.-?:*+]/ # NOT OK
overlapsWithClass2 = /[\w,.-?:*+]/ # $ Alert // NOT OK
escapes = /[\000-\037\047\134\177-\377]/n # OK - they are escapes
nested = /[a-z&&[^a-c]]/ # OK
overlapsWithNothing = /[\w_%-.]/;
overlapsWithNothing = /[\w_%-.]/; # $ Alert

View File

@@ -1 +1,2 @@
queries/security/cwe-078/KernelOpen.ql
query: queries/security/cwe-078/KernelOpen.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,16 +1,16 @@
class UsersController < ActionController::Base
def create
file = params[:file]
open(file) # BAD
IO.read(file) # BAD
IO.write(file) # BAD
IO.binread(file) # BAD
IO.binwrite(file) # BAD
IO.foreach(file) # BAD
IO.readlines(file) # BAD
URI.open(file) # BAD
file = params[:file] # $ Source
open(file) # $ Alert // BAD
IO.read(file) # $ Alert // BAD
IO.write(file) # $ Alert // BAD
IO.binread(file) # $ Alert // BAD
IO.binwrite(file) # $ Alert // BAD
IO.foreach(file) # $ Alert // BAD
IO.readlines(file) # $ Alert // BAD
URI.open(file) # $ Alert // BAD
IO.read(File.join(file, "")) # BAD - file as first argument to File.join
IO.read(File.join(file, "")) # $ Alert // BAD - file as first argument to File.join
IO.read(File.join("", file)) # GOOD - file path is sanitised by guard
File.open(file).read # GOOD
@@ -23,6 +23,6 @@ class UsersController < ActionController::Base
IO.read(file) # GOOD - file path is sanitised by guard
end
open(file) # BAD - sanity check to verify that file was not mistakenly marked as sanitized
open(file) # $ Alert // BAD - sanity check to verify that file was not mistakenly marked as sanitized
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-078/NonConstantKernelOpen.ql
query: queries/security/cwe-078/NonConstantKernelOpen.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -4,18 +4,18 @@ class UsersController < ActionController::Base
def create
file = params[:file]
open(file) # BAD
IO.read(file) # BAD
IO.write(file) # BAD
IO.binread(file) # BAD
IO.binwrite(file) # BAD
IO.foreach(file) # BAD
IO.readlines(file) # BAD
URI.open(file) # BAD
open(file) # $ Alert // BAD
IO.read(file) # $ Alert // BAD
IO.write(file) # $ Alert // BAD
IO.binread(file) # $ Alert // BAD
IO.binwrite(file) # $ Alert // BAD
IO.foreach(file) # $ Alert // BAD
IO.readlines(file) # $ Alert // BAD
URI.open(file) # $ Alert // BAD
File.open(file).read # GOOD
Kernel.open(file) # BAD
Kernel.open(file) # $ Alert // BAD
File.open(file, "r") # GOOD
@@ -25,7 +25,7 @@ class UsersController < ActionController::Base
Kernel.open("this is #{fine}") # GOOD
Kernel.open("#{this_is} bad") # BAD
Kernel.open("#{this_is} bad") # $ Alert // BAD
open("| #{this_is_an_explicit_command} foo bar") # GOOD
@@ -43,6 +43,6 @@ class UsersController < ActionController::Base
open.where(external: false) # GOOD - an open method is called withoout arguments
open(file) # BAD - sanity check to verify that file was not mistakenly marked as sanitized
open(file) # $ Alert // BAD - sanity check to verify that file was not mistakenly marked as sanitized
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-078/UnsafeShellCommandConstruction.ql
query: queries/security/cwe-078/UnsafeShellCommandConstruction.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,6 +1,5 @@
class Foobar
def foo1(target)
IO.popen("cat #{target}", "w") # NOT OK - everything assumed to be imported...
def foo1(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK - everything assumed to be imported...
end
end

View File

@@ -1,7 +1,7 @@
class Foobar
def foo1(target)
IO.popen("cat #{target}", "w") # NOT OK
def foo1(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK
end
end
require 'sub/other2'
require 'sub/other2'

View File

@@ -1,5 +1,5 @@
class Foobar
def foo1(target)
IO.popen("cat #{target}", "w") # NOT OK
def foo1(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK
end
end
end

View File

@@ -1,10 +1,10 @@
class Foobar
def foo1(target)
IO.popen("cat #{target}", "w") # NOT OK
def foo1(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK
end
def foo2(x)
format = sprintf("cat %s", x) # NOT OK
def foo2(x) # $ Source
format = sprintf("cat %s", x) # $ Alert // NOT OK
IO.popen(format, "w")
end
@@ -12,30 +12,30 @@ class Foobar
File.read(path) # OK
end
def my_exec(cmd, command, myCmd, myCommand, innocent_file_path)
def my_exec(cmd, command, myCmd, myCommand, innocent_file_path) # $ Source
IO.popen("which #{cmd}", "w") # OK - the parameter is named `cmd`, so it's meant to be a command
IO.popen("which #{command}", "w") # OK - the parameter is named `command`, so it's meant to be a command
IO.popen("which #{myCmd}", "w") # OK - the parameter is named `myCmd`, so it's meant to be a command
IO.popen("which #{myCommand}", "w") # OK - the parameter is named `myCommand`, so it's meant to be a command
IO.popen("which #{innocent_file_path}", "w") # NOT OK - the parameter is named `innocent_file_path`, so it's not meant to be a command
IO.popen("which #{innocent_file_path}", "w") # $ Alert // NOT OK - the parameter is named `innocent_file_path`, so it's not meant to be a command
end
def escaped(file_path)
def escaped(file_path) # $ Source
IO.popen("cat #{file_path.shellescape}", "w") # OK - the parameter is escaped
IO.popen("cat #{file_path}", "w") # NOT OK - the parameter is not escaped
IO.popen("cat #{file_path}", "w") # $ Alert // NOT OK - the parameter is not escaped
end
end
require File.join(File.dirname(__FILE__), 'sub', 'other')
class Foobar2
def foo1(target)
IO.popen("cat #{target}", "w") # NOT OK
def foo1(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK
end
def id(x)
IO.popen("cat #{x}", "w") # NOT OK - the parameter is not a constant.
def id(x) # $ Source
IO.popen("cat #{x}", "w") # $ Alert // NOT OK - the parameter is not a constant.
return x
end
@@ -44,27 +44,27 @@ class Foobar2
end
# class methods
def self.foo(target)
IO.popen("cat #{target}", "w") # NOT OK
def self.foo(target) # $ Source
IO.popen("cat #{target}", "w") # $ Alert // NOT OK
end
def arrayJoin(x)
IO.popen(x.join(' '), "w") # NOT OK
def arrayJoin(x) # $ Source
IO.popen(x.join(' '), "w") # $ Alert // NOT OK
IO.popen(["foo", "bar", x].join(' '), "w") # NOT OK
IO.popen(["foo", "bar", x].join(' '), "w") # $ Alert // NOT OK
end
def string_concat(x)
IO.popen("cat " + x, "w") # NOT OK
def string_concat(x) # $ Source
IO.popen("cat " + x, "w") # $ Alert // NOT OK
end
def array_taint (x, y)
def array_taint (x, y) # $ Source
arr = ["cat"]
arr.push(x)
IO.popen(arr.join(' '), "w") # NOT OK
IO.popen(arr.join(' '), "w") # $ Alert // NOT OK
arr2 = ["cat"]
arr2 << y
IO.popen(arr.join(' '), "w") # NOT OK
IO.popen(arr.join(' '), "w") # $ Alert // NOT OK
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-079/ReflectedXSS.ql
query: queries/security/cwe-079/ReflectedXSS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1 +1,2 @@
queries/security/cwe-079/StoredXSS.ql
query: queries/security/cwe-079/StoredXSS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1 +1,2 @@
queries/security/cwe-079/UnsafeHtmlConstruction.ql
query: queries/security/cwe-079/UnsafeHtmlConstruction.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -6,34 +6,34 @@ class BarsController < ApplicationController
end
def user_name
return params[:user_name]
return params[:user_name] # $ Source[rb/reflected-xss]
end
def user_name_memo
@user_name ||= params[:user_name]
@user_name ||= params[:user_name] # $ Source[rb/reflected-xss]
end
def show
@user_website = params[:website]
dt = params[:text]
@user_website = params[:website] # $ Source[rb/reflected-xss]
dt = params[:text] # $ Source[rb/reflected-xss]
@instance_text = dt
@safe_foo = params[:text]
@safe_foo = "safe_foo"
@html_escaped = ERB::Util.html_escape(params[:text])
@header_escaped = ERB::Util.html_escape(cookies[:foo]) # OK - cookies not controllable by 3rd party
response.header["content-type"] = params[:content_type]
response.header["content-type"] = params[:content_type] # $ Alert[rb/reflected-xss]
response.header["x-customer-header"] = params[:bar] # OK - header not relevant to XSS
render "foo/bars/show", locals: { display_text: dt, safe_text: "hello" }
end
def make_safe_html
str = params[:user_name]
str.html_safe
str = params[:user_name] # $ Source[rb/reflected-xss]
str.html_safe # $ Alert[rb/reflected-xss]
translate("welcome", name: params[:user_name]).html_safe # NOT OK - translate preserves taint
t("welcome", name: params[:user_name]).html_safe # NOT OK - t is an alias of translate
translate("welcome", name: params[:user_name]).html_safe # $ Alert[rb/reflected-xss] // NOT OK - translate preserves taint
t("welcome", name: params[:user_name]).html_safe # $ Alert[rb/reflected-xss] // NOT OK - t is an alias of translate
t("welcome_html", name: params[:user_name]).html_safe # OK - t escapes html when key ends in _html
I18n.t("welcome_html", name: params[:user_name]).html_safe # NOT OK - I18n.t does not escape html
I18n.translate("welcome_html", name: params[:user_name]).html_safe # NOT OK - alias
I18n.t("welcome_html", name: params[:user_name]).html_safe # $ Alert[rb/reflected-xss] // NOT OK - I18n.t does not escape html
I18n.translate("welcome_html", name: params[:user_name]).html_safe # $ Alert[rb/reflected-xss] // NOT OK - alias
end
end

View File

@@ -5,7 +5,7 @@ class StoresController < ApplicationController
end
def show
dt = File.read("foo.txt")
dt = File.read("foo.txt") # $ Source[rb/stored-xss]
@instance_text = dt
@user = User.find 1
@safe_user_handle = ERB::Util.html_escape(@user.handle)

View File

@@ -2,10 +2,10 @@
<%= raw @display_text %>
<%# BAD: A local rendered raw as a local variable %>
<%= raw display_text %>
<%= raw display_text %> <%# $ Alert[rb/reflected-xss], Alert[rb/stored-xss] %>
<%# BAD: A local rendered raw via the local_assigns hash %>
<%= raw local_assigns[:display_text] %>
<%= raw local_assigns[:display_text] %> <%# $ Alert[rb/reflected-xss], Alert[rb/stored-xss] %>
<%# GOOD: A local rendered with default escaping via the local_assigns hash %>
<%= local_assigns[:display_text] %>

View File

@@ -1,20 +1,20 @@
<%# BAD: An instance variable rendered without escaping %>
<a href="<%= raw @user_website %>">website</a>
<a href="<%= raw @user_website %>">website</a> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: A local rendered raw as a local variable %>
<%= raw display_text %>
<%= raw display_text %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: A local rendered raw via the local_assigns hash %>
<%= raw local_assigns[:display_text] %>
<%= raw local_assigns[:display_text] %> <%# $ Alert[rb/reflected-xss] %>
<% key = :display_text %>
<%# BAD: A local rendered raw via the locals_assigns hash %>
<%= raw local_assigns[key] %>
<%= raw local_assigns[key] %> <%# $ Alert[rb/reflected-xss] %>
<ul>
<% for key in [:display_text, :safe_text] do %>
<%# BAD: A local rendered raw via the locals hash %>
<li><%= raw local_assigns[key] %></li>
<li><%= raw local_assigns[key] %></li> <%# $ Alert[rb/reflected-xss] %>
<% end %>
</ul>
@@ -32,28 +32,28 @@
<%# BAD: html_safe marks string as not requiring HTML escaping %>
<%=
display_text.html_safe
display_text.html_safe <%# $ Alert[rb/reflected-xss] %>
%>
<%# BAD: html_safe marks string as not requiring HTML escaping %>
<%=
@instance_text.html_safe
@instance_text.html_safe <%# $ Alert[rb/reflected-xss] %>
%>
<%= render partial: 'foo/bars/widget', locals: { display_text: "widget_" + display_text } %>
<%# BAD: user_name is a helper method that returns unsanitized user-input %>
<%= user_name.html_safe %>
<%= user_name.html_safe %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: user_name_memo is a helper method that returns unsanitized user-input %>
<%# TODO: we miss this because the return value from user_name_memo is not properly linked to this call %>
<%= user_name_memo.html_safe %>
<%= user_name_memo.html_safe %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: unsanitized user-input should not be passed to link_to as the URL %>
<%= link_to "user website", params[:website] %>
<%= link_to "user website", params[:website] %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: unsanitized user-input should not be passed to link_to as the URL %>
<%= link_to params[:website], class: "user-link" do %>
<%= link_to params[:website], class: "user-link" do %> <%# $ Alert[rb/reflected-xss] %>
user website
<% end %>
@@ -70,20 +70,20 @@
%>
<%# BAD: simple_format called with sanitize: false %>
<%= simple_format(params[:comment], sanitize: false) %>
<%= simple_format(params[:comment], sanitize: false) %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: javasript_include_tag called with remote input %>
<%= javascript_include_tag params[:url] %>
<%= javascript_include_tag params[:url] %> <%# $ Alert[rb/reflected-xss] %>
<%# GOOD: input is sanitized %>
<%= sanitize(params[:comment]).html_safe %>
<%# BAD: A local rendered raw as a local variable %>
<%== display_text %>
<%== display_text %> <%# $ Alert[rb/reflected-xss] %>
<%# BAD: translate preserves taint %>
<%= raw translate("welcome", name: display_text) %>
<%= raw t("welcome", name: display_text) %>
<%= raw translate("welcome", name: display_text) %> <%# $ Alert[rb/reflected-xss] %>
<%= raw t("welcome", name: display_text) %> <%# $ Alert[rb/reflected-xss] %>
<%# GOOD: translate sanitizes for html keys %>
<%= raw t("welcome1.html", name: display_text) %>

View File

@@ -1,17 +1,17 @@
<%# BAD: A local rendered raw as a local variable %>
<%= raw display_text %>
<%= raw display_text %> <%# $ Alert[rb/stored-xss] %>
<%# BAD: A local rendered raw via the local_assigns hash %>
<%= raw local_assigns[:display_text] %>
<%= raw local_assigns[:display_text] %> <%# $ Alert[rb/stored-xss] %>
<% key = :display_text %>
<%# BAD: A local rendered raw via the locals_assigns hash %>
<%= raw local_assigns[key] %>
<%= raw local_assigns[key] %> <%# $ Alert[rb/stored-xss] %>
<ul>
<% for key in [:display_text, :safe_text] do %>
<%# BAD: A local rendered raw via the locals hash %>
<li><%= raw local_assigns[key] %></li>
<li><%= raw local_assigns[key] %></li> <%# $ Alert[rb/stored-xss] %>
<% end %>
</ul>
@@ -29,12 +29,12 @@
<%# BAD: html_safe marks string as not requiring HTML escaping %>
<%=
display_text.html_safe
display_text.html_safe <%# $ Alert[rb/stored-xss] %>
%>
<%# BAD: html_safe marks string as not requiring HTML escaping %>
<%=
@instance_text.html_safe
@instance_text.html_safe <%# $ Alert[rb/stored-xss] %>
%>
<%= render partial: 'foo/bars/widget', locals: { display_text: "widget_" + display_text } %>
@@ -43,7 +43,7 @@
<%= user_name_handle.html_safe %>
<%# BAD: Direct to a database value without escaping %>
<%= @user.handle.html_safe %>
<%= @user.handle.html_safe %> <%# $ Alert[rb/stored-xss] %>
<%# BAD: Indirect to a database value without escaping %>
<%= @user.raw_name.html_safe %>
@@ -60,7 +60,7 @@
<%# BAD: Direct to a database value without escaping %>
<%=
some_user = User.find 1
some_user.handle.html_safe
some_user.handle.html_safe <%# $ Alert[rb/stored-xss] %>
%>
<%# BAD: Indirect to a database value without escaping (currently missed due to lack of 'self' handling in ORM tracking) %>
@@ -83,7 +83,7 @@
<%# BAD: Kernel.sprintf is a taint-step %>
<%=
sprintf("%s", @user.handle).html_safe
sprintf("%s", @user.handle).html_safe <%# $ Alert[rb/stored-xss] %>
%>
<%# GOOD: The `foo.bar.baz` is not recognized as a source %>

View File

@@ -1,27 +1,27 @@
class Foobar
def create_user_description(name)
"<h2>#{name}</h2>".html_safe # NOT OK - the parameter is not escaped
def create_user_description(name) # $ Source[rb/html-constructed-from-input]
"<h2>#{name}</h2>".html_safe # $ Alert[rb/html-constructed-from-input] // NOT OK - the parameter is not escaped
# escape
"<h2>#{ERB::Util.html_escape(name)}</h2>".html_safe # OK - the parameter is escaped
end
def string_like_literal name
def string_like_literal name # $ Source[rb/html-constructed-from-input]
h = <<-HTML
<h2>#{name}</h2>
<h2>#{name}</h2> # $ Alert[rb/html-constructed-from-input]
HTML
h.html_safe # NOT OK - the parameter is not escaped
end
def sprintf_use name
sprintf("<h2>%s</h2>", name).html_safe # NOT OK - the parameter is not escaped
def sprintf_use name # $ Source[rb/html-constructed-from-input]
sprintf("<h2>%s</h2>", name).html_safe # $ Alert[rb/html-constructed-from-input] // NOT OK - the parameter is not escaped
# escape
sprintf("<h2>%s</h2>", ERB::Util.html_escape(name)).html_safe # OK - the parameter is escaped
end
def create_user_description2(name)
"<h2>#{name}</h2>".html_safe # NOT OK - the value is not necessarily HTML safe
def create_user_description2(name) # $ Source[rb/html-constructed-from-input]
"<h2>#{name}</h2>".html_safe # $ Alert[rb/html-constructed-from-input] // NOT OK - the value is not necessarily HTML safe
if name.html_safe?
"<h2>#{name}</h2>".html_safe # OK - value is marked as being HTML safe

View File

@@ -7,13 +7,13 @@ class User < ApplicationRecord
def self.authenticate(name, pass)
# BAD: possible untrusted input interpolated into SQL fragment
find(:first, :conditions => "name='#{name}' and pass='#{pass}'")
find(:first, :conditions => "name='#{name}' and pass='#{pass}'") # $ Alert
# BAD: interpolation in array argument
find(:first, conditions: ["name='#{name}' and pass='#{pass}'"])
find(:first, conditions: ["name='#{name}' and pass='#{pass}'"]) # $ Alert
# GOOD: using SQL parameters
find(:first, conditions: ["name = ? and pass = ?", name, pass])
# BAD: interpolation with flow
conds = "name=#{name}"
conds = "name=#{name}" # $ Alert
find(:first, conditions: conds)
end
@@ -27,7 +27,7 @@ class Admin < User
def self.delete_by(condition = nil)
# BAD: `delete_by overrides an ActiveRecord method, but doesn't perform
# any validation before passing its arguments on to another ActiveRecord method
destroy_by(condition)
destroy_by(condition) # $ Alert
end
end
@@ -39,64 +39,64 @@ class FooController < ActionController::Base
def some_request_handler
# BAD: executes `SELECT AVG(#{params[:column]}) FROM "users"`
# where `params[:column]` is unsanitized
User.calculate(:average, params[:column])
User.calculate(:average, params[:column]) # $ Alert
# BAD: executes `SELECT MAX(#{params[:column]}) FROM "users"`
# where `params[:column]` is unsanitized
User.maximum(params[:column])
User.maximum(params[:column]) # $ Alert
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.delete_by("id = '#{params[:id]}'")
User.delete_by("id = '#{params[:id]}'") # $ Alert
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
# (in Rails < 4.0)
User.delete_all("id = '#{params[:id]}'")
User.delete_all("id = '#{params[:id]}'") # $ Alert
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.destroy_by(["id = '#{params[:id]}'"])
User.destroy_by(["id = '#{params[:id]}'"]) # $ Alert
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
# (in Rails < 4.0)
User.destroy_all(["id = '#{params[:id]}'"])
User.destroy_all(["id = '#{params[:id]}'"]) # $ Alert
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
# where `params[:min_id]` is unsanitized
User.where(<<-SQL, MAX_USER_ID)
id BETWEEN '#{params[:min_id]}' AND ?
User.where(<<-SQL, MAX_USER_ID) # $ Alert
id BETWEEN '#{params[:min_id]}' AND ? # $ Source
SQL
# BAD: chained method case
# executes `SELECT "users".* FROM "users" WHERE (NOT (user_id = 'params[:id]'))`
# where `params[:id]` is unsanitized
User.where.not("user.id = '#{params[:id]}'")
User.where.not("user.id = '#{params[:id]}'") # $ Alert
User.authenticate(params[:name], params[:pass])
User.authenticate(params[:name], params[:pass]) # $ Source
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')` LIMIT 1
# where `params[:id]` is unsanitized
User.find_or_initialize_by("id = '#{params[:id]}'")
User.find_or_initialize_by("id = '#{params[:id]}'") # $ Alert
user = User.first
# BAD: executes `SELECT "users".* FROM "users" WHERE id = 1 LIMIT 1 #{params[:lock]}`
# where `params[:lock]` is unsanitized
user.reload(lock: params[:lock])
user.reload(lock: params[:lock]) # $ Alert
# BAD: executes `SELECT #{params[:column]} FROM "users"`
# where `params[:column]` is unsanitized
User.select(params[:column])
User.reselect(params[:column])
User.select(params[:column]) # $ Alert
User.reselect(params[:column]) # $ Alert
# BAD: executes `SELECT "users".* FROM "users" WHERE (#{params[:condition]})`
# where `params[:condition]` is unsanitized
User.rewhere(params[:condition])
User.rewhere(params[:condition]) # $ Alert
# BAD: executes `UPDATE "users" SET #{params[:fields]}`
# where `params[:fields]` is unsanitized
User.update_all(params[:fields])
User.update_all(params[:fields]) # $ Alert
# GOOD -- `update_all` sanitizes its bind variable arguments
User.find_by(name: params[:user_name])
@@ -104,41 +104,41 @@ class FooController < ActionController::Base
# BAD -- `update_all` does not sanitize its query (array arg)
User.find_by(name: params[:user_name])
.update_all(["name = '#{params[:new_user_name]}'"])
.update_all(["name = '#{params[:new_user_name]}'"]) # $ Alert
# BAD -- `update_all` does not sanitize its query (string arg)
User.find_by(name: params[:user_name])
.update_all("name = '#{params[:new_user_name]}'")
.update_all("name = '#{params[:new_user_name]}'") # $ Alert
User.reorder(params[:direction])
User.reorder(params[:direction]) # $ Alert
User.select('a','b', params[:column])
User.reselect('a','b', params[:column])
User.order('a ASC', "b #{params[:direction]}")
User.reorder('a ASC', "b #{params[:direction]}")
User.group('a', params[:column])
User.pluck('a', params[:column])
User.joins(:a, params[:column])
User.select('a','b', params[:column]) # $ Alert
User.reselect('a','b', params[:column]) # $ Alert
User.order('a ASC', "b #{params[:direction]}") # $ Alert
User.reorder('a ASC', "b #{params[:direction]}") # $ Alert
User.group('a', params[:column]) # $ Alert
User.pluck('a', params[:column]) # $ Alert
User.joins(:a, params[:column]) # $ Alert
User.count_by_sql(params[:custom_sql_query])
User.count_by_sql(params[:custom_sql_query]) # $ Alert
# BAD: executes `SELECT users.* FROM #{params[:tab]}`
# where `params[:tab]` is unsanitized
User.all.from(params[:tab])
User.all.from(params[:tab]) # $ Alert
# BAD: executes `SELECT "users".* FROM (SELECT "users".* FROM "users") #{params[:sq]}
User.all.from(User.all, params[:sq])
User.all.from(User.all, params[:sq]) # $ Alert
end
end
class BarController < ApplicationController
def some_other_request_handler
ps = params
ps = params # $ Source
uid = ps[:id]
uidEq = "= '#{uid}'"
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
# where `uid` is unsantized
User.delete_by("id " + uidEq)
User.delete_by("id " + uidEq) # $ Alert
end
def safe_paths
@@ -171,7 +171,7 @@ end
class BazController < BarController
def yet_another_handler
Admin.delete_by(params[:admin_condition])
Admin.delete_by(params[:admin_condition]) # $ Alert, Source
end
end
@@ -185,7 +185,7 @@ class AnnotatedController < ActionController::Base
def unsafe_action
name = params[:user_name]
# BAD: user input passed into annotations are vulnerable to SQLi
users = User.annotate("this is an unsafe annotation:#{params[:comment]}").find_by(user_name: name)
users = User.annotate("this is an unsafe annotation:#{params[:comment]}").find_by(user_name: name) # $ Alert
end
end
@@ -198,27 +198,27 @@ class RegressionController < ActionController::Base
def index
my_params = permitted_params
query = "SELECT * FROM users WHERE id = #{my_params[:user_id]}"
result = Regression.find_by_sql(query)
result = Regression.find_by_sql(query) # $ Alert
end
def permitted_params
params.require(:my_key).permit(:id, :user_id, :my_type)
params.require(:my_key).permit(:id, :user_id, :my_type) # $ Source
end
def show
ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}")
Regression.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}")
ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}") # $ Alert
Regression.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}") # $ Alert
end
end
class User
scope :with_role, ->(role) { where("role = #{role}") }
scope :with_role, ->(role) { where("role = #{role}") } # $ Alert
end
class UsersController < ActionController::Base
def index
# BAD: user input passed to scope which uses it without sanitization.
@users = User.with_role(params[:role])
@users = User.with_role(params[:role]) # $ Source
end
end

View File

@@ -1,9 +1,9 @@
class PotatoController < ActionController::Base
def unsafe_action
name = params[:user_name]
name = params[:user_name] # $ Source
# BAD: SQL statement constructed from user input
sql = Arel.sql("SELECT * FROM users WHERE name = #{name}")
sql = Arel::Nodes::SqlLiteral.new("SELECT * FROM users WHERE name = #{name}")
sql = Arel.sql("SELECT * FROM users WHERE name = #{name}") # $ Alert
sql = Arel::Nodes::SqlLiteral.new("SELECT * FROM users WHERE name = #{name}") # $ Alert
end
end
end

View File

@@ -3,7 +3,7 @@ class FooController < ActionController::Base
def some_request_handler
# A string tainted by user input is inserted into a query
# (i.e a remote flow source)
name = params[:name]
name = params[:name] # $ Source
# Establish a connection to a PostgreSQL database
conn = PG::Connection.open(:dbname => 'postgresql', :user => 'user', :password => 'pass', :host => 'localhost', :port => '5432')
@@ -11,14 +11,14 @@ class FooController < ActionController::Base
# .exec() and .async_exec()
# BAD: SQL statement constructed from user input
qry1 = "SELECT * FROM users WHERE username = '#{name}';"
conn.exec(qry1)
conn.async_exec(qry1)
conn.exec(qry1) # $ Alert
conn.async_exec(qry1) # $ Alert
# .exec_params() and .async_exec_params()
# BAD: SQL statement constructed from user input
qry2 = "SELECT * FROM users WHERE username = '#{name}';"
conn.exec_params(qry2)
conn.async_exec_params(qry2)
conn.exec_params(qry2) # $ Alert
conn.async_exec_params(qry2) # $ Alert
# .exec_params() and .async_exec_params()
# GOOD: SQL statement constructed from sanitized user input
@@ -29,7 +29,7 @@ class FooController < ActionController::Base
# .prepare() and .exec_prepared()
# BAD: SQL statement constructed from user input
qry3 = "SELECT * FROM users WHERE username = '#{name}';"
conn.prepare("query_1", qry3)
conn.prepare("query_1", qry3) # $ Alert
conn.exec_prepared('query_1')
# .prepare() and .exec_prepared()
@@ -41,7 +41,7 @@ class FooController < ActionController::Base
# .prepare() and .exec_prepared()
# NOT EXECUTED: SQL statement constructed from user input but not executed
qry3 = "SELECT * FROM users WHERE username = '#{name}';"
conn.prepare("query_3", qry3)
conn.prepare("query_3", qry3) # $ Alert
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-089/SqlInjection.ql
query: queries/security/cwe-089/SqlInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1 +1,2 @@
queries/security/cwe-094/UnsafeCodeConstruction.ql
query: queries/security/cwe-094/UnsafeCodeConstruction.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,16 +1,16 @@
class Foobar
def foo1(target)
eval("foo = #{target}") # NOT OK
def foo1(target) # $ Source
eval("foo = #{target}") # $ Alert // NOT OK
end
# sprintf
def foo2(x)
eval(sprintf("foo = %s", x)) # NOT OK
def foo2(x) # $ Source
eval(sprintf("foo = %s", x)) # $ Alert // NOT OK
end
# String#%
def foo3(x)
eval("foo = %{foo}" % {foo: x}) # NOT OK
def foo3(x) # $ Source
eval("foo = %{foo}" % {foo: x}) # $ Alert // NOT OK
end
def indirect_eval(x)
@@ -25,42 +25,42 @@ class Foobar
eval("def \n #{code} \n end") # OK - parameter is named code
end
def joinStuff(my_arr)
eval(my_arr.join("\n")) # NOT OK
def joinStuff(my_arr) # $ Source
eval(my_arr.join("\n")) # $ Alert // NOT OK
end
def joinWithElemt(x)
def joinWithElemt(x) # $ Source
arr = [x, "foobar"]
eval(arr.join("\n")) # NOT OK
eval(arr.join("\n")) # $ Alert // NOT OK
end
def pushArr(x, y)
def pushArr(x, y) # $ Source
arr = []
arr.push(x)
eval(arr.join("\n")) # NOT OK
eval(arr.join("\n")) # $ Alert // NOT OK
arr2 = []
arr2 << y
eval(arr.join("\n")) # NOT OK
eval(arr.join("\n")) # $ Alert // NOT OK
end
def hereDoc(x)
def hereDoc(x) # $ Source
foo = <<~HERE
#{x}
#{x} # $ Alert
HERE
eval(foo) # NOT OK
end
def string_concat(x)
foo = "foo = " + x
def string_concat(x) # $ Source
foo = "foo = " + x # $ Alert
eval(foo) # NOT OK
end
def join_indirect(x, y)
def join_indirect(x, y) # $ Source
arr = Array(x)
eval(arr.join(" ")) # NOT OK
eval(arr.join(" ")) # $ Alert // NOT OK
arr2 = [Array(["foo = ", y]).join(" ")]
eval(arr2.join("\n")) # NOT OK
eval(arr2.join("\n")) # $ Alert // NOT OK
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-116/BadTagFilter.ql
query: queries/security/cwe-116/BadTagFilter.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,22 +1,22 @@
filters = [
/<script.*?>.*?<\/script>/i, # NOT OK - doesn't match newlines or `</script >`
/<script.*?>.*?<\/script>/im, # NOT OK - doesn't match `</script >`
/<script.*?>.*?<\/script>/i, # $ Alert // NOT OK - doesn't match newlines or `</script >`
/<script.*?>.*?<\/script>/im, # $ Alert // NOT OK - doesn't match `</script >`
/<script.*?>.*?<\/script[^>]*>/im, # OK
/<!--.*-->/im, # OK - we don't care regexps that only match comments
/<!--.*--!?>/im, # OK
/<!--.*--!?>/i, # NOT OK, does not match newlines
/<script.*?>(.|\s)*?<\/script[^>]*>/i, # NOT OK - doesn't match inside the script tag
/<script[^>]*?>.*?<\/script[^>]*>/i, # NOT OK - doesn't match newlines inside the content
/<script(\s|\w|=|")*?>.*?<\/script[^>]*>/im, # NOT OK - does not match single quotes for attribute values
/<script(\s|\w|=|')*?>.*?<\/script[^>]*>/im, # NOT OK - does not match double quotes for attribute values
/<script( |\n|\w|=|'|")*?>.*?<\/script[^>]*>/im, # NOT OK - does not match tabs between attributes
/<script.*?>.*?<\/script[^>]*>/m, # NOT OK - does not match uppercase SCRIPT tags
/<(script|SCRIPT).*?>.*?<\/(script|SCRIPT)[^>]*>/m, # NOT OK - does not match mixed case script tags
/<script[^>]*?>[\s\S]*?<\/script.*>/i, # NOT OK - doesn't match newlines in the end tag
/<!--.*--!?>/i, # $ Alert // NOT OK, does not match newlines
/<script.*?>(.|\s)*?<\/script[^>]*>/i, # $ Alert // NOT OK - doesn't match inside the script tag
/<script[^>]*?>.*?<\/script[^>]*>/i, # $ Alert // NOT OK - doesn't match newlines inside the content
/<script(\s|\w|=|")*?>.*?<\/script[^>]*>/im, # $ Alert // NOT OK - does not match single quotes for attribute values
/<script(\s|\w|=|')*?>.*?<\/script[^>]*>/im, # $ Alert // NOT OK - does not match double quotes for attribute values
/<script( |\n|\w|=|'|")*?>.*?<\/script[^>]*>/im, # $ Alert // NOT OK - does not match tabs between attributes
/<script.*?>.*?<\/script[^>]*>/m, # $ Alert // NOT OK - does not match uppercase SCRIPT tags
/<(script|SCRIPT).*?>.*?<\/(script|SCRIPT)[^>]*>/m, # $ Alert // NOT OK - does not match mixed case script tags
/<script[^>]*?>[\s\S]*?<\/script.*>/i, # $ Alert // NOT OK - doesn't match newlines in the end tag
/<script[^>]*?>[\s\S]*?<\/script[^>]*?>/i, # OK
/<script\b[^>]*>([\s\S]*?)<\/script>/gi, # NOT OK - too strict matching on the end tag
/<(?:!--([\S|\s]*?)-->)|([^\/\s>]+)[\S\s]*?>/, # NOT OK - doesn't match comments with the right capture groups
/<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/, # NOT OK - capture groups
/<script\b[^>]*>([\s\S]*?)<\/script>/gi, # $ Alert // NOT OK - too strict matching on the end tag
/<(?:!--([\S|\s]*?)-->)|([^\/\s>]+)[\S\s]*?>/, # $ Alert // NOT OK - doesn't match comments with the right capture groups
/<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/, # $ Alert // NOT OK - capture groups
]
doFilters(filters)
doFilters(filters)

View File

@@ -1 +1,2 @@
queries/security/cwe-116/IncompleteSanitization.ql
query: queries/security/cwe-116/IncompleteSanitization.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,91 +1,91 @@
def bad1(s)
s.sub "'", "" # NOT OK
s.sub! "'", "" # NOT OK
s.sub "'", "" # $ Alert // NOT OK
s.sub! "'", "" # $ Alert // NOT OK
end
def bad2(s)
s.sub /'/, "" # NOT OK
s.sub! /'/, "" # NOT OK
s.sub /'/, "" # $ Alert // NOT OK
s.sub! /'/, "" # $ Alert // NOT OK
end
def bad3(s1, s2, s3)
s1.gsub /'/, "\\'" # NOT OK
s1.gsub /'/, '\\\'' # NOT OK
s2.gsub! /'/, "\\'" # NOT OK
s3.gsub! /'/, '\\\'' # NOT OK
s1.gsub /'/, "\\'" # $ Alert // NOT OK
s1.gsub /'/, '\\\'' # $ Alert // NOT OK
s2.gsub! /'/, "\\'" # $ Alert // NOT OK
s3.gsub! /'/, '\\\'' # $ Alert // NOT OK
end
def bad4(s1, s2, s3)
s1.gsub /'/, "\\\\\\&" # NOT OK
s1.gsub /'/, '\\\\\&' # NOT OK
s2.gsub! /'/, "\\\\\\&" # NOT OK
s3.gsub! /'/, '\\\\\&' # NOT OK
s1.gsub /'/, "\\\\\\&" # $ Alert // NOT OK
s1.gsub /'/, '\\\\\&' # $ Alert // NOT OK
s2.gsub! /'/, "\\\\\\&" # $ Alert // NOT OK
s3.gsub! /'/, '\\\\\&' # $ Alert // NOT OK
end
def bad5(s)
s.gsub /['"]/, '\\\\\&' # NOT OK
s.gsub! /['"]/, '\\\\\&' # NOT OK
s.gsub /['"]/, '\\\\\&' # $ Alert // NOT OK
s.gsub! /['"]/, '\\\\\&' # $ Alert // NOT OK
end
def bad6(s)
s.gsub /(['"])/, '\\\\\\1' # NOT OK
s.gsub! /(['"])/, '\\\\\\1' # NOT OK
s.gsub /(['"])/, '\\\\\\1' # $ Alert // NOT OK
s.gsub! /(['"])/, '\\\\\\1' # $ Alert // NOT OK
end
def bad7(s)
s.gsub /('|")/, '\\\\\1' # NOT OK
s.gsub! /('|")/, '\\\\\1' # NOT OK
s.gsub /('|")/, '\\\\\1' # $ Alert // NOT OK
s.gsub! /('|")/, '\\\\\1' # $ Alert // NOT OK
end
def bad8(s)
s.sub '|', '' # NOT OK
s.sub! '|', '' # NOT OK
s.sub '|', '' # $ Alert // NOT OK
s.sub! '|', '' # $ Alert // NOT OK
end
def bad9(s1, s2, s3, s4)
s1.gsub /"/, "\\\"" # NOT OK
s1.gsub /"/, '\\"' # NOT OK
s1.gsub '"', '\\"' # NOT OK
s2.gsub! /"/, "\\\"" # NOT OK
s3.gsub! /"/, '\\"' # NOT OK
s4.gsub! '"', '\\"' # NOT OK
s1.gsub /"/, "\\\"" # $ Alert // NOT OK
s1.gsub /"/, '\\"' # $ Alert // NOT OK
s1.gsub '"', '\\"' # $ Alert // NOT OK
s2.gsub! /"/, "\\\"" # $ Alert // NOT OK
s3.gsub! /"/, '\\"' # $ Alert // NOT OK
s4.gsub! '"', '\\"' # $ Alert // NOT OK
end
def bad10(s)
s.sub "/", "%2F" # NOT OK
s.sub! "/", "%2F" # NOT OK
s.sub "/", "%2F" # $ Alert // NOT OK
s.sub! "/", "%2F" # $ Alert // NOT OK
end
def bad11(s)
s.sub "%25", "%" # NOT OK
s.sub! "%25", "%" # NOT OK
s.sub "%25", "%" # $ Alert // NOT OK
s.sub! "%25", "%" # $ Alert // NOT OK
end
def bad12(s)
s.sub %q['], %q[] # NOT OK
s.sub! %q['], %q[] # NOT OK
s.sub %q['], %q[] # $ Alert // NOT OK
s.sub! %q['], %q[] # $ Alert // NOT OK
end
def bad13(s)
s.sub "'" + "", "" # NOT OK
s.sub! "'" + "", "" # NOT OK
s.sub "'" + "", "" # $ Alert // NOT OK
s.sub! "'" + "", "" # $ Alert // NOT OK
end
def bad14(s)
s.sub "'", "" + "" # NOT OK
s.sub! "'", "" + "" # NOT OK
s.sub "'", "" + "" # $ Alert // NOT OK
s.sub! "'", "" + "" # $ Alert // NOT OK
end
def bad15(s)
s.sub "'" + "", "" + "" # NOT OK
s.sub! "'" + "", "" + "" # NOT OK
s.sub "'" + "", "" + "" # $ Alert // NOT OK
s.sub! "'" + "", "" + "" # $ Alert // NOT OK
end
def bad16(s)
indirect = /'/
s.sub(indirect, "") # NOT OK
s.sub!(indirect, "") # NOT OK
s.sub(indirect, "") # $ Alert // NOT OK
s.sub!(indirect, "") # $ Alert // NOT OK
end
def good1a(s)
@@ -212,15 +212,15 @@ def good13a(s)
s.sub('[', '').sub(']', '') # OK
s.sub('(', '').sub(')', '') # OK
s.sub('{', '').sub('}', '') # OK
s.sub('<', '').sub('>', '') # NOT OK: too common as a bad HTML sanitizer
s.sub('<', '').sub('>', '') # $ Alert // NOT OK: too common as a bad HTML sanitizer
s.sub('[', '\\[').sub(']', '\\]') # NOT OK
s.sub('{', '\\{').sub('}', '\\}') # NOT OK
s.sub('[', '\\[').sub(']', '\\]') # $ Alert // NOT OK
s.sub('{', '\\{').sub('}', '\\}') # $ Alert // NOT OK
s = s.sub('[', '') # OK
s = s.sub(']', '') # OK
s.sub(/{/, '').sub(/}/, '') # OK
s.sub(']', '').sub('[', '') # probably OK, but still flagged
s.sub(']', '').sub('[', '') # $ Alert // probably OK, but still flagged
end
def good13b(s1)
@@ -245,8 +245,8 @@ def newlines_a(a, b, c)
# motivation for whitelist
`which emacs`.sub("\n", "") # OK
a.sub("\n", "").sub(b, c) # NOT OK
a.sub(b, c).sub("\n", "") # NOT OK
a.sub("\n", "").sub(b, c) # $ Alert // NOT OK
a.sub(b, c).sub("\n", "") # $ Alert // NOT OK
end
def newlines_b(a, b, c)
@@ -255,18 +255,18 @@ def newlines_b(a, b, c)
output.sub!("\n", "") # OK
d = a.dup
d.sub!("\n", "") # NOT OK
d.sub!("\n", "") # $ Alert // NOT OK
d.sub!(b, c)
e = a.dup
d.sub!(b, c)
d.sub!("\n", "") # NOT OK
d.sub!("\n", "") # $ Alert // NOT OK
end
def bad_path_sanitizer(p1, p2)
# attempt at path sanitization
p1.sub! "/../", "" # NOT OK
p2.sub "/../", "" # NOT OK
p1.sub! "/../", "" # $ Alert // NOT OK
p2.sub "/../", "" # $ Alert // NOT OK
end
def each_line_sanitizer(p1)

View File

@@ -1 +1,2 @@
queries/security/cwe-117/LogInjection.ql
query: queries/security/cwe-117/LogInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -12,9 +12,9 @@ class UsersController < ApplicationController
def read_from_params
init_logger
unsanitized = params[:foo]
@logger.debug unsanitized # BAD: unsanitized user input
@logger.error "input: " + unsanitized # BAD: unsanitized user input
unsanitized = params[:foo] # $ Source
@logger.debug unsanitized # $ Alert // BAD: unsanitized user input
@logger.error "input: " + unsanitized # $ Alert // BAD: unsanitized user input
sanitized = unsanitized.gsub("\n", "")
@logger.fatal sanitized # GOOD: sanitized user input
@@ -22,17 +22,17 @@ class UsersController < ApplicationController
unsanitized2 = unsanitized.sub("\n", "")
@logger.info do
unsanitized2 # BAD: partially sanitized user input
unsanitized2 # $ Alert // BAD: partially sanitized user input
end
@logger << "input: " + unsanitized2 # BAD: partially sanitized user input
@logger << "input: " + unsanitized2 # $ Alert // BAD: partially sanitized user input
end
def read_from_cookies
init_logger
unsanitized = cookies[:bar]
@logger.add(Logger::INFO) { unsanitized } # BAD: unsanitized user input
@logger.log(Logger::WARN) { "input: " + unsanitized } # BAD: unsanitized user input
unsanitized = cookies[:bar] # $ Source
@logger.add(Logger::INFO) { unsanitized } # $ Alert // BAD: unsanitized user input
@logger.log(Logger::WARN) { "input: " + unsanitized } # $ Alert // BAD: unsanitized user input
end
def html_sanitization
@@ -46,7 +46,7 @@ class UsersController < ApplicationController
def inspect_sanitization
init_logger
@logger.debug params[:foo] # BAD: unsanitized user input
@logger.debug params[:foo] # $ Alert // BAD: unsanitized user input
@logger.debug params[:foo].inspect # GOOD: sanitized user input
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-1333/ReDoS.ql
query: queries/security/cwe-1333/ReDoS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,7 +1,7 @@
# NOT GOOD; attack: "_" + "__".repeat(100)
# Adapted from marked (https://github.com/markedjs/marked), which is licensed
# under the MIT license; see file marked-LICENSE.
bad1 = /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/
bad1 = /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/ # $ Alert
# GOOD
# Adapted from marked (https://github.com/markedjs/marked), which is licensed
@@ -16,7 +16,7 @@ good2 = /(.*,)+.+/
# NOT GOOD; attack: " '" + "\\\\".repeat(100)
# Adapted from CodeMirror (https://github.com/codemirror/codemirror),
# which is licensed under the MIT license; see file CodeMirror-LICENSE.
bad2 = /^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/
bad2 = /^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/ # $ Alert
# GOOD
# Adapted from lulucms2 (https://github.com/yiifans/lulucms2).
@@ -28,89 +28,89 @@ good2 = /\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/
good3 = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/
# NOT GOOD, variant of good3; attack: "a|\n:|\n" + "||\n".repeat(100)
bad4 = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)a/
bad4 = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)a/ # $ Alert
# NOT GOOD; attack: "/" + "\\/a".repeat(100)
# Adapted from ANodeBlog (https://github.com/gefangshuai/ANodeBlog),
# which is licensed under the Apache License 2.0; see file ANodeBlog-LICENSE.
bad5 = /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/
bad5 = /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/ # $ Alert
# NOT GOOD; attack: "##".repeat(100) + "\na"
# Adapted from CodeMirror (https://github.com/codemirror/codemirror),
# which is licensed under the MIT license; see file CodeMirror-LICENSE.
bad6 = /^([\s\[\{\(]|#.*)*$/
bad6 = /^([\s\[\{\(]|#.*)*$/ # $ Alert
# GOOD
good4 = /(\r\n|\r|\n)+/
# BAD - PoC: `node -e "/((?:[^\"\']|\".*?\"|\'.*?\')*?)([(,)]|$)/.test(\"'''''''''''''''''''''''''''''''''''''''''''''\\\"\");"`. It's complicated though, because the regexp still matches something, it just matches the empty-string after the attack string.
actuallyBad = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/
actuallyBad = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/ # $ Alert
# NOT GOOD; attack: "a" + "[]".repeat(100) + ".b\n"
# Adapted from Knockout (https://github.com/knockout/knockout), which is
# licensed under the MIT license; see file knockout-LICENSE
bad6 = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i
bad6 = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i # $ Alert
# GOOD
good6 = /(a|.)*/
# Testing the NFA - only some of the below are detected.
bad7 = /^([a-z]+)+$/
bad8 = /^([a-z]*)*$/
bad9 = /^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/
bad10 = /^(([a-z])+.)+[A-Z]([a-z])+$/
bad7 = /^([a-z]+)+$/ # $ Alert
bad8 = /^([a-z]*)*$/ # $ Alert
bad9 = /^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/ # $ Alert
bad10 = /^(([a-z])+.)+[A-Z]([a-z])+$/ # $ Alert
# NOT GOOD; attack: "[" + "][".repeat(100) + "]!"
# Adapted from Prototype.js (https://github.com/prototypejs/prototype), which
# is licensed under the MIT license; see file Prototype.js-LICENSE.
bad11 = /(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/
bad11 = /(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/ # $ Alert
# NOT GOOD; attack: "'" + "\\a".repeat(100) + '"'
# Adapted from Prism (https://github.com/PrismJS/prism), which is licensed
# under the MIT license; see file Prism-LICENSE.
bad12 = /("|')(\\?.)*?\1/
bad12 = /("|')(\\?.)*?\1/ # $ Alert
# NOT GOOD
bad13 = /(b|a?b)*c/
bad13 = /(b|a?b)*c/ # $ Alert
# NOT GOOD
bad15 = /(a|aa?)*b/
bad15 = /(a|aa?)*b/ # $ Alert
# GOOD
good7 = /(.|\n)*!/
# NOT GOOD; attack: "\n".repeat(100) + "."
bad16 = /(.|\n)*!/m
bad16 = /(.|\n)*!/m # $ Alert
# GOOD
good8 = /([\w.]+)*/
# NOT GOOD
bad17 = Regexp.new '(a|aa?)*b'
bad17 = Regexp.new '(a|aa?)*b' # $ Alert
# GOOD - not used as regexp
good9 = '(a|aa?)*b'
# NOT GOOD
bad18 = /(([\S\s]|[^a])*)"/
bad18 = /(([\S\s]|[^a])*)"/ # $ Alert
# GOOD - there is no witness in the end that could cause the regexp to not match
good10 = /([^"']+)*/
# NOT GOOD
bad20 = /((.|[^a])*)"/
bad20 = /((.|[^a])*)"/ # $ Alert
# GOOD
good10 = /((a|[^a])*)"/
# NOT GOOD
bad21 = /((b|[^a])*)"/
bad21 = /((b|[^a])*)"/ # $ Alert
# NOT GOOD
bad22 = /((G|[^a])*)"/
bad22 = /((G|[^a])*)"/ # $ Alert
# NOT GOOD
bad23 = /(([0-9]|[^a])*)"/
bad23 = /(([0-9]|[^a])*)"/ # $ Alert
# BAD - missing result
bad24 = /(?:=(?:([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)|"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"))?/
@@ -122,55 +122,55 @@ bad25 = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"/
bad26 = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)"/
# NOT GOOD
bad27 = /(([a-z]|[d-h])*)"/
bad27 = /(([a-z]|[d-h])*)"/ # $ Alert
# NOT GOOD
bad27 = /(([^a-z]|[^0-9])*)"/
bad27 = /(([^a-z]|[^0-9])*)"/ # $ Alert
# NOT GOOD
bad28 = /((\d|[0-9])*)"/
bad28 = /((\d|[0-9])*)"/ # $ Alert
# NOT GOOD
bad29 = /((\s|\s)*)"/
bad29 = /((\s|\s)*)"/ # $ Alert
# NOT GOOD
bad30 = /((\w|G)*)"/
bad30 = /((\w|G)*)"/ # $ Alert
# GOOD
good11 = /((\s|\d)*)"/
# NOT GOOD
bad31 = /((\d|\w)*)"/
bad31 = /((\d|\w)*)"/ # $ Alert
# NOT GOOD
bad32 = /((\d|5)*)"/
bad32 = /((\d|5)*)"/ # $ Alert
# BAD - \f is not handled correctly
bad33 = /((\s|[\f])*)"/
bad33 = /((\s|[\f])*)"/ # $ Alert
# BAD - \v is not handled correctly
bad34 = /((\s|[\v]|\\v)*)"/
bad34 = /((\s|[\v]|\\v)*)"/ # $ Alert
# NOT GOOD
bad35 = /((\f|[\f])*)"/
bad35 = /((\f|[\f])*)"/ # $ Alert
# NOT GOOD
bad36 = /((\W|\D)*)"/
bad36 = /((\W|\D)*)"/ # $ Alert
# NOT GOOD
bad37 = /((\S|\w)*)"/
bad37 = /((\S|\w)*)"/ # $ Alert
# NOT GOOD
bad38 = /((\S|[\w])*)"/
bad38 = /((\S|[\w])*)"/ # $ Alert
# NOT GOOD
bad39 = /((1s|[\da-z])*)"/
bad39 = /((1s|[\da-z])*)"/ # $ Alert
# NOT GOOD
bad40 = /((0|[\d])*)"/
bad40 = /((0|[\d])*)"/ # $ Alert
# NOT GOOD
bad41 = /(([\d]+)*)"/
bad41 = /(([\d]+)*)"/ # $ Alert
# GOOD - there is no witness in the end that could cause the regexp to not match
good12 = /(\d+(X\d+)?)+/
@@ -182,49 +182,49 @@ good13 = /([0-9]+(X[0-9]*)?)*/
good15 = /^([^>]+)*(>|$)/
# NOT GOOD
bad43 = /^([^>a]+)*(>|$)/
bad43 = /^([^>a]+)*(>|$)/ # $ Alert
# NOT GOOD
bad44 = /(\n\s*)+$/
bad44 = /(\n\s*)+$/ # $ Alert
# NOT GOOD
bad45 = /^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/
bad45 = /^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ # $ Alert
# NOT GOOD
bad46 = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/
bad46 = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ # $ Alert
# NOT GOOD
bad47 = /(a+|b+|c+)*c/
bad47 = /(a+|b+|c+)*c/ # $ Alert
# NOT GOOD
bad48 = /(((a+a?)*)+b+)/
bad48 = /(((a+a?)*)+b+)/ # $ Alert
# NOT GOOD
bad49 = /(a+)+bbbb/
bad49 = /(a+)+bbbb/ # $ Alert
# GOOD
good16 = /(a+)+aaaaa*a+/
# NOT GOOD
bad50 = /(a+)+aaaaa$/
bad50 = /(a+)+aaaaa$/ # $ Alert
# GOOD
good17 = /(\n+)+\n\n/
# NOT GOOD
bad51 = /(\n+)+\n\n$/
bad51 = /(\n+)+\n\n$/ # $ Alert
# NOT GOOD
bad52 = /([^X]+)*$/
bad52 = /([^X]+)*$/ # $ Alert
# NOT GOOD
bad53 = /(([^X]b)+)*$/
bad53 = /(([^X]b)+)*$/ # $ Alert
# GOOD
good18 = /(([^X]b)+)*($|[^X]b)/
# NOT GOOD
bad54 = /(([^X]b)+)*($|[^X]c)/
bad54 = /(([^X]b)+)*($|[^X]c)/ # $ Alert
# GOOD
good20 = /((ab)+)*ababab/
@@ -236,13 +236,13 @@ good21 = /((ab)+)*abab(ab)*(ab)+/
good22 = /((ab)+)*/
# NOT GOOD
bad55 = /((ab)+)*$/
bad55 = /((ab)+)*$/ # $ Alert
# GOOD
good23 = /((ab)+)*[a1][b1][a2][b2][a3][b3]/
# NOT GOOD
bad56 = /([\n\s]+)*(.)/
bad56 = /([\n\s]+)*(.)/ # $ Alert
# GOOD - any witness passes through the accept state.
good24 = /(A*A*X)*/
@@ -251,13 +251,13 @@ good24 = /(A*A*X)*/
good26 = /([^\\\]]+)*/
# NOT GOOD
bad59 = /(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-/
bad59 = /(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-/ # $ Alert
# NOT GOOD
bad60 = /(.thisisagoddamnlongstringforstresstestingthequery|\sthisisagoddamnlongstringforstresstestingthequery)*-/
bad60 = /(.thisisagoddamnlongstringforstresstestingthequery|\sthisisagoddamnlongstringforstresstestingthequery)*-/ # $ Alert
# NOT GOOD
bad61 = /(thisisagoddamnlongstringforstresstestingthequery|this\w+query)*-/
bad61 = /(thisisagoddamnlongstringforstresstestingthequery|this\w+query)*-/ # $ Alert
# GOOD
good27 = /(thisisagoddamnlongstringforstresstestingthequery|imanotherbutunrelatedstringcomparedtotheotherstring)*-/
@@ -269,58 +269,58 @@ good27 = /(thisisagoddamnlongstringforstresstestingthequery|imanotherbutunrelate
#good29 = /foo((\uDC66|\uDC67)|(\uDC68|\uDC69))*foo/
# NOT GOOD (but cannot currently construct a prefix)
bad62 = /a{2,3}(b+)+X/
bad62 = /a{2,3}(b+)+X/ # $ Alert
# NOT GOOD (and a good prefix test)
bad63 = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
bad63 = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/ # $ Alert
# GOOD
good30 = /(a+)*[\S\s][\S\s][\S\s]?/
# GOOD - but we fail to see that repeating the attack string ends in the "accept any" state (due to not parsing the range `[^]{2,3}`).
good31 = /(a+)*[\S\s]{2,3}/
good31 = /(a+)*[\S\s]{2,3}/ # $ Alert
# GOOD - but we spuriously conclude that a rejecting suffix exists (due to not parsing the range `[^]{2,}` when constructing the NFA).
good32 = /(a+)*([\S\s]{2,}|X)$/
good32 = /(a+)*([\S\s]{2,}|X)$/ # $ Alert
# GOOD
good33 = /(a+)*([\S\s]*|X)$/
# NOT GOOD
bad64 = /((a+)*$|[\S\s]+)/
bad64 = /((a+)*$|[\S\s]+)/ # $ Alert
# GOOD - but still flagged. The only change compared to the above is the order of alternatives, which we don't model.
good34 = /([\S\s]+|(a+)*$)/
good34 = /([\S\s]+|(a+)*$)/ # $ Alert
# GOOD
good35 = /((;|^)a+)+$/
# NOT GOOD (a good prefix test)
bad65 = /(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f/
bad65 = /(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f/ # $ Alert
# NOT GOOD
bad66 = /^ab(c+)+$/
bad66 = /^ab(c+)+$/ # $ Alert
# NOT GOOD
bad67 = /(\d(\s+)*){20}/
bad67 = /(\d(\s+)*){20}/ # $ Alert
# GOOD - but we spuriously conclude that a rejecting suffix exists.
good36 = /(([^\/]|X)+)(\/[\S\s]*)*$/
good36 = /(([^\/]|X)+)(\/[\S\s]*)*$/ # $ Alert
# GOOD - but we spuriously conclude that a rejecting suffix exists.
good37 = /^((x([^Y]+)?)*(Y|$))/
good37 = /^((x([^Y]+)?)*(Y|$))/ # $ Alert
# NOT GOOD
bad68 = /(a*)+b/
bad68 = /(a*)+b/ # $ Alert
# NOT GOOD
bad69 = /foo([\w-]*)+bar/
bad69 = /foo([\w-]*)+bar/ # $ Alert
# NOT GOOD
bad70 = /((ab)*)+c/
bad70 = /((ab)*)+c/ # $ Alert
# NOT GOOD
bad71 = /(a?a?)*b/
bad71 = /(a?a?)*b/ # $ Alert
# GOOD
good38 = /(a?)*b/
@@ -329,54 +329,54 @@ good38 = /(a?)*b/
bad72 = /(c?a?)*b/
# NOT GOOD
bad73 = /(?:a|a?)+b/
bad73 = /(?:a|a?)+b/ # $ Alert
# NOT GOOD - but not detected.
bad74 = /(a?b?)*$/
# NOT GOOD
bad76 = /PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)/
bad76 = /PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)/ # $ Alert
# NOT GOOD - but not detected
bad77 = /^((a)+\w)+$/
bad77 = /^((a)+\w)+$/ # $ Alert
# NOT GOOD
bad78 = /^(b+.)+$/
bad78 = /^(b+.)+$/ # $ Alert
# GOOD
good39 = /a*b/
# All 4 bad combinations of nested * and +
bad79 = /(a*)*b/
bad80 = /(a+)*b/
bad81 = /(a*)+b/
bad82 = /(a+)+b/
bad79 = /(a*)*b/ # $ Alert
bad80 = /(a+)*b/ # $ Alert
bad81 = /(a*)+b/ # $ Alert
bad82 = /(a+)+b/ # $ Alert
# GOOD
good40 = /(a|b)+/
good41 = /(?:[\s;,"'<>(){}|\[\]@=+*]|:(?![\/\\]))+/
# NOT GOOD
bad83 = /^((?:a{|-)|\w\{)+X$/
bad84 = /^((?:a{0|-)|\w\{\d)+X$/
bad85 = /^((?:a{0,|-)|\w\{\d,)+X$/
bad86 = /^((?:a{0,2|-)|\w\{\d,\d)+X$/
bad83 = /^((?:a{|-)|\w\{)+X$/ # $ Alert
bad84 = /^((?:a{0|-)|\w\{\d)+X$/ # $ Alert
bad85 = /^((?:a{0,|-)|\w\{\d,)+X$/ # $ Alert
bad86 = /^((?:a{0,2|-)|\w\{\d,\d)+X$/ # $ Alert
# NOT GOOD
bad87 = /^((?:a{0,2}|-)|\w\{\d,\d\})+X$/
# NOT GOOD
bad88 = /^X(\u0061|a)*Y$/
bad88 = /^X(\u0061|a)*Y$/ # $ Alert
# GOOD
good43 = /^X(\u0061|b)+Y$/
# NOT GOOD
bad88 = /X([[:digit:]]|\d)+Y/
bad88 = /X([[:digit:]]|\d)+Y/ # $ Alert
# NOT GOOD
bad89 = /\G(a|\w)*$/
bad90 = /\b(a|\w)*$/
bad89 = /\G(a|\w)*$/ # $ Alert
bad90 = /\b(a|\w)*$/ # $ Alert
# NOT GOOD; attack: "0".repeat(30) + "!"
# Adapated from addressable (https://github.com/sporkmonger/addressable)
@@ -387,5 +387,5 @@ module Bad91
var_char_class = ALPHA + DIGIT + '_'
var_char = "(?:(?:[#{var_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
var = "(?:#{var_char}(?:\\.?#{var_char})*)"
bad91 = /^#{var}$/
bad91 = /^#{var}$/ # $ Alert
end

View File

@@ -1 +1,2 @@
queries/security/cwe-1333/PolynomialReDoS.ql
query: queries/security/cwe-1333/PolynomialReDoS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,35 +1,35 @@
class FooController < ActionController::Base
def some_request_handler
# A source for the data-flow query (i.e. a remote flow source)
name = params[:name]
name = params[:name] # $ Source
# A vulnerable regex
regex = /^\s+|\s+$/
# Various sinks that match the source against the regex
name =~ regex # NOT GOOD
name !~ regex # NOT GOOD
name[regex] # NOT GOOD
name.gsub regex, '' # NOT GOOD
name.index regex # NOT GOOD
name.match regex # NOT GOOD
name.match? regex # NOT GOOD
name.partition regex # NOT GOOD
name.rindex regex # NOT GOOD
name.rpartition regex # NOT GOOD
name.scan regex # NOT GOOD
name.split regex # NOT GOOD
name.sub regex, '' # NOT GOOD
regex.match name # NOT GOOD
regex.match? name # NOT GOOD
name =~ regex # $ Alert // NOT GOOD
name !~ regex # $ Alert // NOT GOOD
name[regex] # $ Alert // NOT GOOD
name.gsub regex, '' # $ Alert // NOT GOOD
name.index regex # $ Alert // NOT GOOD
name.match regex # $ Alert // NOT GOOD
name.match? regex # $ Alert // NOT GOOD
name.partition regex # $ Alert // NOT GOOD
name.rindex regex # $ Alert // NOT GOOD
name.rpartition regex # $ Alert // NOT GOOD
name.scan regex # $ Alert // NOT GOOD
name.split regex # $ Alert // NOT GOOD
name.sub regex, '' # $ Alert // NOT GOOD
regex.match name # $ Alert // NOT GOOD
regex.match? name # $ Alert // NOT GOOD
# Destructive variants
a = params[:b]
a.gsub! regex, '' # NOT GOOD
b = params[:a]
b.slice! regex # NOT GOOD
c = params[:c]
c.sub! regex, '' # NOT GOOD
a = params[:b] # $ Source
a.gsub! regex, '' # $ Alert // NOT GOOD
b = params[:a] # $ Source
b.slice! regex # $ Alert // NOT GOOD
c = params[:c] # $ Source
c.sub! regex, '' # $ Alert // NOT GOOD
# GOOD - guarded by a string length check
if name.length < 1024
@@ -39,19 +39,19 @@ class FooController < ActionController::Base
# GOOD - regex does not suffer from polynomial backtracking (regression test)
params[:foo] =~ /\A[bc].*\Z/
case name # NOT GOOD
case name # $ Sink // NOT GOOD
when regex
puts "foo"
end
end # $ Alert
case name # NOT GOOD
case name # $ Sink // NOT GOOD
in /^\s+|\s+$/ then
puts "foo"
end
end # $ Alert
end
def some_other_request_handle
name = params[:name] # source
name = params[:name] # $ Source // source
indirect_use_of_reg /^\s+|\s+$/, name
@@ -59,22 +59,22 @@ class FooController < ActionController::Base
end
def indirect_use_of_reg (reg, input)
input.gsub reg, '' # NOT GOOD
input.gsub reg, '' # $ Alert // NOT GOOD
end
def as_string_indirect (reg_as_string, input)
input.match? reg_as_string, '' # NOT GOOD
input.match? reg_as_string, '' # $ Alert // NOT GOOD
end
def re_compile_indirect
name = params[:name] # source
name = params[:name] # $ Source // source
reg = Regexp.new '^\s+|\s+$'
re_compile_indirect_2 reg, name
end
def re_compile_indirect_2 (reg, input)
input.gsub reg, '' # NOT GOOD
input.gsub reg, '' # $ Alert // NOT GOOD
end
# See https://github.com/dependabot/dependabot-core/blob/37dc1767fde9b7184020763f4d0c1434f93d11d6/python/lib/dependabot/python/requirement_parser.rb#L6-L25
@@ -100,8 +100,8 @@ class FooController < ActionController::Base
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
def use_marker_expr
name = params[:name] # source
name = params[:name] # $ Source // source
name =~ MARKER_EXPR
name =~ MARKER_EXPR # $ Alert
end
end

View File

@@ -1,13 +1,13 @@
module Foo
def bar(x)
def bar(x) # $ Source
# Run the /a+$/ regex on the input x.
match = x.match(/a+$/)
match = x.match(/a+$/) # $ Alert
end
protected
def baz(x)
match = x.match(/a+$/)
def baz(x) # $ Source
match = x.match(/a+$/) # $ Alert
match2 = x.match(/(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)C.*Y$/)
match2 = x.match(/(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)(AA|BB)C.*Y$/) # $ Alert
end
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-1333/RegExpInjection.ql
query: queries/security/cwe-1333/RegExpInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,26 +1,26 @@
class FooController < ActionController::Base
# BAD
def route0
name = params[:name]
regex = /#{name}/
name = params[:name] # $ Source
regex = /#{name}/ # $ Alert
end
# BAD
def route1
name = params[:name]
regex = /foo#{name}bar/
name = params[:name] # $ Source
regex = /foo#{name}bar/ # $ Alert
end
# BAD
def route2
name = params[:name]
regex = Regexp.new(name)
name = params[:name] # $ Source
regex = Regexp.new(name) # $ Alert
end
# BAD
def route3
name = params[:name]
regex = Regexp.new("@" + name)
name = params[:name] # $ Source
regex = Regexp.new("@" + name) # $ Alert
end
# GOOD - string is compared against a constant string
@@ -51,7 +51,7 @@ class FooController < ActionController::Base
# BAD
def route8
name = params[:name]
regex = Regexp.compile("@" + name)
name = params[:name] # $ Source
regex = Regexp.compile("@" + name) # $ Alert
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-134/TaintedFormatString.ql
query: queries/security/cwe-134/TaintedFormatString.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -1,44 +1,44 @@
class UsersController < ActionController::Base
def show
printf(params[:format], arg) # BAD
Kernel.printf(params[:format], arg) # BAD
printf(params[:format], arg) # $ Alert // BAD
Kernel.printf(params[:format], arg) # $ Alert // BAD
printf(params[:format]) # GOOD
Kernel.printf(params[:format]) # GOOD
printf(IO.new(1), params[:format], arg) # BAD
Kernel.printf(IO.new(1), params[:format], arg) # BAD
printf(IO.new(1), params[:format], arg) # $ Alert // BAD
Kernel.printf(IO.new(1), params[:format], arg) # $ Alert // BAD
printf("%s", params[:format]) # GOOD
Kernel.printf("%s", params[:format]) # GOOD
fmt = "%s"
printf(fmt, params[:format]) # GOOD
printf(IO.new(1), params[:format]) # GOOD [FALSE POSITIVE]
Kernel.printf(IO.new(1), params[:format]) # GOOD [FALSE POSITIVE]
printf(IO.new(1), params[:format]) # $ Alert // GOOD [FALSE POSITIVE]
Kernel.printf(IO.new(1), params[:format]) # $ Alert // GOOD [FALSE POSITIVE]
str1 = Kernel.sprintf(params[:format], arg) # BAD
str2 = sprintf(params[:format], arg) # BAD
str1 = Kernel.sprintf(params[:format], arg) # $ Alert // BAD
str2 = sprintf(params[:format], arg) # $ Alert // BAD
str1 = Kernel.sprintf(params[:format]) # GOOD
str2 = sprintf(params[:format]) # GOOD
stdout = IO.new 1
stdout.printf(params[:format], arg) # BAD
stdout.printf(params[:format], arg) # $ Alert // BAD
stdout.printf(params[:format]) # GOOD
# Taint via string concatenation
printf("A log message: " + params[:format], arg) # BAD
printf("A log message: " + params[:format], arg) # $ Alert // BAD
# Taint via string interpolation
printf("A log message: #{params[:format]}", arg) # BAD
printf("A log message: #{params[:format]}", arg) # $ Alert // BAD
# Using String#
"A log message #{params[:format]} %{foo}" % {foo: "foo"} # BAD
"A log message #{params[:format]} %{foo}" % {foo: "foo"} # $ Alert // BAD
# String# with an array
"A log message #{params[:format]} %08x" % ["foo"] # BAD
"A log message #{params[:format]} %08x" % ["foo"] # $ Alert // BAD
end
end
end

View File

@@ -1 +1,2 @@
queries/security/cwe-209/StackTraceExposure.ql
query: queries/security/cwe-209/StackTraceExposure.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -3,19 +3,19 @@ class FooController < ApplicationController
def show
something_that_might_fail()
rescue => e
render body: e.backtrace, content_type: "text/plain"
render body: e.backtrace, content_type: "text/plain" # $ Alert
end
def show2
bt = caller()
render body: bt, content_type: "text/plain"
bt = caller() # $ Source
render body: bt, content_type: "text/plain" # $ Alert
end
def show3
not_a_method()
rescue NoMethodError => e
render body: e.backtrace, content_type: "text/plain"
render body: e.backtrace, content_type: "text/plain" # $ Alert
end
end

View File

@@ -3,31 +3,31 @@ require "excon"
def method1
# BAD
Excon.defaults[:ssl_verify_peer] = false
Excon.get("http://example.com/")
Excon.get("http://example.com/") # $ Alert
end
def method2
# BAD
Excon.ssl_verify_peer = false
Excon.get("http://example.com/")
Excon.get("http://example.com/") # $ Alert
end
def method3(secure)
# BAD
Excon.defaults[:ssl_verify_peer] = (secure ? true : false)
Excon.get("http://example.com/")
Excon.get("http://example.com/") # $ Alert
end
def method4
# BAD
conn = Excon::Connection.new("http://example.com/", ssl_verify_peer: false)
conn.get
conn.get # $ Alert
end
def method5
# BAD
Excon.ssl_verify_peer = true
Excon.new("http://example.com/", ssl_verify_peer: false).get
Excon.new("http://example.com/", ssl_verify_peer: false).get # $ Alert
end
def method6
@@ -65,4 +65,4 @@ def method10
# GOOD
connection = Excon.new("foo")
connection.get("bar")
end
end

View File

@@ -2,11 +2,11 @@ require "faraday"
# BAD
connection = Faraday.new("http://example.com", ssl: { verify: false })
response = connection.get("/")
response = connection.get("/") # $ Alert
# BAD
connection = Faraday.new("http://example.com", ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
response = connection.get("/")
response = connection.get("/") # $ Alert
# GOOD
connection = Faraday.new("http://example.com")
@@ -32,7 +32,7 @@ response = connection.get("/")
def verify_as_arg(host, path, arg)
# BAD, due to the call below
connection = Faraday.new(host, ssl: { verify: arg })
response = connection.get(path)
response = connection.get(path) # $ Alert
end
verify_as_arg("http://example.com", "/", false)
@@ -41,7 +41,7 @@ verify_as_arg("http://example.com", "/", false)
def verify_mode_as_arg(host, path, arg)
# BAD, due to the call below
connection = Faraday.new(host, ssl: { verify_mode: arg })
response = connection.get(path)
response = connection.get(path) # $ Alert
end
verify_mode_as_arg("http://example.com", "/", OpenSSL::SSL::VERIFY_NONE)

View File

@@ -3,7 +3,7 @@ require "httpclient"
# BAD
client = HTTPClient.new
client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
client.get("https://example.com")
client.get("https://example.com") # $ Alert
# GOOD
client = HTTPClient.new
@@ -15,4 +15,4 @@ client = HTTPClient.new
client.get("https://example.com")
# GOOD
HTTPClient.get("https://example.com/")
HTTPClient.get("https://example.com/")

View File

@@ -1,19 +1,19 @@
require "httparty"
# BAD
HTTParty.get("http://example.com/", verify: false)
HTTParty.get("http://example.com/", verify: false) # $ Alert
# BAD
HTTParty.get("http://example.com/", verify_peer: false)
HTTParty.get("http://example.com/", verify_peer: false) # $ Alert
# BAD
HTTParty.get("http://example.com/", { verify_peer: false })
HTTParty.get("http://example.com/", { verify_peer: false }) # $ Alert
# BAD
HTTParty.post("http://example.com/", body: "some_data", verify: false)
HTTParty.post("http://example.com/", body: "some_data", verify: false) # $ Alert
# BAD
HTTParty.post("http://example.com/", { body: "some_data", verify: false })
HTTParty.post("http://example.com/", { body: "some_data", verify: false }) # $ Alert
# GOOD
HTTParty.get("http://example.com/")
@@ -34,4 +34,4 @@ HTTParty.post("http://example.com/", body: "some_data", verify: true)
HTTParty.post("http://example.com/", { body: "some_data" })
# GOOD
HTTParty.post("http://example.com/", { body: "some_data", verify: true })
HTTParty.post("http://example.com/", { body: "some_data", verify: true })

View File

@@ -6,5 +6,5 @@ http = Net::HTTP.new uri.host, uri.port
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new uri.request_uri
response = http.request request
response = http.request request # $ Alert
puts response.body

View File

@@ -1,24 +1,24 @@
require "open-uri"
# BAD
Kernel.open("https://example.com", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
Kernel.open("https://example.com", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) # $ Alert
# BAD
Kernel.open("https://example.com", { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE })
Kernel.open("https://example.com", { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }) # $ Alert
# BAD
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }
Kernel.open("https://example.com", options)
Kernel.open("https://example.com", options) # $ Alert
# BAD
URI.parse("https://example.com").open(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
URI.parse("https://example.com").open(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) # $ Alert
# BAD
URI.parse("https://example.com").open({ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE })
URI.parse("https://example.com").open({ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }) # $ Alert
# BAD
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }
URI.parse("https://example.com").open(options)
URI.parse("https://example.com").open(options) # $ Alert
# GOOD
Kernel.open("https://example.com")
@@ -44,4 +44,4 @@ URI.parse("https://example.com").open({ ssl_verify_mode: OpenSSL::SSL::VERIFY_PE
# GOOD
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER }
URI.parse("https://example.com").open(options)
URI.parse("https://example.com").open(options)

Some files were not shown because too many files have changed in this diff Show More